From 2cc50a1260bd67439233f6dcee206774cd04b183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Wed, 1 May 2019 12:38:27 +0200 Subject: [PATCH 001/664] Docs: RichText: elaborate on use of pre-wrap (#15170) --- .../block-editor/src/components/rich-text/style.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index cae87d5129b83e..d1560a163c24d4 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -6,6 +6,7 @@ .block-editor-rich-text__editable { position: relative; + // In HTML, leading and trailing spaces are not visible, and multiple spaces // elsewhere are visually reduced to one space. This rule prevents spaces // from collapsing so all space is visible in the editor and can be removed. @@ -15,6 +16,15 @@ // breaking spaces in between words. If also prevent Firefox from inserting // a trailing `br` node to visualise any trailing space, causing the element // to be saved. + // + // > Authors are encouraged to set the 'white-space' property on editing + // > hosts and on markup that was originally created through these editing + // > mechanisms to the value 'pre-wrap'. Default HTML whitespace handling is + // > not well suited to WYSIWYG editing, and line wrapping will not work + // > correctly in some corner cases if 'white-space' is left at its default + // > value. + // > + // > https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors white-space: pre-wrap !important; > p:first-child { From 56e6d775b4c3e203468a2042313b250b00320e63 Mon Sep 17 00:00:00 2001 From: Jackie6 <541172791@qq.com> Date: Wed, 1 May 2019 06:52:08 -0400 Subject: [PATCH 002/664] Change the filtervalue to lowercase (#14786) * Change the filtervalue to lowercase * Remove the space between paratheses Co-Authored-By: Jackie6 <541172791@qq.com> --- .../components/post-taxonomies/hierarchical-term-selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js index bc36ab06170587..4403884ed83598 100644 --- a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js @@ -302,7 +302,7 @@ class HierarchicalTermSelector extends Component { // If the term's name contains the filterValue, or it has children // (i.e. some child matched at some point in the tree) then return it. - if ( -1 !== term.name.toLowerCase().indexOf( filterValue ) || term.children.length > 0 ) { + if ( -1 !== term.name.toLowerCase().indexOf( filterValue.toLowerCase() ) || term.children.length > 0 ) { return term; } From 655270648fa075c6d74b96244a08202ac16227d0 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 1 May 2019 12:18:02 +0100 Subject: [PATCH 003/664] Update: Image Block: Change image by drag & drop an image from the desktop (#14983) --- packages/block-library/src/image/edit.js | 60 ++++++++++++------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index bc4a18927b0f0a..cea813aefc4a6d 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -400,39 +400,40 @@ class ImageEdit extends Component { ) } ); + const src = isExternal ? url : undefined; + const labels = { + title: ! url ? __( 'Image' ) : __( 'Edit image' ), + instructions: __( 'Drag an image to upload, select a file from your library or add one from an URL.' ), + }; + const mediaPreview = ( !! url && { ); + const mediaPlaceholder = ( + } + className={ className } + labels={ labels } + onSelect={ this.onSelectImage } + onSelectURL={ this.onSelectURL } + onDoubleClick={ this.toggleIsEditing } + onCancel={ !! url && this.toggleIsEditing } + notices={ noticeUI } + onError={ this.onUploadError } + accept="image/*" + allowedTypes={ ALLOWED_MEDIA_TYPES } + value={ { id, src } } + mediaPreview={ mediaPreview } + dropZoneUIOnly={ ! isEditing && url } + /> + ); if ( isEditing || ! url ) { - const src = isExternal ? url : undefined; - - const labels = { - title: ! url ? __( 'Image' ) : __( 'Edit image' ), - instructions: __( 'Drag an image to upload, select a file from your library or add one from an URL.' ), - }; - - const mediaPreview = ( !! url && { ); - return ( { controls } - } - className={ className } - labels={ labels } - onSelect={ this.onSelectImage } - onSelectURL={ this.onSelectURL } - onDoubleClick={ this.toggleIsEditing } - onCancel={ !! url && this.toggleIsEditing } - notices={ noticeUI } - onError={ this.onUploadError } - accept="image/*" - allowedTypes={ ALLOWED_MEDIA_TYPES } - value={ { id, src } } - mediaPreview={ mediaPreview } - /> + { mediaPlaceholder } ); } @@ -707,6 +708,7 @@ class ImageEdit extends Component { /> ) } + { mediaPlaceholder } ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ From a9a390767cdcd7f291e98bed8d56602dea027b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Wed, 1 May 2019 13:24:59 +0200 Subject: [PATCH 004/664] Components: remove __unstablePositionedAtSelection (#15035) * Components: remove __unstablePositionedAtSelection * Address feedback * Address feedback --- .../src/components/writing-flow/index.js | 2 +- packages/components/src/index.js | 1 - packages/components/src/popover/index.js | 1 + .../src/positioned-at-selection/index.js | 65 ------------------- packages/dom/README.md | 4 -- packages/dom/src/dom.js | 8 +-- packages/format-library/src/image/index.js | 32 +++++---- 7 files changed, 24 insertions(+), 89 deletions(-) delete mode 100644 packages/components/src/positioned-at-selection/index.js diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index d03b11d0a84f7a..951df53592ae33 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -284,7 +284,7 @@ class WritingFlow extends Component { if ( ! isVertical ) { this.verticalRect = null; } else if ( ! this.verticalRect ) { - this.verticalRect = computeCaretRect( target ); + this.verticalRect = computeCaretRect(); } if ( isShift ) { diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 65c44bb660ef38..ce378c0b09e3a8 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -43,7 +43,6 @@ export { default as PanelHeader } from './panel/header'; export { default as PanelRow } from './panel/row'; export { default as Placeholder } from './placeholder'; export { default as Popover } from './popover'; -export { default as __unstablePositionedAtSelection } from './positioned-at-selection'; export { default as QueryControls } from './query-controls'; export { default as RadioControl } from './radio-control'; export { default as RangeControl } from './range-control'; diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index bdf1b5c0c7c624..4515cd2facfe78 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -282,6 +282,7 @@ class Popover extends Component { getAnchorRect, expandOnMobile, animate = true, + anchorRect, /* eslint-enable no-unused-vars */ ...contentProps } = this.props; diff --git a/packages/components/src/positioned-at-selection/index.js b/packages/components/src/positioned-at-selection/index.js deleted file mode 100644 index 37c039d77777df..00000000000000 --- a/packages/components/src/positioned-at-selection/index.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; -import { getOffsetParent, getRectangleFromRange } from '@wordpress/dom'; - -/** - * Returns a style object for applying as `position: absolute` for an element - * relative to the bottom-center of the current selection. Includes `top` and - * `left` style properties. - * - * @return {Object} Style object. - */ -function getCurrentCaretPositionStyle() { - const selection = window.getSelection(); - - // Unlikely, but in the case there is no selection, return empty styles so - // as to avoid a thrown error by `Selection#getRangeAt` on invalid index. - if ( selection.rangeCount === 0 ) { - return {}; - } - - // Get position relative viewport. - const rect = getRectangleFromRange( selection.getRangeAt( 0 ) ); - let top = rect.top + rect.height; - let left = rect.left + ( rect.width / 2 ); - - // Offset by positioned parent, if one exists. - const offsetParent = getOffsetParent( selection.anchorNode ); - if ( offsetParent ) { - const parentRect = offsetParent.getBoundingClientRect(); - top -= parentRect.top; - left -= parentRect.left; - } - - return { top, left }; -} - -/** - * Component which renders itself positioned under the current caret selection. - * The position is calculated at the time of the component being mounted, so it - * should only be mounted after the desired selection has been made. - * - * @type {WPComponent} - */ -export default class PositionedAtSelection extends Component { - constructor() { - super( ...arguments ); - - this.state = { - style: getCurrentCaretPositionStyle(), - }; - } - - render() { - const { children } = this.props; - const { style } = this.state; - - return ( -
- { children } -
- ); - } -} diff --git a/packages/dom/README.md b/packages/dom/README.md index 1212dea1f4f5d5..d52e47552f342e 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -18,10 +18,6 @@ npm install @wordpress/dom --save Get the rectangle for the selection in a container. -_Parameters_ - -- _container_ `Element`: Editable container. - _Returns_ - `?DOMRect`: The rectangle. diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index aba561f992d593..b676448ec4424f 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -235,15 +235,9 @@ export function getRectangleFromRange( range ) { /** * Get the rectangle for the selection in a container. * - * @param {Element} container Editable container. - * * @return {?DOMRect} The rectangle. */ -export function computeCaretRect( container ) { - if ( ! container.isContentEditable ) { - return; - } - +export function computeCaretRect() { const selection = window.getSelection(); const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index 2e2bc942de76b3..c4934e59a8e3cc 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -1,12 +1,13 @@ /** * WordPress dependencies */ -import { Path, SVG, TextControl, Popover, IconButton, __unstablePositionedAtSelection } from '@wordpress/components'; +import { Path, SVG, TextControl, Popover, IconButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { Component, useMemo } from '@wordpress/element'; import { insertObject } from '@wordpress/rich-text'; import { MediaUpload, RichTextToolbarButton, MediaUploadCheck } from '@wordpress/block-editor'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; +import { computeCaretRect } from '@wordpress/dom'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -14,6 +15,17 @@ const name = 'core/image'; const stopKeyPropagation = ( event ) => event.stopPropagation(); +const PopoverAtImage = ( { dependencies, ...props } ) => { + return ( + computeCaretRect(), dependencies ) } + { ...props } + /> + ); +}; + export const image = { name, title: __( 'Image' ), @@ -81,9 +93,6 @@ export const image = { render() { const { value, onChange, isObjectActive, activeObjectAttributes } = this.props; const { style } = activeObjectAttributes; - // Rerender PositionedAtSelection when the selection changes or when - // the width changes. - const key = value.start + style; return ( @@ -113,10 +122,11 @@ export const image = { return null; } } /> } - { isObjectActive && <__unstablePositionedAtSelection key={ key }> - { // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ } @@ -154,8 +164,8 @@ export const image = { { /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } - - } + + } ); } From b9e2e893f6a0ff30545869466a6f3c6892ad502c Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 1 May 2019 13:23:39 -0400 Subject: [PATCH 005/664] Bump version to 5.6.0 (#15373) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 006fcfb6aeeb20..c348ac6a5c9ae8 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.6.0-rc.1 + * Version: 5.6.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index cac714a983cf21..be5c378712df5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.6.0-rc.1", + "version": "5.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9e6b1cab001396..bf3bbc7e2e42b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.6.0-rc.1", + "version": "5.6.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From b54161e40a03b972f5fd5ee53a36e04ae92de0c4 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 1 May 2019 16:45:37 -0400 Subject: [PATCH 006/664] Bump version to 5.6.1 (#15378) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index c348ac6a5c9ae8..80d8252a4fed3d 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.6.0 + * Version: 5.6.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index be5c378712df5e..2ea228aa9e96f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.6.0", + "version": "5.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index bf3bbc7e2e42b3..ef38c002ece9ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.6.0", + "version": "5.6.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From b36a56d4222922dc33d07f422c6d04edb7d74e9a Mon Sep 17 00:00:00 2001 From: Andrey Date: Thu, 2 May 2019 16:54:33 +1000 Subject: [PATCH 007/664] Update README.md (#15386) * Update README.md This is a very nasty gotcha that can suck out hours from your day. * Update packages/block-editor/src/components/inner-blocks/README.md Co-Authored-By: andreyc0d3r * Update packages/block-editor/src/components/inner-blocks/README.md Co-Authored-By: andreyc0d3r --- packages/block-editor/src/components/inner-blocks/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index 321f9a71d0d114..331e15c4c4f84d 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -105,9 +105,9 @@ Template locking of `InnerBlocks` is similar to [Custom Post Type templates lock Template locking allows locking the `InnerBlocks` area for the current template. *Options:* -- `all` — prevents all operations. It is not possible to insert new blocks. Move existing blocks or delete them. -- `insert` — prevents inserting or removing blocks, but allows moving existing ones. -- `false` — prevents locking from being applied to an `InnerBlocks` area even if a parent block contains locking. +- `'all'` — prevents all operations. It is not possible to insert new blocks. Move existing blocks or delete them. +- `'insert'` — prevents inserting or removing blocks, but allows moving existing ones. +- `false` — prevents locking from being applied to an `InnerBlocks` area even if a parent block contains locking. ( Boolean ) If locking is not set in an `InnerBlocks` area: the locking of the parent `InnerBlocks` area is used. From 8f0cb516ef5d2c5cbd1edde1ec4c312b961558ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Thu, 2 May 2019 09:54:13 +0200 Subject: [PATCH 008/664] Format Library: Code => Inline Code (#15199) --- packages/format-library/src/bold/index.js | 5 +++-- packages/format-library/src/code/index.js | 5 +++-- packages/format-library/src/image/index.js | 5 +++-- packages/format-library/src/italic/index.js | 5 +++-- packages/format-library/src/link/index.js | 5 +++-- packages/format-library/src/strikethrough/index.js | 5 +++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index 70eb752cee8470..d90bbea5d72412 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -7,10 +7,11 @@ import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; const name = 'core/bold'; +const title = __( 'Bold' ); export const bold = { name, - title: __( 'Bold' ), + title, tagName: 'strong', className: null, edit( { isActive, value, onChange } ) { @@ -26,7 +27,7 @@ export const bold = { event.stopPropagation(); @@ -28,7 +29,7 @@ const PopoverAtImage = ( { dependencies, ...props } ) => { export const image = { name, - title: __( 'Image' ), + title, keywords: [ __( 'photo' ), __( 'media' ) ], object: true, tagName: 'img', @@ -98,7 +99,7 @@ export const image = { } - title={ __( 'Inline Image' ) } + title={ title } onClick={ this.openModal } isActive={ isObjectActive } /> diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index 194a0691c22774..eee6cd14a649c9 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -7,10 +7,11 @@ import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; const name = 'core/italic'; +const title = __( 'Italic' ); export const italic = { name, - title: __( 'Italic' ), + title, tagName: 'em', className: null, edit( { isActive, value, onChange } ) { @@ -26,7 +27,7 @@ export const italic = { Date: Thu, 2 May 2019 04:03:58 -0400 Subject: [PATCH 009/664] Latest Post Block (Post Content Support) iteration (#14627) * Ran npm run test-e2e -- -u * Revert "Ran npm run test-e2e -- -u" This reverts commit 98e9b739b6a1aff6d7357dfef9858959da391ad1. * Rename postsToShow to postCount * Rebased into master branch * Remove redundant calls to updateAttribute and make changes in place under onChange * Fix grid view CSS style error * Fixed editor 55 words error for post content case * Removed HTML tags from excerpt and only displaying text * Changed constant name temp to excerptElement --- .../block-library/src/latest-posts/edit.js | 100 +++++++++++++----- .../block-library/src/latest-posts/index.php | 70 ++++++++++-- .../block-library/src/latest-posts/style.scss | 5 +- 3 files changed, 136 insertions(+), 39 deletions(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index e8b6896ae514d3..433810e467b0fb 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -7,11 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - Component, - Fragment, - RawHTML, -} from '@wordpress/element'; +import { Component, Fragment, RawHTML } from '@wordpress/element'; import { PanelBody, Placeholder, @@ -20,6 +16,7 @@ import { Spinner, ToggleControl, Toolbar, + RadioControl, } from '@wordpress/components'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; @@ -45,7 +42,6 @@ class LatestPostsEdit extends Component { this.state = { categoriesList: [], }; - this.toggleDisplayPostDate = this.toggleDisplayPostDate.bind( this ); } componentDidMount() { @@ -71,35 +67,59 @@ class LatestPostsEdit extends Component { this.isStillMounted = false; } - toggleDisplayPostDate() { - const { displayPostDate } = this.props.attributes; - const { setAttributes } = this.props; - - setAttributes( { displayPostDate: ! displayPostDate } ); - } - render() { const { attributes, setAttributes, latestPosts } = this.props; const { categoriesList } = this.state; - const { displayPostDate, postLayout, columns, order, orderBy, categories, postsToShow } = attributes; + const { displayPostContentRadio, displayPostContent, displayPostDate, postLayout, columns, order, orderBy, categories, postCount, excerptLength } = attributes; const inspectorControls = ( - + + setAttributes( { displayPostContent: value } ) } + /> + { displayPostContent && + setAttributes( { displayPostContentRadio: value } ) } + /> + } + { displayPostContent && displayPostContentRadio === 'excerpt' && + setAttributes( { excerptLength: value } ) } + min={ 10 } + max={ 100 } + /> + } + + + + setAttributes( { displayPostDate: value } ) } + /> + + + setAttributes( { order: value } ) } onOrderByChange={ ( value ) => setAttributes( { orderBy: value } ) } onCategoryChange={ ( value ) => setAttributes( { categories: '' !== value ? value : undefined } ) } - onNumberOfItemsChange={ ( value ) => setAttributes( { postsToShow: value } ) } - /> - setAttributes( { postCount: value } ) } /> { postLayout === 'grid' && postsToShow ? - latestPosts.slice( 0, postsToShow ) : + const displayPosts = latestPosts.length > postCount ? + latestPosts.slice( 0, postCount ) : latestPosts; const layoutControls = [ @@ -163,6 +183,7 @@ class LatestPostsEdit extends Component {
    { displayPosts.map( ( post, i ) => { const titleTrimmed = post.title.rendered.trim(); + let excerpt = post.excerpt.rendered; + if ( post.excerpt.raw === '' ) { + excerpt = post.content.raw; + } + const excerptElement = document.createElement( 'div' ); + excerptElement.innerHTML = excerpt; + excerpt = excerptElement.textContent || excerptElement.innerText || ''; return (
  • @@ -186,6 +214,26 @@ class LatestPostsEdit extends Component { { dateI18n( dateFormat, post.date_gmt ) } } + { displayPostContent && displayPostContentRadio === 'excerpt' && + + } + { displayPostContent && displayPostContentRadio === 'full_post' && +
    + + { post.content.raw.trim() } + +
    + }
  • ); } ) } @@ -196,13 +244,13 @@ class LatestPostsEdit extends Component { } export default withSelect( ( select, props ) => { - const { postsToShow, order, orderBy, categories } = props.attributes; + const { postCount, order, orderBy, categories } = props.attributes; const { getEntityRecords } = select( 'core' ); const latestPostsQuery = pickBy( { categories, order, orderby: orderBy, - per_page: postsToShow, + per_page: postCount, }, ( value ) => ! isUndefined( value ) ); return { latestPosts: getEntityRecords( 'postType', 'post', latestPostsQuery ), diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index e3ec985bab9c82..d3a7a30c90c2f5 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -14,7 +14,7 @@ */ function render_block_core_latest_posts( $attributes ) { $args = array( - 'posts_per_page' => $attributes['postsToShow'], + 'posts_per_page' => $attributes['postCount'], 'post_status' => 'publish', 'order' => $attributes['order'], 'orderby' => $attributes['orderBy'], @@ -29,6 +29,8 @@ function render_block_core_latest_posts( $attributes ) { $list_items_markup = ''; + $excerpt_length = $attributes['excerptLength']; + foreach ( $recent_posts as $post ) { $title = get_the_title( $post ); if ( ! $title ) { @@ -48,10 +50,44 @@ function render_block_core_latest_posts( $attributes ) { ); } + if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent'] + && isset( $attributes['displayPostContentRadio'] ) && 'excerpt' == $attributes['displayPostContentRadio'] ) { + $post_excerpt = $post->post_excerpt; + if ( ! ( $post_excerpt ) ) { + $post_excerpt = $post->post_content; + } + $trimmed_excerpt = esc_html( wp_trim_words( $post_excerpt, $excerpt_length, ' … ' ) ); + + $list_items_markup .= sprintf( + '
    %1$s', + $trimmed_excerpt + ); + + if ( strpos( $trimmed_excerpt, ' … ' ) !== false ) { + $list_items_markup .= sprintf( + '%2$s
    ', + esc_url( get_permalink( $post ) ), + __( 'Read More' ) + ); + } else { + $list_items_markup .= sprintf( + '' + ); + } + } + + if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent'] + && isset( $attributes['displayPostContentRadio'] ) && 'full_post' == $attributes['displayPostContentRadio'] ) { + $list_items_markup .= sprintf( + '
    %1$s
    ', + wp_kses_post( html_entity_decode( $post->post_content, ENT_QUOTES, get_option( 'blog_charset' ) ) ) + ); + } + $list_items_markup .= "\n"; } - $class = 'wp-block-latest-posts'; + $class = 'wp-block-latest-posts wp-block-latest-posts__list'; if ( isset( $attributes['align'] ) ) { $class .= ' align' . $attributes['align']; } @@ -89,37 +125,49 @@ function register_block_core_latest_posts() { 'core/latest-posts', array( 'attributes' => array( - 'align' => array( + 'align' => array( 'type' => 'string', 'enum' => array( 'left', 'center', 'right', 'wide', 'full' ), ), - 'className' => array( + 'className' => array( 'type' => 'string', ), - 'categories' => array( + 'categories' => array( 'type' => 'string', ), - 'postsToShow' => array( + 'postCount' => array( 'type' => 'number', 'default' => 5, ), - 'displayPostDate' => array( + 'displayPostContent' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'displayPostContentRadio' => array( + 'type' => 'string', + 'default' => 'excerpt', + ), + 'excerptLength' => array( + 'type' => 'number', + 'default' => 55, + ), + 'displayPostDate' => array( 'type' => 'boolean', 'default' => false, ), - 'postLayout' => array( + 'postLayout' => array( 'type' => 'string', 'default' => 'list', ), - 'columns' => array( + 'columns' => array( 'type' => 'number', 'default' => 3, ), - 'order' => array( + 'order' => array( 'type' => 'string', 'default' => 'desc', ), - 'orderBy' => array( + 'orderBy' => array( 'type' => 'string', 'default' => 'date', ), diff --git a/packages/block-library/src/latest-posts/style.scss b/packages/block-library/src/latest-posts/style.scss index 0877c3d5dee9cc..a72331d6eed09f 100644 --- a/packages/block-library/src/latest-posts/style.scss +++ b/packages/block-library/src/latest-posts/style.scss @@ -7,11 +7,13 @@ /*rtl:ignore*/ margin-left: 2em; } + &.wp-block-latest-posts__list { + list-style: none; + } &.is-grid { display: flex; flex-wrap: wrap; padding: 0; - list-style: none; li { margin: 0 16px 16px 0; @@ -33,4 +35,3 @@ color: $dark-gray-300; font-size: $default-font-size; } - From 9a99071fac9f903ec696f957849f47f6c9a30229 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis Date: Thu, 2 May 2019 18:52:56 +0300 Subject: [PATCH 010/664] [Rnmobile] Fix the list handling on Android (#15168) * If text already changed, don't modify it * Able to not lose content * Use a flag to signal Aztec-originated changes And assume that when that flag is false, component changes need to get sent/reflected down to Aztec. * Differentiate Android and iOS since assumptions diverged The iOS side still expects to just check against `this.lastContent` to force the change into Aztec. * Force Aztec update if "Enter" fired before text change * Need to specify firedAfterTextChanged on all Aztec events * Fix lint issues * chore: Fix: Lint error that makes unit tests (and CI tests) fail. (#15073) * Trivial change to trigger Travis * Revert "Trivial change to trigger Travis" This reverts commit e22ffde2ef44bf376508e11669e2feb8b3addcca. * Just use onFormatChange which now defaults to "force" * Have Aztec delete the detected Enter key for paragraphs Aztec-Android doesn't swallow the Enter key (like the list handling does) so, instruct Aztec to delete it for the paragraph block. * Don't force Aztec update on format button toggles Doing this by reverting onFormatChange's behavior back to assuming doUpdateChild is false by default. --- .../src/components/rich-text/index.native.js | 48 ++++++++++++++----- .../src/paragraph/edit.native.js | 1 + 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index e22ddec63e14b8..aca01b3897556f 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -163,9 +163,9 @@ export class RichText extends Component { // value. This also provides an opportunity for the parent component to // determine whether the before/after value has changed using a trivial // strict equality operation. - if ( isEmpty( after ) ) { + if ( isEmpty( after ) && before.text.length === currentRecord.text.length ) { before = currentRecord; - } else if ( isEmpty( before ) ) { + } else if ( isEmpty( before ) && after.text.length === currentRecord.text.length ) { after = currentRecord; } @@ -230,7 +230,7 @@ export class RichText extends Component { } ); if ( newContent && newContent !== this.props.value ) { this.props.onChange( newContent ); - if ( record.needsSelectionUpdate && record.start && record.end ) { + if ( record.needsSelectionUpdate && record.start && record.end && doUpdateChild ) { this.forceSelectionUpdate( record.start, record.end ); } } else { @@ -274,6 +274,8 @@ export class RichText extends Component { this.lastEventCount = event.nativeEvent.eventCount; const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); this.lastContent = contentWithoutRootTag; + this.comesFromAztec = true; + this.firedAfterTextChanged = true; // the onChange event always fires after the fact this.props.onChange( this.lastContent ); } @@ -289,6 +291,8 @@ export class RichText extends Component { // eslint-disable-next-line no-unused-vars onEnter( event ) { this.lastEventCount = event.nativeEvent.eventCount; + this.comesFromAztec = true; + this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; const currentRecord = this.createRecord( { ...event.nativeEvent, @@ -303,10 +307,10 @@ export class RichText extends Component { this.setState( { needsSelectionUpdate: false, } ); - this.onSplit( ...split( currentRecord ).map( this.valueToFormat ) ); + this.splitContent( currentRecord ); } else { const insertedLineSeparator = { needsSelectionUpdate: true, ...insertLineSeparator( currentRecord ) }; - this.onFormatChangeForceChild( insertedLineSeparator ); + this.onFormatChange( insertedLineSeparator, ! this.firedAfterTextChanged ); } } else if ( event.shiftKey || ! this.onSplit ) { const insertedLineBreak = { needsSelectionUpdate: true, ...insert( currentRecord, '\n' ) }; @@ -393,7 +397,6 @@ export class RichText extends Component { }, } ); this.lastContent = this.valueToFormat( linkedRecord ); - this.lastEventCount = undefined; this.props.onChange( this.lastContent ); // Allows us to ask for this information when we get a report. @@ -425,7 +428,6 @@ export class RichText extends Component { const recordToInsert = create( { html: pastedContent } ); const insertedContent = insert( currentRecord, recordToInsert ); const newContent = this.valueToFormat( insertedContent ); - this.lastEventCount = undefined; this.lastContent = newContent; // explicitly set selection after inline paste @@ -488,6 +490,8 @@ export class RichText extends Component { // we don't want to refresh aztec native as no content can have changed from this event // let's update lastContent to prevent that in shouldComponentUpdate this.lastContent = newContent; + this.comesFromAztec = true; + this.firedAfterTextChanged = true; // Selection change event always fires after the fact this.props.onChange( this.lastContent ); } } @@ -573,7 +577,7 @@ export class RichText extends Component { // This logic will handle the selection when two blocks are merged or when block is split // into two blocks - if ( nextTextContent.startsWith( this.savedContent ) ) { + if ( nextTextContent.startsWith( this.savedContent ) && this.savedContent && this.savedContent.length > 0 ) { let length = this.savedContent.length; if ( length === 0 && nextTextContent !== this.props.value ) { length = this.props.value.length; @@ -592,7 +596,7 @@ export class RichText extends Component { } shouldComponentUpdate( nextProps ) { - if ( nextProps.tagName !== this.props.tagName || nextProps.isSelected !== this.props.isSelected ) { + if ( nextProps.tagName !== this.props.tagName ) { this.lastEventCount = undefined; this.lastContent = undefined; return true; @@ -602,13 +606,27 @@ export class RichText extends Component { // It was removed in https://github.com/WordPress/gutenberg/pull/12417 to fix undo/redo problem. // If the component is changed React side (undo/redo/merging/splitting/custom text actions) - // we need to make sure the native is updated as well + // we need to make sure the native is updated as well. + + const previousValueToCheck = Platform.OS === 'android' ? this.props.value : this.lastContent; + + // Also, don't trust the "this.lastContent" as on Android, incomplete text events arrive + // with only some of the text, while the virtual keyboard's suggestion system does its magic. + // ** compare with this.lastContent for optimizing performance by not forcing Aztec with text it already has + // , but compare with props.value to not lose "half word" text because of Android virtual keyb autosuggestion behavior if ( ( typeof nextProps.value !== 'undefined' ) && - ( typeof this.lastContent !== 'undefined' ) && - nextProps.value !== this.lastContent ) { + ( typeof this.props.value !== 'undefined' ) && + ( Platform.OS === 'ios' || ( Platform.OS === 'android' && ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) ) ) && + nextProps.value !== previousValueToCheck ) { this.lastEventCount = undefined; // force a refresh on the native side } + if ( Platform.OS === 'android' && this.comesFromAztec === false ) { + if ( this.needsSelectionUpdate ) { + this.lastEventCount = undefined; // force a refresh on the native side + } + } + return true; } @@ -683,6 +701,11 @@ export class RichText extends Component { } } + if ( this.comesFromAztec ) { + this.comesFromAztec = false; + this.firedAfterTextChanged = false; + } + return ( { isSelected && this.multilineTag === 'li' && ( @@ -713,6 +736,7 @@ export class RichText extends Component { text={ { text: html, eventCount: this.lastEventCount, selection } } placeholder={ this.props.placeholder } placeholderTextColor={ this.props.placeholderTextColor || styles[ 'block-editor-rich-text' ].textDecorationColor } + deleteEnter={ this.props.deleteEnter } onChange={ this.onChange } onFocus={ this.onFocus } onBlur={ this.onBlur } diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 12abc71c8d22bd..e7eafbac1797c6 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -125,6 +125,7 @@ class ParagraphEdit extends Component { isSelected={ this.props.isSelected } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props + deleteEnter={ true } style={ style } onChange={ ( nextContent ) => { setAttributes( { From 92e73e8557d14b66819b97dc1f06819dbd105ad3 Mon Sep 17 00:00:00 2001 From: David Aguilera Date: Thu, 2 May 2019 17:55:14 +0200 Subject: [PATCH 011/664] Code Block should not render embeds (or shortcodes) (#13996) * Fixes 13927. * Preventing isolated URLs from becoming an embed in Code blocks. * Refactored code to make it more clear. Escaping forward slashes in isolated URLs only. * Using lodash's `flow` to combine the transformations applied to the content of a code block on saving * Modified regexp to escape isolated URLs so that it matches the one used in WordPress' embed PHP class * Documented helper functions * Fixed reference in comment * Escaping (and unescaping) shortcode and link characters 'dynamically' in CodeEdit * Fixed a typo in a substitution regexp * Added unit tests for the escape and unescape functions in core/code block * Renamed test file to match standards --- packages/block-library/src/code/edit.js | 5 +- .../block-library/src/code/edit.native.js | 5 +- packages/block-library/src/code/test/utils.js | 62 ++++++++++ packages/block-library/src/code/utils.js | 117 ++++++++++++++++++ 4 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 packages/block-library/src/code/test/utils.js create mode 100644 packages/block-library/src/code/utils.js diff --git a/packages/block-library/src/code/edit.js b/packages/block-library/src/code/edit.js index 5aa4b337bd3f12..2926774f46ff22 100644 --- a/packages/block-library/src/code/edit.js +++ b/packages/block-library/src/code/edit.js @@ -7,13 +7,14 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { PlainText } from '@wordpress/block-editor'; +import { escape, unescape } from './utils'; export default function CodeEdit( { attributes, setAttributes, className } ) { return (
    setAttributes( { content } ) } + value={ unescape( attributes.content ) } + onChange={ ( content ) => setAttributes( { content: escape( content ) } ) } placeholder={ __( 'Write code…' ) } aria-label={ __( 'Code' ) } /> diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js index 264814f760dee7..3f021790c2a403 100644 --- a/packages/block-library/src/code/edit.native.js +++ b/packages/block-library/src/code/edit.native.js @@ -12,6 +12,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { PlainText } from '@wordpress/block-editor'; +import { escape, unescape } from './utils'; /** * Block code style @@ -26,11 +27,11 @@ export default function CodeEdit( props ) { return ( <View> <PlainText - value={ attributes.content } + value={ unescape( attributes.content ) } style={ [ style, styles.blockCode ] } multiline={ true } underlineColorAndroid="transparent" - onChange={ ( content ) => setAttributes( { content } ) } + onChange={ ( content ) => setAttributes( { content: escape( content ) } ) } placeholder={ __( 'Write code…' ) } aria-label={ __( 'Code' ) } isSelected={ props.isSelected } diff --git a/packages/block-library/src/code/test/utils.js b/packages/block-library/src/code/test/utils.js new file mode 100644 index 00000000000000..4926eef16d5282 --- /dev/null +++ b/packages/block-library/src/code/test/utils.js @@ -0,0 +1,62 @@ +/** + * Internal dependencies + */ +import { escape, unescape } from '../utils'; + +describe( 'core/code', () => { + describe( 'escape()', () => { + it( 'should escape ampersands', () => { + const text = escape( '&' ); + expect( text ).toBe( '&amp;' ); + } ); + + it( 'should escape opening square brackets', () => { + const text = escape( '[shortcode][/shortcode]' ); + expect( text ).toBe( '&#91;shortcode]&#91;/shortcode]' ); + } ); + + it( 'should escape the protocol of an isolated url', () => { + const text = escape( 'https://example.com/test/' ); + expect( text ).toBe( 'https:&#47;&#47;example.com/test/' ); + } ); + + it( 'should not escape the protocol of a non isolated url', () => { + const text = escape( 'Text https://example.com/test/' ); + expect( text ).toBe( 'Text https://example.com/test/' ); + } ); + + it( 'should escape ampersands last', () => { + const text = escape( '[shortcode][/shortcode]' ); + expect( text ).toBe( '&#91;shortcode]&#91;/shortcode]' ); + expect( text ).not.toBe( '&amp;#91;shortcode]&amp;#91;/shortcode]' ); + } ); + } ); + + describe( 'unescape()', () => { + it( 'should unescape escaped ampersands', () => { + const text = unescape( '&amp;' ); + expect( text ).toBe( '&' ); + } ); + + it( 'should unescape escaped opening square brackets', () => { + const text = unescape( '&#91;shortcode]&#91;/shortcode]' ); + expect( text ).toBe( '[shortcode][/shortcode]' ); + } ); + + it( 'should unescape the escaped protocol of an isolated url', () => { + const text = unescape( 'https:&#47;&#47;example.com/test/' ); + expect( text ).toBe( 'https://example.com/test/' ); + } ); + + it( 'should revert the result of escape()', () => { + const ampersand = unescape( escape( '&' ) ); + expect( ampersand ).toBe( '&' ); + + const squareBracket = unescape( escape( '[shortcode][/shortcode]' ) ); + expect( squareBracket ).toBe( '[shortcode][/shortcode]' ); + + const url = unescape( escape( 'https://example.com/test/' ) ); + expect( url ).toBe( 'https://example.com/test/' ); + } ); + } ); +} ); diff --git a/packages/block-library/src/code/utils.js b/packages/block-library/src/code/utils.js new file mode 100644 index 00000000000000..fb8557a95b18ee --- /dev/null +++ b/packages/block-library/src/code/utils.js @@ -0,0 +1,117 @@ +/** + * External dependencies + */ +import { flow } from 'lodash'; + +/** + * Escapes ampersands, shortcodes, and links. + * + * @param {string} content The content of a code block. + * @return {string} The given content with some characters escaped. + */ +export function escape( content ) { + return flow( + escapeAmpersands, + escapeOpeningSquareBrackets, + escapeProtocolInIsolatedUrls + )( content || '' ); +} + +/** + * Unescapes escaped ampersands, shortcodes, and links. + * + * @param {string} content Content with (maybe) escaped ampersands, shortcodes, and links. + * @return {string} The given content with escaped characters unescaped. + */ +export function unescape( content ) { + return flow( + unescapeProtocolInIsolatedUrls, + unescapeOpeningSquareBrackets, + unescapeAmpersands + )( content || '' ); +} + +/** + * Returns the given content with all its ampersand characters converted + * into their HTML entity counterpart (i.e. & => &amp;) + * + * @param {string} content The content of a code block. + * @return {string} The given content with its ampersands converted into + * their HTML entity counterpart (i.e. & => &amp;) + */ +function escapeAmpersands( content ) { + return content.replace( /&/g, '&amp;' ); +} + +/** + * Returns the given content with all &amp; HTML entities converted into &. + * + * @param {string} content The content of a code block. + * @return {string} The given content with all &amp; HTML entities + * converted into &. + */ +function unescapeAmpersands( content ) { + return content.replace( /&amp;/g, '&' ); +} + +/** + * Returns the given content with all opening shortcode characters converted + * into their HTML entity counterpart (i.e. [ => &#91;). For instance, a + * shortcode like [embed] becomes &#91;embed] + * + * This function replicates the escaping of HTML tags, where a tag like + * <strong> becomes &lt;strong>. + * + * @param {string} content The content of a code block. + * @return {string} The given content with its opening shortcode characters + * converted into their HTML entity counterpart + * (i.e. [ => &#91;) + */ +function escapeOpeningSquareBrackets( content ) { + return content.replace( /\[/g, '&#91;' ); +} + +/** + * Returns the given content translating all &#91; into [. + * + * @param {string} content The content of a code block. + * @return {string} The given content with all &#91; into [. + */ +function unescapeOpeningSquareBrackets( content ) { + return content.replace( /&#91;/g, '[' ); +} + +/** + * Converts the first two forward slashes of any isolated URL into their HTML + * counterparts (i.e. // => &#47;&#47;). For instance, https://youtube.com/watch?x + * becomes https:&#47;&#47;youtube.com/watch?x. + * + * An isolated URL is a URL that sits in its own line, surrounded only by spacing + * characters. + * + * See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403 + * + * @param {string} content The content of a code block. + * @return {string} The given content with its ampersands converted into + * their HTML entity counterpart (i.e. & => &amp;) + */ +function escapeProtocolInIsolatedUrls( content ) { + return content.replace( /^(\s*https?:)\/\/([^\s<>"]+\s*)$/m, '$1&#47;&#47;$2' ); +} + +/** + * Converts the first two forward slashes of any isolated URL from the HTML entity + * &#73; into /. + * + * An isolated URL is a URL that sits in its own line, surrounded only by spacing + * characters. + * + * See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403 + * + * @param {string} content The content of a code block. + * @return {string} The given content with the first two forward slashes of any + * isolated URL from the HTML entity &#73; into /. + */ +function unescapeProtocolInIsolatedUrls( content ) { + return content.replace( /^(\s*https?:)&#47;&#47;([^\s<>"]+\s*)$/m, '$1//$2' ); +} From 77b8234222f6c900d76e8087f863fe276c5a638e Mon Sep 17 00:00:00 2001 From: Nicky Lim <nickylimjj.nl@gmail.com> Date: Thu, 2 May 2019 12:26:59 -0400 Subject: [PATCH 012/664] Clarified best practices pertaining to Color Palette values (#15006) * Clarified best practices pertaining to Color Palette values * Update docs/designers-developers/developers/themes/theme-support.md @aduth's review Co-Authored-By: nickylimjj <nickylimjj.nl@gmail.com> * Include explanation for dynamic values --- docs/designers-developers/developers/themes/theme-support.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index 636f05bb8b6443..ad1c76bb9a039d 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -114,6 +114,10 @@ add_theme_support( 'editor-color-palette', array( ) ); ``` +`name` is a human-readable label (demonstrated above) that appears in the tooltip and provides a meaningful description of the color to users. It is especially important for those who rely on screen readers or would otherwise have difficulty perceiving the color. `slug` is a unique identifier for the color and is used to generate the CSS classes used by the Gutenberg color palette. `color` is the hexadecimal code to specify the color. + +Some colors change dynamically — such as "Primary" and "Secondary" color — such as in the Twenty Nineteen theme and cannot be described programmatically. In spite of that, it is still advisable to provide meaningful `name`s for at least the default values when applicable. + The colors will be shown in order on the palette, and there's no limit to how many can be specified. Themes are responsible for creating the classes that apply the colors in different contexts. Core blocks use "color" and "background-color" contexts. So to correctly apply "strong magenta" to all contexts of core blocks a theme should implement the following classes: From 920b8f03debfef626587ddb5c758877bc44eb11c Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Thu, 2 May 2019 12:57:57 -0400 Subject: [PATCH 013/664] Refactor: move `core/edit-post` INIT effect to use action-generators and controls (#14740) * move constants into constants file * move init effect into action-generators and controls * add tests for new functionality * update docs build * update changelog * clarify controls/actions that are experimental/unstable This leaves room for future refactors that could eliminate these. * fix reference for version in changelog * move dispatch of initialize action to initializeEditor execution * add js docs for constants * doc change * Add flag for triggering initial invoking of listener props @aduth * eliminate ADJUST_SIDEBAR control and move logic into action * update tests * update docs --- packages/edit-post/CHANGELOG.md | 6 +- packages/edit-post/src/index.js | 1 + packages/edit-post/src/store/actions.js | 78 ++++++++ packages/edit-post/src/store/constants.js | 11 ++ packages/edit-post/src/store/controls.js | 48 +++++ packages/edit-post/src/store/effects.js | 75 +------- packages/edit-post/src/store/index.js | 6 +- packages/edit-post/src/store/index.native.js | 3 +- packages/edit-post/src/store/test/actions.js | 178 +++++++++++++++++++ packages/edit-post/src/store/utils.js | 7 +- 10 files changed, 335 insertions(+), 78 deletions(-) create mode 100644 packages/edit-post/src/store/constants.js create mode 100644 packages/edit-post/src/store/controls.js diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index c01997e5377e0b..0f2ad4fd0eb853 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,9 +1,13 @@ -## V.V.V (Unreleased) +## Master ### New Features - Implement the `addToGallery` option in the `MediaUpload` hook. The option allows users to open the media modal in the `gallery-library`instead of `gallery-edit` state. +### Refactor + +- convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740) + ## 3.2.0 (2019-03-06) diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index ecfac09f9347ee..cf8070c8c7b4e4 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -76,6 +76,7 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening <!DOCTYPE html>. Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); } + dispatch( 'core/edit-post' ).__unstableInitialize(); dispatch( 'core/nux' ).triggerGuide( [ 'core/editor.inserter', 'core/editor.settings', diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index c12eb674837eb4..e3538cccddc64f 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -3,6 +3,13 @@ */ import { castArray } from 'lodash'; +/** + * Internal dependencies + */ +import { __unstableSubscribe } from './controls'; +import { onChangeListener } from './utils'; +import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from './constants'; + /** * Returns an action object used in signalling that the user opened an editor sidebar. * @@ -232,3 +239,74 @@ export function metaBoxUpdatesSuccess() { }; } +/** + * Returns an action generator used to initialize some subscriptions for the + * post editor: + * + * - subscription for toggling the `edit-post/block` general sidebar when a + * block is selected. + * - subscription for hiding/showing the sidebar depending on size of viewport. + * - subscription for updating the "View Post" link in the admin bar when + * permalink is updated. + */ +export function* __unstableInitialize() { + // Select the block settings tab when the selected block changes + yield __unstableSubscribe( ( registry ) => onChangeListener( + () => !! registry.select( 'core/block-editor' ) + .getBlockSelectionStart(), + ( hasBlockSelection ) => { + if ( ! registry.select( 'core/edit-post' ).isEditorSidebarOpened() ) { + return; + } + if ( hasBlockSelection ) { + registry.dispatch( STORE_KEY ) + .openGeneralSidebar( 'edit-post/block' ); + } else { + registry.dispatch( STORE_KEY ) + .openGeneralSidebar( 'edit-post/document' ); + } + } + ) ); + // hide/show the sidebar depending on size of viewport. + yield __unstableSubscribe( ( registry ) => onChangeListener( + () => registry.select( 'core/viewport' ) + .isViewportMatch( '< medium' ), + ( () => { + let sidebarToReOpenOnExpand = null; + return ( isSmall ) => { + const { getActiveGeneralSidebarName } = registry.select( STORE_KEY ); + const { + closeGeneralSidebar: closeSidebar, + openGeneralSidebar: openSidebar, + } = registry.dispatch( STORE_KEY ); + if ( isSmall ) { + sidebarToReOpenOnExpand = getActiveGeneralSidebarName(); + if ( sidebarToReOpenOnExpand ) { + closeSidebar(); + } + } else if ( + sidebarToReOpenOnExpand && + ! getActiveGeneralSidebarName() + ) { + openSidebar( sidebarToReOpenOnExpand ); + } + }; + } )(), + true + ) ); + // Update View Post link in the admin bar when permalink is updated. + yield __unstableSubscribe( ( registry ) => onChangeListener( + () => registry.select( 'core/editor' ).getCurrentPost().link, + ( newPermalink ) => { + if ( ! newPermalink ) { + return; + } + const nodeToUpdate = document.querySelector( VIEW_AS_LINK_SELECTOR ); + if ( ! nodeToUpdate ) { + return; + } + nodeToUpdate.setAttribute( 'href', newPermalink ); + } + ) ); +} + diff --git a/packages/edit-post/src/store/constants.js b/packages/edit-post/src/store/constants.js new file mode 100644 index 00000000000000..60f80d914a5c7b --- /dev/null +++ b/packages/edit-post/src/store/constants.js @@ -0,0 +1,11 @@ +/** + * The identifier for the data store. + * @type {string} + */ +export const STORE_KEY = 'core/edit-post'; + +/** + * CSS selector string for the admin bar view post link anchor tag. + * @type {string} + */ +export const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; diff --git a/packages/edit-post/src/store/controls.js b/packages/edit-post/src/store/controls.js new file mode 100644 index 00000000000000..462741a1734075 --- /dev/null +++ b/packages/edit-post/src/store/controls.js @@ -0,0 +1,48 @@ +/** + * WordPress dependencies + */ +import { createRegistryControl } from '@wordpress/data'; + +/** + * Calls a selector using the current state. + * + * @param {string} storeName Store name. + * @param {string} selectorName Selector name. + * @param {Array} args Selector arguments. + * + * @return {Object} control descriptor. + */ +export function select( storeName, selectorName, ...args ) { + return { + type: 'SELECT', + storeName, + selectorName, + args, + }; +} + +/** + * Calls a subscriber using the current state. + * + * @param {function} listenerCallback A callback for the subscriber that + * receives the registry. + * @return {Object} control descriptor. + */ +export function __unstableSubscribe( listenerCallback ) { + return { type: 'SUBSCRIBE', listenerCallback }; +} + +const controls = { + SELECT: createRegistryControl( + ( registry ) => ( { storeName, selectorName, args } ) => { + return registry.select( storeName )[ selectorName ]( ...args ); + } + ), + SUBSCRIBE: createRegistryControl( + ( registry ) => ( { listenerCallback } ) => { + return registry.subscribe( listenerCallback( registry ) ); + } + ), +}; + +export default controls; diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index b273d0a4b3e350..6c64a173130975 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -14,20 +14,9 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { - metaBoxUpdatesSuccess, - requestMetaBoxUpdates, - openGeneralSidebar, - closeGeneralSidebar, -} from './actions'; -import { - getActiveMetaBoxLocations, - getActiveGeneralSidebarName, -} from './selectors'; +import { metaBoxUpdatesSuccess, requestMetaBoxUpdates } from './actions'; +import { getActiveMetaBoxLocations } from './selectors'; import { getMetaBoxContainer } from '../utils/meta-boxes'; -import { onChangeListener } from './utils'; - -const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; const effects = { SET_META_BOXES_PER_LOCATIONS( action, store ) { @@ -126,66 +115,6 @@ const effects = { const message = action.mode === 'visual' ? __( 'Visual editor selected' ) : __( 'Code editor selected' ); speak( message, 'assertive' ); }, - INIT( _, store ) { - // Select the block settings tab when the selected block changes - subscribe( onChangeListener( - () => !! select( 'core/block-editor' ).getBlockSelectionStart(), - ( hasBlockSelection ) => { - if ( ! select( 'core/edit-post' ).isEditorSidebarOpened() ) { - return; - } - if ( hasBlockSelection ) { - store.dispatch( openGeneralSidebar( 'edit-post/block' ) ); - } else { - store.dispatch( openGeneralSidebar( 'edit-post/document' ) ); - } - } ) - ); - - const isMobileViewPort = () => select( 'core/viewport' ).isViewportMatch( '< medium' ); - const adjustSidebar = ( () => { - // contains the sidebar we close when going to viewport sizes lower than medium. - // This allows to reopen it when going again to viewport sizes greater than medium. - let sidebarToReOpenOnExpand = null; - return ( isSmall ) => { - if ( isSmall ) { - sidebarToReOpenOnExpand = getActiveGeneralSidebarName( store.getState() ); - if ( sidebarToReOpenOnExpand ) { - store.dispatch( closeGeneralSidebar() ); - } - } else if ( sidebarToReOpenOnExpand && ! getActiveGeneralSidebarName( store.getState() ) ) { - store.dispatch( openGeneralSidebar( sidebarToReOpenOnExpand ) ); - } - }; - } )(); - - adjustSidebar( isMobileViewPort() ); - - // Collapse sidebar when viewport shrinks. - // Reopen sidebar it if viewport expands and it was closed because of a previous shrink. - subscribe( onChangeListener( isMobileViewPort, adjustSidebar ) ); - - // Update View as link when currentPost link changes - const updateViewAsLink = ( newPermalink ) => { - if ( ! newPermalink ) { - return; - } - - const nodeToUpdate = document.querySelector( - VIEW_AS_LINK_SELECTOR - ); - if ( ! nodeToUpdate ) { - return; - } - nodeToUpdate.setAttribute( 'href', newPermalink ); - }; - - subscribe( onChangeListener( - () => select( 'core/editor' ).getCurrentPost().link, - updateViewAsLink - ) ); - }, - }; export default effects; diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 9294d7cd90ac6b..4560fd12d46bbb 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -10,15 +10,17 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as actions from './actions'; import * as selectors from './selectors'; +import controls from './controls'; +import { STORE_KEY } from './constants'; -const store = registerStore( 'core/edit-post', { +const store = registerStore( STORE_KEY, { reducer, actions, selectors, + controls, persist: [ 'preferences' ], } ); applyMiddlewares( store ); -store.dispatch( { type: 'INIT' } ); export default store; diff --git a/packages/edit-post/src/store/index.native.js b/packages/edit-post/src/store/index.native.js index 611a2b4eaae559..10355071bd454a 100644 --- a/packages/edit-post/src/store/index.native.js +++ b/packages/edit-post/src/store/index.native.js @@ -10,8 +10,9 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as actions from './actions'; import * as selectors from './selectors'; +import { STORE_KEY } from './constants'; -const store = registerStore( 'core/edit-post', { +const store = registerStore( STORE_KEY, { reducer, actions, selectors, diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 07dc4b81ece682..a16e58f9cc18d8 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -15,7 +15,9 @@ import { toggleFeature, togglePinnedPluginItem, requestMetaBoxUpdates, + __unstableInitialize, } from '../actions'; +import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../constants'; describe( 'actions', () => { describe( 'openGeneralSidebar', () => { @@ -133,4 +135,180 @@ describe( 'actions', () => { } ); } ); } ); + + describe( '__unstableInitialize', () => { + let fulfillment; + const reset = () => fulfillment = __unstableInitialize(); + const registryMock = { select: {}, dispatch: {} }; + describe( 'yields subscribe control descriptor for block settings', () => { + reset(); + const { value } = fulfillment.next(); + const listenerCallback = value.listenerCallback; + const isEditorSidebarOpened = jest.fn(); + const getBlockSelectionStart = jest.fn(); + const openSidebar = jest.fn(); + beforeEach( () => { + registryMock.select = ( store ) => { + const stores = { + 'core/block-editor': { getBlockSelectionStart }, + 'core/edit-post': { isEditorSidebarOpened }, + }; + return stores[ store ]; + }; + registryMock.dispatch = () => ( { openGeneralSidebar: openSidebar } ); + } ); + afterEach( () => { + isEditorSidebarOpened.mockClear(); + getBlockSelectionStart.mockClear(); + openSidebar.mockClear(); + } ); + it( 'returns subscribe control descriptor', () => { + expect( value.type ).toBe( 'SUBSCRIBE' ); + } ); + it( 'does nothing if sidebar is not opened', () => { + getBlockSelectionStart.mockReturnValue( true ); + isEditorSidebarOpened.mockReturnValue( false ); + const listener = listenerCallback( registryMock ); + getBlockSelectionStart.mockReturnValue( false ); + listener(); + expect( getBlockSelectionStart ).toHaveBeenCalled(); + expect( isEditorSidebarOpened ).toHaveBeenCalled(); + expect( openSidebar ).not.toHaveBeenCalled(); + } ); + it( 'opens block sidebar if block is selected', () => { + isEditorSidebarOpened.mockReturnValue( true ); + getBlockSelectionStart.mockReturnValue( false ); + const listener = listenerCallback( registryMock ); + getBlockSelectionStart.mockReturnValue( true ); + listener(); + expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/block' ); + } ); + it( 'opens document sidebar if block is not selected', () => { + isEditorSidebarOpened.mockReturnValue( true ); + getBlockSelectionStart.mockReturnValue( true ); + const listener = listenerCallback( registryMock ); + getBlockSelectionStart.mockReturnValue( false ); + listener(); + expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/document' ); + } ); + } ); + describe( 'yields subscribe control descriptor for adjusting the ' + + 'sidebar', () => { + reset(); + fulfillment.next(); + const { value } = fulfillment.next(); + const listenerCallback = value.listenerCallback; + const isViewportMatch = jest.fn(); + const getActiveGeneralSidebarName = jest.fn(); + const willCloseGeneralSidebar = jest.fn(); + const willOpenGeneralSidebar = jest.fn(); + beforeEach( () => { + registryMock.select = ( store ) => { + const stores = { + 'core/viewport': { isViewportMatch }, + [ STORE_KEY ]: { getActiveGeneralSidebarName }, + }; + return stores[ store ]; + }; + registryMock.dispatch = ( store ) => { + const stores = { + [ STORE_KEY ]: { + closeGeneralSidebar: willCloseGeneralSidebar, + openGeneralSidebar: willOpenGeneralSidebar, + }, + }; + return stores[ store ]; + }; + registryMock.subscribe = jest.fn(); + isViewportMatch.mockReturnValue( true ); + } ); + afterEach( () => { + isViewportMatch.mockClear(); + getActiveGeneralSidebarName.mockClear(); + willCloseGeneralSidebar.mockClear(); + willOpenGeneralSidebar.mockClear(); + } ); + it( 'returns subscribe control descriptor', () => { + expect( value.type ).toBe( 'SUBSCRIBE' ); + } ); + it( 'initializes and does nothing when viewport is not small', () => { + isViewportMatch.mockReturnValue( false ); + listenerCallback( registryMock )(); + expect( isViewportMatch ).toHaveBeenCalled(); + expect( getActiveGeneralSidebarName ).not.toHaveBeenCalled(); + } ); + it( 'does not close sidebar if viewport is small and there is no ' + + 'active sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( false ); + listenerCallback( registryMock )(); + expect( willCloseGeneralSidebar ).not.toHaveBeenCalled(); + expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); + } ); + it( 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); + listenerCallback( registryMock )(); + expect( willCloseGeneralSidebar ).toHaveBeenCalled(); + expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); + } ); + it( 'opens sidebar if viewport is not small, there is a cached sidebar to ' + + 'reopen on expand, and there is no current sidebar name available', () => { + getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); + const listener = listenerCallback( registryMock ); + listener(); + isViewportMatch.mockReturnValue( false ); + getActiveGeneralSidebarName.mockReturnValue( false ); + listener(); + expect( willCloseGeneralSidebar ).toHaveBeenCalledTimes( 1 ); + expect( willOpenGeneralSidebar ).toHaveBeenCalledTimes( 1 ); + } ); + } ); + describe( 'yields subscribe control descriptor for updating the ' + + 'view post link when the permalink changes', () => { + reset(); + fulfillment.next(); + fulfillment.next(); + const { value } = fulfillment.next(); + const listenerCallback = value.listenerCallback; + const getCurrentPost = jest.fn(); + const setAttribute = jest.fn(); + beforeEach( () => { + document.querySelector = jest.fn().mockReturnValue( { setAttribute } ); + getCurrentPost.mockReturnValue( { link: 'foo' } ); + registryMock.select = ( store ) => { + const stores = { 'core/editor': { getCurrentPost } }; + return stores[ store ]; + }; + } ); + afterEach( () => { + setAttribute.mockClear(); + getCurrentPost.mockClear(); + } ); + it( 'returns expected control descriptor', () => { + expect( value.type ).toBe( 'SUBSCRIBE' ); + } ); + it( 'updates nothing if there is no new permalink', () => { + const listener = listenerCallback( registryMock ); + listener(); + expect( getCurrentPost ).toHaveBeenCalledTimes( 2 ); + expect( document.querySelector ).not.toHaveBeenCalled(); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'does not do anything if the node is not found', () => { + const listener = listenerCallback( registryMock ); + getCurrentPost.mockReturnValue( { link: 'bar' } ); + document.querySelector.mockReturnValue( false ); + listener(); + expect( document.querySelector ) + .toHaveBeenCalledWith( VIEW_AS_LINK_SELECTOR ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'updates with the new permalink when node is found', () => { + const listener = listenerCallback( registryMock ); + getCurrentPost.mockReturnValue( { link: 'bar' } ); + listener(); + expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); + } ); + } ); + } ); } ); diff --git a/packages/edit-post/src/store/utils.js b/packages/edit-post/src/store/utils.js index 86b043327e291d..7dfd1ea08ed892 100644 --- a/packages/edit-post/src/store/utils.js +++ b/packages/edit-post/src/store/utils.js @@ -4,10 +4,15 @@ * * @param {function} selector Selector. * @param {function} listener Listener. + * @param {boolean} initial Flags whether listener should be invoked on + * initial call. * @return {function} Listener creator. */ -export const onChangeListener = ( selector, listener ) => { +export const onChangeListener = ( selector, listener, initial = false ) => { let previousValue = selector(); + if ( initial ) { + listener( selector() ); + } return () => { const selectedValue = selector(); if ( selectedValue !== previousValue ) { From 23043f84e3ab13f8d40cf986fc3982284146df04 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 2 May 2019 14:22:13 -0400 Subject: [PATCH 014/664] docgen: Remove unneccessary argument from Array#pop (#15397) --- packages/docgen/CHANGELOG.md | 6 ++++++ packages/docgen/src/index.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 9c2240b06686fc..ebd35131a443d0 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Internal + +- Remove unneccessary argument from an instance of `Array#pop`. + ## 1.0.0 (2019-03-06) - Initial release diff --git a/packages/docgen/src/index.js b/packages/docgen/src/index.js index 0fef8a2563a597..8b03dfe9bfafcd 100644 --- a/packages/docgen/src/index.js +++ b/packages/docgen/src/index.js @@ -51,7 +51,7 @@ const processFile = ( rootDir, inputFile ) => { currentFileStack.push( inputFile ); const relativePath = path.relative( rootDir, inputFile ); const result = engine( relativePath, data, getIRFromRelativePath( rootDir, last( currentFileStack ) ) ); - currentFileStack.pop( inputFile ); + currentFileStack.pop(); return result; } catch ( e ) { process.stderr.write( `\n${ e }` ); From e24615769524b560813df8899b8e9acd5ff000ff Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Fri, 3 May 2019 10:10:38 +0800 Subject: [PATCH 015/664] Fix or improve eslint disable comments (#15384) --- .../src/components/block-list/block.js | 2 +- .../src/components/inserter/test/menu.js | 3 +-- .../src/components/url-input/test/button.js | 3 +-- .../src/components/writing-flow/index.js | 2 +- packages/block-library/src/classic/edit.js | 19 +++++++++----- .../block-library/src/embed/embed-preview.js | 6 ++--- .../components/src/external-link/index.js | 3 ++- .../higher-order/with-filters/test/index.js | 6 ++--- .../with-focus-outside/test/index.js | 3 +-- .../src/isolated-event-container/index.js | 1 + .../src/navigable-container/container.js | 1 + packages/components/src/popover/test/index.js | 4 +-- packages/components/src/tooltip/test/index.js | 3 +-- .../format-library/src/link/modal.native.js | 26 +++++++++---------- 14 files changed, 40 insertions(+), 42 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 84dfd7491920d6..e76a0f8e207217 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -602,7 +602,7 @@ export class BlockListBlock extends Component { ) } </IgnoreNestedEvents> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } } </HoverArea> ); diff --git a/packages/block-editor/src/components/inserter/test/menu.js b/packages/block-editor/src/components/inserter/test/menu.js index 421b76d6b3593f..ef9d2b10214a98 100644 --- a/packages/block-editor/src/components/inserter/test/menu.js +++ b/packages/block-editor/src/components/inserter/test/menu.js @@ -107,9 +107,8 @@ const getWrapperForProps = ( propOverrides ) => { const initializeMenuDefaultStateAndReturnElement = ( propOverrides ) => { const wrapper = getWrapperForProps( propOverrides ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node return ReactDOM.findDOMNode( wrapper ); - /* eslint-enable react/no-find-dom-node */ }; const initializeAllClosedMenuStateAndReturnElement = ( propOverrides ) => { diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js index 5bf29d48c8f7ff..427ea0ca8531e8 100644 --- a/packages/block-editor/src/components/url-input/test/button.js +++ b/packages/block-editor/src/components/url-input/test/button.js @@ -75,8 +75,7 @@ describe( 'URLInputButton', () => { expect( wrapper.state.expanded ).toBe( true ); TestUtils.Simulate.submit( formElement() ); expect( wrapper.state.expanded ).toBe( false ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - /* eslint-enable react/no-find-dom-node */ } ); } ); diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 951df53592ae33..1acb85b1695584 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -357,7 +357,7 @@ class WritingFlow extends Component { /> </div> ); - /* eslint-disable jsx-a11y/no-static-element-interactions */ + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index 49c1ec2340e506..5b30ecb7f90692 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -178,13 +178,18 @@ export default class ClassicEdit extends Component { render() { const { clientId } = this.props; - // Disable reason: the toolbar itself is non-interactive, but must capture - // events from the KeyboardShortcuts component to stop their propagation. - /* eslint-disable jsx-a11y/no-static-element-interactions */ + // Disable reasons: + // + // jsx-a11y/no-static-element-interactions + // - the toolbar itself is non-interactive, but must capture events + // from the KeyboardShortcuts component to stop their propagation. + // + // jsx-a11y/no-static-element-interactions + // - Clicking on this visual placeholder should create the + // toolbar, it can also be created by focussing the field below. + + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return [ - // Disable reason: Clicking on this visual placeholder should create - // the toolbar, it can also be created by focussing the field below. - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ <div key="toolbar" id={ `toolbar-${ clientId }` } @@ -200,6 +205,6 @@ export default class ClassicEdit extends Component { className="wp-block-freeform block-library-rich-text__tinymce" />, ]; - /* eslint-enable jsx-a11y/no-static-element-interactions */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index eeda8d33f91153..e519ca6aaa2da8 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -69,8 +69,7 @@ class EmbedPreview extends Component { // Disabled because the overlay div doesn't actually have a role or functionality // as far as the user is concerned. We're just catching the first click so that // the block can be selected without interacting with the embed preview that the overlay covers. - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - /* eslint-disable jsx-a11y/no-static-element-interactions */ + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */ const embedWrapper = 'wp-embed' === type ? ( <WpEmbedPreview html={ html } @@ -89,8 +88,7 @@ class EmbedPreview extends Component { onMouseUp={ this.hideOverlay } /> } </div> ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ + /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */ return ( <figure className={ classnames( className, 'wp-block-embed', { 'is-type-video': 'video' === type } ) }> diff --git a/packages/components/src/external-link/index.js b/packages/components/src/external-link/index.js index 6b57f3978fd5f6..68bf56394b8f86 100644 --- a/packages/components/src/external-link/index.js +++ b/packages/components/src/external-link/index.js @@ -24,7 +24,8 @@ export function ExternalLink( { href, children, className, rel = '', ...addition ] ) ).join( ' ' ); const classes = classnames( 'components-external-link', className ); return ( - <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> { /* eslint-disable-line react/jsx-no-target-blank */ } + // eslint-disable-next-line react/jsx-no-target-blank + <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> { children } <span className="screen-reader-text"> { diff --git a/packages/components/src/higher-order/with-filters/test/index.js b/packages/components/src/higher-order/with-filters/test/index.js index 8b41886c11621e..68aecd9685e80b 100644 --- a/packages/components/src/higher-order/with-filters/test/index.js +++ b/packages/components/src/higher-order/with-filters/test/index.js @@ -17,9 +17,8 @@ import { Component } from '@wordpress/element'; import withFilters from '..'; const assertExpectedHtml = ( wrapper, expectedHTML ) => { - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node const element = ReactDOM.findDOMNode( wrapper ); - /* eslint-enable react/no-find-dom-node */ expect( element.outerHTML ).toBe( expectedHTML ); }; @@ -42,13 +41,12 @@ describe( 'withFilters', () => { afterEach( () => { if ( wrapper ) { - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); } if ( shallowWrapper ) { shallowWrapper.unmount(); } - /* eslint-enable react/no-find-dom-node */ removeAllFilters( hookName ); } ); diff --git a/packages/components/src/higher-order/with-focus-outside/test/index.js b/packages/components/src/higher-order/with-focus-outside/test/index.js index 0534d39999e2b1..dd39f910981c7e 100644 --- a/packages/components/src/higher-order/with-focus-outside/test/index.js +++ b/packages/components/src/higher-order/with-focus-outside/test/index.js @@ -95,9 +95,8 @@ describe( 'withFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'input' ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - /* eslint-enable react/no-find-dom-node */ jest.runAllTimers(); diff --git a/packages/components/src/isolated-event-container/index.js b/packages/components/src/isolated-event-container/index.js index 282e6dc5c5b7af..1f305071c95cf7 100644 --- a/packages/components/src/isolated-event-container/index.js +++ b/packages/components/src/isolated-event-container/index.js @@ -28,6 +28,7 @@ class IsolatedEventContainer extends Component { { children } </div> ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/components/src/navigable-container/container.js b/packages/components/src/navigable-container/container.js index fe056fa25ddbf8..244d646de026fc 100644 --- a/packages/components/src/navigable-container/container.js +++ b/packages/components/src/navigable-container/container.js @@ -120,6 +120,7 @@ class NavigableContainer extends Component { { children } </div> ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/components/src/popover/test/index.js b/packages/components/src/popover/test/index.js index a66e68023f0ae0..0ba92b4337c924 100644 --- a/packages/components/src/popover/test/index.js +++ b/packages/components/src/popover/test/index.js @@ -38,10 +38,8 @@ describe( 'Popover', () => { it( 'should turn off auto refresh', () => { wrapper = TestUtils.renderIntoDocument( <Popover /> ); - /* eslint-disable react/no-find-dom-node */ + // eslint-disable-next-line react/no-find-dom-node ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - /* eslint-enable react/no-find-dom-node */ - expect( Popover.prototype.toggleAutoRefresh ).toHaveBeenCalledWith( false ); } ); diff --git a/packages/components/src/tooltip/test/index.js b/packages/components/src/tooltip/test/index.js index c84749f0b73173..bc98cdf1f024b0 100644 --- a/packages/components/src/tooltip/test/index.js +++ b/packages/components/src/tooltip/test/index.js @@ -90,10 +90,9 @@ describe( 'Tooltip', () => { </Tooltip> ); - /* eslint-disable react/no-find-dom-node */ const button = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); + // eslint-disable-next-line react/no-find-dom-node TestUtils.Simulate.mouseEnter( ReactDOM.findDOMNode( button ) ); - /* eslint-enable react/no-find-dom-node */ expect( originalMouseEnter ).toHaveBeenCalledTimes( 1 ); expect( wrapper.state.isOver ).toBe( false ); diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index 57bc01b9f28ef2..b11929c821b8bb 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -130,19 +130,19 @@ class ModalLinkUI extends Component { onClose={ this.onDismiss } hideHeader > - { /* eslint-disable jsx-a11y/no-autofocus */ - <BottomSheet.Cell - icon={ 'admin-links' } - label={ __( 'URL' ) } - value={ this.state.inputValue } - placeholder={ __( 'Add URL' ) } - autoCapitalize="none" - autoCorrect={ false } - keyboardType="url" - onChangeValue={ this.onChangeInputValue } - autoFocus={ Platform.OS === 'ios' } - /> - /* eslint-enable jsx-a11y/no-autofocus */ } + { /* eslint-disable jsx-a11y/no-autofocus */ } + <BottomSheet.Cell + icon={ 'admin-links' } + label={ __( 'URL' ) } + value={ this.state.inputValue } + placeholder={ __( 'Add URL' ) } + autoCapitalize="none" + autoCorrect={ false } + keyboardType="url" + onChangeValue={ this.onChangeInputValue } + autoFocus={ Platform.OS === 'ios' } + /> + { /* eslint-enable jsx-a11y/no-autofocus */ } <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Link Text' ) } From 5c48dc17d8bc4ba94f03210d8120e73eadcc516a Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Fri, 3 May 2019 10:20:14 +0800 Subject: [PATCH 016/664] Try using createElement for renderAppender render prop (#15259) * Use createElement for renderAppender render prop * Use JSX instead of createElement --- .../src/components/block-list-appender/index.js | 6 +++--- packages/block-library/src/group/edit.js | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index 215fe23bae5257..35bf09bb89e669 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -21,17 +21,17 @@ function BlockListAppender( { rootClientId, canInsertDefaultBlock, isLocked, - renderAppender, + renderAppender: CustomAppender, } ) { if ( isLocked ) { return null; } // A render prop has been provided, use it to render the appender. - if ( renderAppender ) { + if ( CustomAppender ) { return ( <div className="block-list-appender"> - { renderAppender() } + <CustomAppender /> </div> ); } diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 4b06ec7945ccc8..f391a277a46bbb 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -17,8 +17,6 @@ import { withColors, } from '@wordpress/block-editor'; -const renderAppender = () => <InnerBlocks.ButtonBlockAppender />; - function GroupEdit( { className, setBackgroundColor, @@ -49,7 +47,7 @@ function GroupEdit( { </InspectorControls> <div className={ classes } style={ styles }> <InnerBlocks - renderAppender={ ! hasInnerBlocks && renderAppender } + renderAppender={ ! hasInnerBlocks && InnerBlocks.ButtonBlockAppender } /> </div> </Fragment> From 056b595a5c9bdd0dbd699c1fca1c04de89762d5e Mon Sep 17 00:00:00 2001 From: Weston Ruter <westonruter@google.com> Date: Fri, 3 May 2019 05:52:39 -0700 Subject: [PATCH 017/664] FocalPointPicker: Supply default value as initial percentages state to fix NaN warning (#15400) * Supply default value as initial percentages state to fix NaN warning * Eliminate needless componentDidMount; set value state in constructor from props --- packages/components/src/focal-point-picker/index.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/components/src/focal-point-picker/index.js b/packages/components/src/focal-point-picker/index.js index 85f8f374556550..d502ebbded0e03 100644 --- a/packages/components/src/focal-point-picker/index.js +++ b/packages/components/src/focal-point-picker/index.js @@ -21,13 +21,13 @@ const TEXTCONTROL_MIN = 0; const TEXTCONTROL_MAX = 100; export class FocalPointPicker extends Component { - constructor() { - super( ...arguments ); + constructor( props ) { + super( props ); this.onMouseMove = this.onMouseMove.bind( this ); this.state = { isDragging: false, bounds: {}, - percentages: {}, + percentages: props.value, }; this.containerRef = createRef(); this.imageRef = createRef(); @@ -35,11 +35,6 @@ export class FocalPointPicker extends Component { this.verticalPositionChanged = this.verticalPositionChanged.bind( this ); this.onLoad = this.onLoad.bind( this ); } - componentDidMount() { - this.setState( { - percentages: this.props.value, - } ); - } componentDidUpdate( prevProps ) { if ( prevProps.url !== this.props.url ) { this.setState( { From 3335c0b282ed64321f4b2c2ace00cb23692705d3 Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <nicolai.vadim@gmail.com> Date: Fri, 3 May 2019 18:30:52 +0300 Subject: [PATCH 018/664] Re-exported React.memo in packages/element/src/react.js (#15385) * Refactored concatChildren internal memo. * Renamed reduce accumulator. --- packages/element/README.md | 6 ++++++ packages/element/src/react.js | 12 +++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/element/README.md b/packages/element/README.md index c3d916c5587365..052ea244540560 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -219,6 +219,12 @@ _Related_ - <https://reactjs.org/docs/react-api.html#reactlazy> +<a name="memo" href="#memo">#</a> **memo** + +_Related_ + +- <https://reactjs.org/docs/react-api.html#reactmemo> + <a name="RawHTML" href="#RawHTML">#</a> **RawHTML** Component used as equivalent of Fragment with unescaped HTML, in cases where diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 275e77276e229e..561776257f388e 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -11,6 +11,7 @@ import { forwardRef, Fragment, isValidElement, + memo, StrictMode, useState, useEffect, @@ -106,6 +107,11 @@ export { Fragment }; */ export { isValidElement }; +/** + * @see https://reactjs.org/docs/react-api.html#reactmemo + */ +export { memo }; + /** * Component that activates additional checks and warnings for its descendants. */ @@ -179,7 +185,7 @@ export { Suspense }; * @return {Array} The concatenated value. */ export function concatChildren( ...childrenArguments ) { - return childrenArguments.reduce( ( memo, children, i ) => { + return childrenArguments.reduce( ( result, children, i ) => { Children.forEach( children, ( child, j ) => { if ( child && 'string' !== typeof child ) { child = cloneElement( child, { @@ -187,10 +193,10 @@ export function concatChildren( ...childrenArguments ) { } ); } - memo.push( child ); + result.push( child ); } ); - return memo; + return result; }, [] ); } From 42eb03c6d12a64ffc11267e452f422fb88052ba7 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <asmussen@gmail.com> Date: Fri, 3 May 2019 17:45:51 +0200 Subject: [PATCH 019/664] Try tweaking the specificity of the custom color syntax (#15167) * Try tweaking the specificity of the custom color syntax This fixes #12986 and is based on the conversation there. Instead of doubling the selectors to increase specificity, we are supplying the element as well. * Try :root * Address feedback. --- packages/block-library/src/style.scss | 144 ++++++++++++++------------ 1 file changed, 77 insertions(+), 67 deletions(-) diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 83588d4f223dd2..73325d5d9167c1 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -25,97 +25,107 @@ @import "./verse/style.scss"; @import "./video/style.scss"; -// Class names are doubled to increase specificity to assure colors take effect -// over another base class color. +// The following selectors have increased specificity (using the :root prefix) +// to assure colors take effect over another base class color, mainly to let +// the colors override the added specificity by link states such as :hover. -.has-pale-pink-background-color.has-pale-pink-background-color { - background-color: #f78da7; -} +:root { + // Background colors. -.has-vivid-red-background-color.has-vivid-red-background-color { - background-color: #cf2e2e; -} + .has-pale-pink-background-color { + background-color: #f78da7; + } -.has-luminous-vivid-orange-background-color.has-luminous-vivid-orange-background-color { - background-color: #ff6900; -} + .has-vivid-red-background-color { + background-color: #cf2e2e; + } -.has-luminous-vivid-amber-background-color.has-luminous-vivid-amber-background-color { - background-color: #fcb900; -} + .has-luminous-vivid-orange-background-color { + background-color: #ff6900; + } -.has-light-green-cyan-background-color.has-light-green-cyan-background-color { - background-color: #7bdcb5; -} + .has-luminous-vivid-amber-background-color { + background-color: #fcb900; + } -.has-vivid-green-cyan-background-color.has-vivid-green-cyan-background-color { - background-color: #00d084; -} + .has-light-green-cyan-background-color { + background-color: #7bdcb5; + } -.has-pale-cyan-blue-background-color.has-pale-cyan-blue-background-color { - background-color: #8ed1fc; -} + .has-vivid-green-cyan-background-color { + background-color: #00d084; + } -.has-vivid-cyan-blue-background-color.has-vivid-cyan-blue-background-color { - background-color: #0693e3; -} + .has-pale-cyan-blue-background-color { + background-color: #8ed1fc; + } -.has-very-light-gray-background-color.has-very-light-gray-background-color { - background-color: #eee; -} + .has-vivid-cyan-blue-background-color { + background-color: #0693e3; + } -.has-cyan-bluish-gray-background-color.has-cyan-bluish-gray-background-color { - background-color: #abb8c3; -} + .has-very-light-gray-background-color { + background-color: #eee; + } -.has-very-dark-gray-background-color.has-very-dark-gray-background-color { - background-color: #313131; -} + .has-cyan-bluish-gray-background-color { + background-color: #abb8c3; + } -.has-pale-pink-color.has-pale-pink-color { - color: #f78da7; -} + .has-very-dark-gray-background-color { + background-color: #313131; + } -.has-vivid-red-color.has-vivid-red-color { - color: #cf2e2e; -} + // Foreground colors. -.has-luminous-vivid-orange-color.has-luminous-vivid-orange-color { - color: #ff6900; -} + .has-pale-pink-color { + color: #f78da7; + } -.has-luminous-vivid-amber-color.has-luminous-vivid-amber-color { - color: #fcb900; -} + .has-vivid-red-color { + color: #cf2e2e; + } -.has-light-green-cyan-color.has-light-green-cyan-color { - color: #7bdcb5; -} + .has-luminous-vivid-orange-color { + color: #ff6900; + } -.has-vivid-green-cyan-color.has-vivid-green-cyan-color { - color: #00d084; -} + .has-luminous-vivid-amber-color { + color: #fcb900; + } -.has-pale-cyan-blue-color.has-pale-cyan-blue-color { - color: #8ed1fc; -} + .has-light-green-cyan-color { + color: #7bdcb5; + } -.has-vivid-cyan-blue-color.has-vivid-cyan-blue-color { - color: #0693e3; -} + .has-vivid-green-cyan-color { + color: #00d084; + } -.has-very-light-gray-color.has-very-light-gray-color { - color: #eee; -} + .has-pale-cyan-blue-color { + color: #8ed1fc; + } -.has-cyan-bluish-gray-color.has-cyan-bluish-gray-color { - color: #abb8c3; -} + .has-vivid-cyan-blue-color { + color: #0693e3; + } + + .has-very-light-gray-color { + color: #eee; + } -.has-very-dark-gray-color.has-very-dark-gray-color { - color: #313131; + .has-cyan-bluish-gray-color { + color: #abb8c3; + } + + .has-very-dark-gray-color { + color: #313131; + } } + +// Font sizes. + .has-small-font-size { font-size: 13px; } From 0ad0414e7a469254ef1ad105fdcf14248a986686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 3 May 2019 17:53:01 +0200 Subject: [PATCH 020/664] Use Block Editor instead of Gutenberg when appropriatte in the Handbook (#15411) --- docs/contributors/coding-guidelines.md | 2 +- docs/contributors/design.md | 6 +++--- docs/contributors/document.md | 2 +- docs/contributors/history.md | 2 +- docs/contributors/localizing.md | 2 +- docs/contributors/principles.md | 2 +- .../designers/design-patterns.md | 4 ++-- .../designers/design-resources.md | 2 +- .../backward-compatibility/meta-box.md | 16 ++++++++-------- .../developers/block-api/block-annotations.md | 2 +- .../developers/block-api/block-deprecation.md | 2 +- .../developers/block-api/block-edit-save.md | 4 ++-- .../developers/block-api/block-registration.md | 6 +++--- .../developers/themes/README.md | 2 +- .../developers/themes/theme-support.md | 16 ++++++++-------- .../block-tutorial/creating-dynamic-blocks.md | 2 +- .../tutorials/javascript/js-build-setup.md | 2 +- .../developers/tutorials/javascript/readme.md | 2 +- .../javascript/versions-and-building.md | 4 ++-- docs/manifest.json | 2 +- 20 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index bb763af00018b8..ab3258f7f8b512 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -1,6 +1,6 @@ # Coding Guidelines -This living document serves to prescribe coding guidelines specific to the Gutenberg editor project. Base coding guidelines follow the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/). The following sections outline additional patterns and conventions used in the Gutenberg project. +This living document serves to prescribe coding guidelines specific to the Gutenberg project. Base coding guidelines follow the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/). The following sections outline additional patterns and conventions used in the Gutenberg project. ## CSS diff --git a/docs/contributors/design.md b/docs/contributors/design.md index bd68ba060718ed..d71221003d378a 100644 --- a/docs/contributors/design.md +++ b/docs/contributors/design.md @@ -8,7 +8,7 @@ This is a living document that outlines the design principles and patterns of th ### Goal of Gutenberg -Gutenberg's all-encompassing goal is a post- and page-building experience that makes it easy to create rich layouts. +Gutenberg's all-encompassing goal is a post- and page-building experience that makes it easy to create rich layouts. The block editor was the first product launched following this methodology for working with content. From the [kickoff post](https://make.wordpress.org/core/2017/01/04/focus-tech-and-design-leads/): @@ -18,13 +18,13 @@ We can extract a few key principles from this: - **Authoring rich posts is a key strength of WordPress.** - **Blocks will unify features and types of interaction under a single interface.** Users shouldn’t have to write shortcodes, custom HTML, or paste URLs to embed. Users only need to learn how the block works in order to use all of its features. -- **Gutenberg makes core features more discoverable**, reducing hard-to-find “Mystery meat.” WordPress supports a large number of blocks and 30+ embeds. Let’s increase their visibility. +- **Make core features more discoverable**, reducing hard-to-find “Mystery meat.” WordPress supports a large number of blocks and 30+ embeds. Let’s increase their visibility. ### Why One thing that sets WordPress apart from other systems is that it allows users to create as rich a post layout as they can imagine — as long as they know HTML and CSS and build a custom theme. -Gutenberg reshapes the editor into tool that allows users write rich posts and build beautiful layouts in a few clicks — no technical knowledge needed. WordPress will become a powerful and flexible content tool that’s accessible to all. +Gutenberg reshapes the editor into a tool that allows users write rich posts and build beautiful layouts in a few clicks — no technical knowledge needed. WordPress will become a powerful and flexible content tool that’s accessible to all. ### Vision diff --git a/docs/contributors/document.md b/docs/contributors/document.md index f51578d99e3c31..31f372bc865cfd 100644 --- a/docs/contributors/document.md +++ b/docs/contributors/document.md @@ -15,7 +15,7 @@ To add a new documentation page: It's very likely that at some point you will want to link to other documentation pages. It's worth emphasizing that all documents can be browsed in different contexts: -- Gutenberg Handbook +- Block Editor Handbook - GitHub website - npm website diff --git a/docs/contributors/history.md b/docs/contributors/history.md index b38aacc43ecce9..a0f05a9cb446a3 100644 --- a/docs/contributors/history.md +++ b/docs/contributors/history.md @@ -4,7 +4,7 @@ There was a survey done: [https://make.wordpress.org/core/2017/04/07/editor-experience-survey-results/](https://make.wordpress.org/core/2017/04/07/editor-experience-survey-results/) ## Inspiration -This includes a list of historical articles and influences on Gutenberg. +This includes a list of historical articles and influences on the Gutenberg project. - LivingDocs: [https://beta.livingdocs.io/articles](https://beta.livingdocs.io/articles) - Parrot: [https://intenseminimalism.com/2017/parrot-an-integrated-site-builder-and-editor-concept-for-wordpress/](https://intenseminimalism.com/2017/parrot-an-integrated-site-builder-and-editor-concept-for-wordpress/) diff --git a/docs/contributors/localizing.md b/docs/contributors/localizing.md index c5b22e675d51ef..66d8df9568296c 100644 --- a/docs/contributors/localizing.md +++ b/docs/contributors/localizing.md @@ -6,4 +6,4 @@ A Global Translation Editor (GTE) or Project Translation Editor (PTE) with suita Language packs are automatically generated once 95% of the plugin's strings are translated and approved for a locale. -The eventual inclusion of Gutenberg into WordPress core means that more than 51% of WordPress installations running a translated WordPress installation will have Gutenberg's translated strings compiled into the core language pack as well. +The inclusion of Gutenberg into WordPress core means that more than 51% of WordPress installations running a translated WordPress installation have Gutenberg's translated strings compiled into the core language pack as well. diff --git a/docs/contributors/principles.md b/docs/contributors/principles.md index c7a63be051bbc0..8acedac0958266 100644 --- a/docs/contributors/principles.md +++ b/docs/contributors/principles.md @@ -1,6 +1,6 @@ # Principles -First, let’s look at the big picture. If the architectural and UX principles described here are activated at scale, how will Gutenberg improve and transform both users and creators experiences? +First, let’s look at the big picture. If the architectural and UX principles described here are activated at scale, how will the Gutenberg project improve and transform both users and creators experiences? How Gutenberg can transform the *user experience*: diff --git a/docs/designers-developers/designers/design-patterns.md b/docs/designers-developers/designers/design-patterns.md index d6fa0fd30f1e69..bf06df6da5e9df 100644 --- a/docs/designers-developers/designers/design-patterns.md +++ b/docs/designers-developers/designers/design-patterns.md @@ -2,7 +2,7 @@ ## Basic Editor Interface -Gutenberg’s general layout uses on a bar at the top, with content below. +The block editor’s general layout uses on a bar at the top, with content below. ![Editor Interface](https://cldup.com/VWA_jMcIRw-3000x3000.png) @@ -22,7 +22,7 @@ A selected block shows a number of contextual actions: ![Block Interface](https://cldup.com/3tQqIncKPB-3000x3000.png) -The block interface has basic actions. Gutenberg aims for good, common defaults, so users should be able to create a complete document without actually needing the advanced actions in the Settings Sidebar. +The block interface has basic actions. The block editor aims for good, common defaults, so users should be able to create a complete document without actually needing the advanced actions in the Settings Sidebar. **The Block Toolbar** highlights commonly-used actions. The **Block Chip** lives in the block toolbar, and contains high-level controls for the selected block. It primarily allows users to transform a block into another type of compatible block. Some blocks also use the block chip to users to choose from a set of alternate block styles. diff --git a/docs/designers-developers/designers/design-resources.md b/docs/designers-developers/designers/design-resources.md index a6abc870dba11b..a6bad10ef02f9d 100644 --- a/docs/designers-developers/designers/design-resources.md +++ b/docs/designers-developers/designers/design-resources.md @@ -1,6 +1,6 @@ # Resources -The [SketchPress](https://github.com/10up/SketchPress) project includes a library of Gutenberg design components helpful for designing and prototyping Gutenberg and Gutenberg blocks: +The [SketchPress](https://github.com/10up/SketchPress) project includes a library of Gutenberg design components helpful for designing and prototyping: [Download Sketch mockups & patterns files](https://github.com/10up/SketchPress) diff --git a/docs/designers-developers/developers/backward-compatibility/meta-box.md b/docs/designers-developers/developers/backward-compatibility/meta-box.md index e7350dadba7fb3..dc8ce593eb8565 100644 --- a/docs/designers-developers/developers/backward-compatibility/meta-box.md +++ b/docs/designers-developers/developers/backward-compatibility/meta-box.md @@ -1,12 +1,12 @@ # Meta Boxes -This is a brief document detailing how meta box support works in Gutenberg. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. +This is a brief document detailing how meta box support works in the block editor. With the superior developer and user experience of blocks, especially once block templates are available, **porting PHP meta boxes to blocks is highly encouraged!** See the [Meta Block tutorial](/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md) for how to store post meta data using blocks. ### Testing, Converting, and Maintaining Existing Meta Boxes -Before converting meta boxes to blocks, it may be easier to test if a meta box works with Gutenberg, and explicitly mark it as such. +Before converting meta boxes to blocks, it may be easier to test if a meta box works with the block editor, and explicitly mark it as such. -If a meta box *doesn't* work with in Gutenberg, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: +If a meta box *doesn't* work with the block editor, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: ```php add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', @@ -30,11 +30,11 @@ add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', ); ``` -When Gutenberg is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the classic editor. +When the block editor is used, this meta box will no longer be displayed in the meta box area, as it now only exists for backward compatibility purposes. It will continue to display correctly in the classic editor. ### Meta Box Data Collection -On each Gutenberg page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. +On each block editor page load, we register an action that collects the meta box data to determine if an area is empty. The original global state is reset upon collection of meta box data. See [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/). @@ -48,7 +48,7 @@ Ideally, this could be done at instantiation of the editor and help simplify thi ### Redux and React Meta Box Management -When rendering the Gutenberg Page, the meta boxes are rendered to a hidden div `#metaboxes`. +When rendering the block editor, the meta boxes are rendered to a hidden div `#metaboxes`. *The Redux store will hold all meta boxes as inactive by default*. When `INITIALIZE_META_BOX_STATE` comes in, the store will update any active meta box areas by setting the `isActive` flag to `true`. Once this happens React will check for the new props sent in by Redux on the `MetaBox` component. If that `MetaBox` is now active, instead of rendering null, a `MetaBoxArea` component will be rendered. The `MetaBox` component is the container component that mediates between the `MetaBoxArea` and the Redux Store. *If no meta boxes are active, nothing happens. This will be the default behavior, as all core meta boxes have been stripped.* @@ -71,10 +71,10 @@ This page mimics the `post.php` post form, so when it is submitted it will fire ### Common Compatibility Issues -Most PHP meta boxes should continue to work in Gutenberg, but some meta boxes that include advanced functionality could break. Here are some common reasons why meta boxes might not work as expected in Gutenberg: +Most PHP meta boxes should continue to work in the block editor, but some meta boxes that include advanced functionality could break. Here are some common reasons why meta boxes might not work as expected in the block editor: - Plugins relying on selectors that target the post title, post content fields, and other metaboxes (of the old editor). -- Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in Gutenberg. +- Plugins relying on TinyMCE's API because there's no longer a single TinyMCE instance to talk to in the block editor. - Plugins making updates to their DOM on "submit" or on "save". Please also note that if your plugin triggers a PHP warning or notice to be output on the page, this will cause the HTML document type (`<!DOCTYPE html>`) to be output incorrectly. This will cause the browser to render using "Quirks Mode", which is a compatibility layer that gets enabled when the browser doesn't know what type of document it is parsing. The block editor is not meant to work in this mode, but it can _appear_ to be working just fine. If you encounter issues such as *meta boxes overlaying the editor* or other layout issues, please check the raw page source of your document to see that the document type definition is the first thing output on the page. There will also be a warning in the JavaScript console, noting the issue. diff --git a/docs/designers-developers/developers/block-api/block-annotations.md b/docs/designers-developers/developers/block-api/block-annotations.md index 70df5b61e5d187..85a523904fd495 100644 --- a/docs/designers-developers/developers/block-api/block-annotations.md +++ b/docs/designers-developers/developers/block-api/block-annotations.md @@ -2,7 +2,7 @@ **Note: This API is experimental, that means it is subject to non-backward compatible changes or removal in any future version.** -Annotations are a way to highlight a specific piece in a Gutenberg post. Examples of this include commenting on a piece of text and spellchecking. Both can use the annotations API to mark a piece of text. +Annotations are a way to highlight a specific piece in a post created with the block editor. Examples of this include commenting on a piece of text and spellchecking. Both can use the annotations API to mark a piece of text. ## API diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index 3e5533289cc230..795a5951b2fd04 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -3,7 +3,7 @@ When updating static blocks markup and attributes, block authors need to consider existing posts using the old versions of their block. In order to provide a good upgrade path, you can choose one of the following strategies: - Do not deprecate the block and create a new one (a different name) - - Provide a "deprecated" version of the block allowing users opening these blocks in Gutenberg to edit them using the updated block. + - Provide a "deprecated" version of the block allowing users opening these in the block editor to edit them using the updated block. A block can have several deprecated versions. A deprecation will be tried if a parsed block appears to be invalid, or if there is a deprecation defined for which its `isEligible` property function returns true. diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index fc69abc553e9fd..362bc08e6bccef 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -197,11 +197,11 @@ const addListItem = ( newListItem ) => { ``` {% end %} -Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, Gutenberg follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. +Why do this? In JavaScript, arrays and objects are passed by reference, so this practice ensures changes won't affect other code that might hold references to the same data. Furthermore, the Gutenberg project follows the philosophy of the Redux library that [state should be immutable](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)—data should not be changed directly, but instead a new version of the data created containing the changes. ## Save -The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized by Gutenberg into `post_content`. +The `save` function defines the way in which the different attributes should be combined into the final markup, which is then serialized into `post_content`. {% codetabs %} {% ES5 %} diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index c78263ed6cbb1b..8a2d6c26a7dabf 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -546,21 +546,21 @@ anchor: true, customClassName: false, ``` -- `className` (default `true`): By default, Gutenberg adds a class with the form `.wp-block-your-block-name` to the root element of your saved markup. This helps having a consistent mechanism for styling blocks that themes and plugins can rely on. If for whatever reason a class is not desired on the markup, this functionality can be disabled. +- `className` (default `true`): By default, the class `.wp-block-your-block-name` is added to the root element of your saved markup. This helps having a consistent mechanism for styling blocks that themes and plugins can rely on. If for whatever reason a class is not desired on the markup, this functionality can be disabled. ```js // Remove the support for the generated className. className: false, ``` -- `html` (default `true`): By default, Gutenberg will allow a block's markup to be edited individually. To disable this behavior, set `html` to `false`. +- `html` (default `true`): By default, a block's markup can be edited individually. To disable this behavior, set `html` to `false`. ```js // Remove support for an HTML mode. html: false, ``` -- `inserter` (default `true`): By default, all blocks will appear in the Gutenberg inserter. To hide a block so that it can only be inserted programmatically, set `inserter` to `false`. +- `inserter` (default `true`): By default, all blocks will appear in the inserter. To hide a block so that it can only be inserted programmatically, set `inserter` to `false`. ```js // Hide this block from the inserter. diff --git a/docs/designers-developers/developers/themes/README.md b/docs/designers-developers/developers/themes/README.md index e2bc67c0599eaa..c36b28093d015b 100644 --- a/docs/designers-developers/developers/themes/README.md +++ b/docs/designers-developers/developers/themes/README.md @@ -1,4 +1,4 @@ -# Theming for Gutenberg +# Theming for the Block Editor The new editor provides a number of options for theme designers and developers, including theme-defined color settings, font size control, and much more. diff --git a/docs/designers-developers/developers/themes/theme-support.md b/docs/designers-developers/developers/themes/theme-support.md index ad1c76bb9a039d..210072f5d2cf0b 100644 --- a/docs/designers-developers/developers/themes/theme-support.md +++ b/docs/designers-developers/developers/themes/theme-support.md @@ -61,7 +61,7 @@ For more information about this function, see [the developer docs on `add_theme_ It can be difficult to create a responsive layout that accommodates wide images, a sidebar, a centered column, and floated elements that stay within that centered column. -Gutenberg adds additional markup to floated images to make styling them easier. +The block editor adds additional markup to floated images to make styling them easier. Here's the markup for an `Image` with a caption: @@ -87,7 +87,7 @@ Here's an example using the above markup to achieve a responsive layout that fea ### Block Color Palettes -Different blocks have the possibility of customizing colors. Gutenberg provides a default palette, but a theme can overwrite it and provide its own: +Different blocks have the possibility of customizing colors. The block editor provides a default palette, but a theme can overwrite it and provide its own: ```php add_theme_support( 'editor-color-palette', array( @@ -114,7 +114,7 @@ add_theme_support( 'editor-color-palette', array( ) ); ``` -`name` is a human-readable label (demonstrated above) that appears in the tooltip and provides a meaningful description of the color to users. It is especially important for those who rely on screen readers or would otherwise have difficulty perceiving the color. `slug` is a unique identifier for the color and is used to generate the CSS classes used by the Gutenberg color palette. `color` is the hexadecimal code to specify the color. +`name` is a human-readable label (demonstrated above) that appears in the tooltip and provides a meaningful description of the color to users. It is especially important for those who rely on screen readers or would otherwise have difficulty perceiving the color. `slug` is a unique identifier for the color and is used to generate the CSS classes used by the block editor color palette. `color` is the hexadecimal code to specify the color. Some colors change dynamically — such as "Primary" and "Secondary" color — such as in the Twenty Nineteen theme and cannot be described programmatically. In spite of that, it is still advisable to provide meaningful `name`s for at least the default values when applicable. @@ -136,7 +136,7 @@ The class name is built appending 'has-', followed by the class name _using_ keb ### Block Font Sizes: -Blocks may allow the user to configure the font sizes they use, e.g., the paragraph block. Gutenberg provides a default set of font sizes, but a theme can overwrite it and provide its own: +Blocks may allow the user to configure the font sizes they use, e.g., the paragraph block. The block provides a default set of font sizes, but a theme can overwrite it and provide its own: ```php add_theme_support( 'editor-font-sizes', array( @@ -184,7 +184,7 @@ Themes can disable the ability to set custom font sizes with the following code: add_theme_support('disable-custom-font-sizes'); ``` -When set, users will be restricted to the default sizes provided in Gutenberg or the sizes provided via the `editor-font-sizes` theme support setting. +When set, users will be restricted to the default sizes provided in the block editor or the sizes provided via the `editor-font-sizes` theme support setting. ### Disabling custom colors in block Color Palettes @@ -200,9 +200,9 @@ This flag will make sure users are only able to choose colors from the `editor-c ## Editor styles -Gutenberg supports the theme's [editor styles](https://codex.wordpress.org/Editor_Style), however it works a little differently than in the classic editor. +The block editor supports the theme's [editor styles](https://codex.wordpress.org/Editor_Style), however it works a little differently than in the classic editor. -In the classic editor, the editor stylesheet is loaded directly into the iframe of the WYSIWYG editor, with no changes. Gutenberg, however, doesn't use iframes. To make sure your styles are applied only to the content of the editor, we automatically transform your editor styles by selectively rewriting or adjusting certain CSS selectors. This also allows Gutenberg to leverage your editor style in block variation previews. +In the classic editor, the editor stylesheet is loaded directly into the iframe of the WYSIWYG editor, with no changes. The block editor, however, doesn't use iframes. To make sure your styles are applied only to the content of the editor, we automatically transform your editor styles by selectively rewriting or adjusting certain CSS selectors. This also allows the block editor to leverage your editor style in block variation previews. For example, if you write `body { ... }` in your editor style, this is rewritten to `.editor-styles-wrapper { ... }`. This also means that you should _not_ target any of the editor class names directly. @@ -212,7 +212,7 @@ Because it works a little differently, you need to opt-in to this by adding an e add_theme_support('editor-styles'); ``` -You shouldn't need to change your editor styles too much; most themes can add the snippet above and get similar results in the classic editor and inside Gutenberg. +You shouldn't need to change your editor styles too much; most themes can add the snippet above and get similar results in the classic editor and inside the block editor. ### Dark backgrounds diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index 8c92d48349b7e8..73448eadf0d023 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -130,7 +130,7 @@ There are a few things to notice: * The built-in `save` function just returns `null` because the rendering is performed server-side. * The server-side rendering is a function taking the block attributes and the block inner content as arguments, and returning the markup (quite similar to shortcodes) -## Live rendering in Gutenberg editor +## Live rendering in the block editor Gutenberg 2.8 added the [`<ServerSideRender>`](/packages/components/src/server-side-render) block which enables rendering to take place on the server using PHP rather than in JavaScript. diff --git a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md index 2e6b1f4d21a419..410e8b8be1c38b 100644 --- a/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md +++ b/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md @@ -2,7 +2,7 @@ This page covers how to set up your development environment to use the ESNext and [JSX](https://reactjs.org/docs/introducing-jsx.html) syntaxes. ESNext is JavaScript code written using features that are only available in a specification greater than ECMAScript 5 (ES5 for short). JSX is a custom syntax extension to JavaScript which helps you to describe what the UI should look like. -This documentation covers development for your plugin to work with Gutenberg. If you want to setup a development environment for developing Gutenberg itself, see the [Getting Started](/docs/contributors/getting-started.md) documentation. +This documentation covers development for your plugin to work with the new setup provided by the Gutenberg project (ie: the block editor). If you want to develop Gutenberg itself, see the [Getting Started](/docs/contributors/getting-started.md) documentation. Most browsers can not interpret or run ESNext and JSX syntaxes, so we use a transformation step to convert these syntaxes to code that browsers can understand. diff --git a/docs/designers-developers/developers/tutorials/javascript/readme.md b/docs/designers-developers/developers/tutorials/javascript/readme.md index 1c2f9c2c110716..af8a895461e412 100644 --- a/docs/designers-developers/developers/tutorials/javascript/readme.md +++ b/docs/designers-developers/developers/tutorials/javascript/readme.md @@ -1,6 +1,6 @@ # Getting Started with JavaScript -The purpose of this tutorial is to step through getting started with JavaScript and WordPress, specifically around the new block editor. The Gutenberg Handbook documentation contains information on the APIs available for working with the block editor. The goal of this tutorial is to get you comfortable on how to use the API reference and snippets of code found within. +The purpose of this tutorial is to step through getting started with JavaScript and WordPress, specifically around the new block editor. The Block Editor Handbook contains information on the APIs available for working with this new setup. The goal of this tutorial is to get you comfortable on how to use the API reference and snippets of code found within. ### What is JavaScript diff --git a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md index 416afe6254934d..86900a89b8619a 100644 --- a/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md +++ b/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md @@ -1,12 +1,12 @@ # JavaScript Versions and Build Step -The Gutenberg Handbook shows JavaScript examples in two syntaxes: ES5 and ESNext. These are version names for the JavaScript language standard definitions. You may also see elsewhere the names ES6, or ECMAScript 2015 mentioned. See the [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) Wikipedia article for all the details. +The Block Editor Handbook shows JavaScript examples in two syntaxes: ES5 and ESNext. These are version names for the JavaScript language standard definitions. You may also see elsewhere the names ES6, or ECMAScript 2015 mentioned. See the [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) Wikipedia article for all the details. ES5 code is compatible with WordPress's minimum [target for browser support](https://make.wordpress.org/core/handbook/best-practices/browser-support/). "ESNext" doesn't refer to a specific version of JavaScript, but is "dynamic" and refers to the next language definitions, whatever they might be. Because some browsers won't support these features yet (because they're new or proposed), an extra build step is required to transform the code to a syntax that works in all browsers. Webpack and babel are the tools that perform this transformation step. -Additionally, the ESNext code examples in the Gutenberg handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using Webpack and Babel to transform into compatible code. +Additionally, the ESNext code examples in the handbook include [JSX syntax](https://reactjs.org/docs/introducing-jsx.html), a syntax that blends HTML and JavaScript. It makes it easier to read and write markup code, but likewise requires the build step using Webpack and Babel to transform into compatible code. For simplicity, the JavaScript tutorial uses the ES5 definition, without JSX. This code can run straight in your browser and does not require an additional build step. In many cases, it will be perfectly fine to follow the same approach to quickly start experimenting with your plugin or theme. As soon as your codebase grows to hundreds of lines it might be a good idea to get familiar with the [JavaScript Build Setup documentation](/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md) for setting up a development environment to use ESNext syntax. diff --git a/docs/manifest.json b/docs/manifest.json index 523a83487f11c1..9e3806f91b1160 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -132,7 +132,7 @@ "parent": "developers" }, { - "title": "Theming for Gutenberg", + "title": "Theming for the Block Editor", "slug": "themes", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/themes/README.md", "parent": "developers" From f5821226770658ab8d02dda20e8fb87d3660e0c5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 3 May 2019 12:16:22 -0400 Subject: [PATCH 021/664] docgen: Omit docblocks with private tag (#15173) --- packages/blocks/README.md | 8 ---- packages/blocks/src/api/children.js | 11 +++++ packages/blocks/src/api/node.js | 11 +++++ packages/docgen/CHANGELOG.md | 4 ++ .../docgen/src/get-symbol-tags-by-name.js | 16 ++++++++ packages/docgen/src/index.js | 17 ++++++-- packages/docgen/src/is-symbol-private.js | 16 ++++++++ packages/docgen/src/markdown/formatter.js | 19 +++++---- packages/docgen/src/markdown/index.js | 6 +-- .../docgen/src/test/fixtures/markdown/code.js | 24 ----------- .../docgen/src/test/fixtures/markdown/docs.md | 41 ------------------- 11 files changed, 86 insertions(+), 87 deletions(-) create mode 100644 packages/docgen/src/get-symbol-tags-by-name.js create mode 100644 packages/docgen/src/is-symbol-private.js delete mode 100644 packages/docgen/src/test/fixtures/markdown/code.js delete mode 100644 packages/docgen/src/test/fixtures/markdown/docs.md diff --git a/packages/blocks/README.md b/packages/blocks/README.md index e6b041aa7fb576..03442bbe5e7297 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -176,10 +176,6 @@ In the random image block above, we've given the `alt` attribute of the image a <!-- START TOKEN(Autogenerated API docs) --> -<a name="children" href="#children">#</a> **children** - -Undocumented declaration. - <a name="cloneBlock" href="#cloneBlock">#</a> **cloneBlock** Given a block object, returns a copy of the block object, optionally merging @@ -536,10 +532,6 @@ _Returns_ - `boolean`: True if the parameter is a valid icon and false otherwise. -<a name="node" href="#node">#</a> **node** - -Undocumented declaration. - <a name="normalizeIconObject" href="#normalizeIconObject">#</a> **normalizeIconObject** Function that receives an icon as set by the blocks during the registration diff --git a/packages/blocks/src/api/children.js b/packages/blocks/src/api/children.js index c31064f5e22a92..7cf70d2da78151 100644 --- a/packages/blocks/src/api/children.js +++ b/packages/blocks/src/api/children.js @@ -138,6 +138,17 @@ export function matcher( selector ) { }; } +/** + * Object of utility functions used in managing block attribute values of + * source `children`. + * + * @see https://github.com/WordPress/gutenberg/pull/10439 + * + * @deprecated since 4.0. The `children` source should not be used, and can be + * replaced by the `html` source. + * + * @private + */ export default { concat, getChildrenArray, diff --git a/packages/blocks/src/api/node.js b/packages/blocks/src/api/node.js index abcacf2b41c0c8..075715f1d84c4a 100644 --- a/packages/blocks/src/api/node.js +++ b/packages/blocks/src/api/node.js @@ -118,6 +118,17 @@ export function matcher( selector ) { }; } +/** + * Object of utility functions used in managing block attribute values of + * source `node`. + * + * @see https://github.com/WordPress/gutenberg/pull/10439 + * + * @deprecated since 4.0. The `node` source should not be used, and can be + * replaced by the `html` source. + * + * @private + */ export default { isNodeOfType, fromDOM, diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index ebd35131a443d0..943d22d361ba0d 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,5 +1,9 @@ ## Master +### Enhancements + +- Docblocks including a `@private` tag will be omitted from the generated result. + ### Internal - Remove unneccessary argument from an instance of `Array#pop`. diff --git a/packages/docgen/src/get-symbol-tags-by-name.js b/packages/docgen/src/get-symbol-tags-by-name.js new file mode 100644 index 00000000000000..194077cf11c9e4 --- /dev/null +++ b/packages/docgen/src/get-symbol-tags-by-name.js @@ -0,0 +1,16 @@ +/** + * Given a symbol object and tag name(s), returns tag objects from the symbol + * matching the given name. + * + * @param {Object} symbol Symbol object. + * @param {...string} names Names of tags to return. + * + * @return {Object[]} Matching tag objects. + */ +function getSymbolTagsByName( symbol, ...names ) { + return symbol.tags.filter( ( tag ) => { + return names.some( ( name ) => name === tag.title ); + } ); +} + +module.exports = getSymbolTagsByName; diff --git a/packages/docgen/src/index.js b/packages/docgen/src/index.js index 8b03dfe9bfafcd..475a458955d070 100644 --- a/packages/docgen/src/index.js +++ b/packages/docgen/src/index.js @@ -10,6 +10,7 @@ const { last } = require( 'lodash' ); */ const engine = require( './engine' ); const defaultMarkdownFormatter = require( './markdown' ); +const isSymbolPrivate = require( './is-symbol-private' ); /** * Helpers functions. @@ -102,7 +103,17 @@ module.exports = function( sourceFile, options ) { // Process const result = processFile( processDir, sourceFile ); - const filteredIr = result.ir.filter( ( { name } ) => options.ignore ? ! name.match( options.ignore ) : true ); + const filteredIR = result.ir.filter( ( symbol ) => { + if ( isSymbolPrivate( symbol ) ) { + return false; + } + + if ( options.ignore ) { + return ! symbol.name.match( options.ignore ); + } + + return true; + } ); // Ouput if ( result === undefined ) { @@ -113,9 +124,9 @@ module.exports = function( sourceFile, options ) { } if ( options.formatter ) { - runCustomFormatter( path.join( processDir, options.formatter ), processDir, doc, filteredIr, 'API' ); + runCustomFormatter( path.join( processDir, options.formatter ), processDir, doc, filteredIR, 'API' ); } else { - defaultMarkdownFormatter( options, processDir, doc, filteredIr, 'API' ); + defaultMarkdownFormatter( options, processDir, doc, filteredIR, 'API' ); } if ( debugMode ) { diff --git a/packages/docgen/src/is-symbol-private.js b/packages/docgen/src/is-symbol-private.js new file mode 100644 index 00000000000000..a26974f3d42559 --- /dev/null +++ b/packages/docgen/src/is-symbol-private.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +const getSymbolTagsByName = require( './get-symbol-tags-by-name' ); + +/** + * Returns true if, given a symbol object, it contains a @private tag, or false + * otherwise. + * + * @param {Object} symbol Symbol object. + * + * @return {boolean} Whether symbol is private. + */ +const isSymbolPrivate = ( symbol ) => getSymbolTagsByName( symbol, 'private' ).length > 0; + +module.exports = isSymbolPrivate; diff --git a/packages/docgen/src/markdown/formatter.js b/packages/docgen/src/markdown/formatter.js index 0912b7d0343168..eaec51925f3062 100644 --- a/packages/docgen/src/markdown/formatter.js +++ b/packages/docgen/src/markdown/formatter.js @@ -1,4 +1,7 @@ -const getTagsByName = ( tags, ...names ) => tags.filter( ( tag ) => names.some( ( name ) => name === tag.title ) ); +/** + * Internal dependencies + */ +const getSymbolTagsByName = require( '../get-symbol-tags-by-name' ); const cleanSpaces = ( paragraph ) => paragraph ? @@ -57,7 +60,7 @@ const getSymbolHeading = ( text ) => { }; module.exports = function( rootDir, docPath, symbols, headingTitle, headingStartIndex ) { - const docs = [ ]; + const docs = []; let headingIndex = headingStartIndex || 1; if ( headingTitle ) { docs.push( getHeading( headingIndex, `${ headingTitle }` ) ); @@ -79,30 +82,30 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart if ( symbols && symbols.length > 0 ) { symbols.forEach( ( symbol ) => { docs.push( getSymbolHeading( symbol.name ) ); - formatDeprecated( getTagsByName( symbol.tags, 'deprecated' ), docs ); + formatDeprecated( getSymbolTagsByName( symbol, 'deprecated' ), docs ); formatDescription( symbol.description, docs ); formatTag( 'Related', - getTagsByName( symbol.tags, 'see', 'link' ), + getSymbolTagsByName( symbol, 'see', 'link' ), ( tag ) => `\n- ${ tag.description }`, docs ); - formatExamples( getTagsByName( symbol.tags, 'example' ), docs ); + formatExamples( getSymbolTagsByName( symbol, 'example' ), docs ); formatTag( 'Type', - getTagsByName( symbol.tags, 'type' ), + getSymbolTagsByName( symbol, 'type' ), ( tag ) => `\n- \`${ tag.type }\` ${ cleanSpaces( tag.description ) }`, docs ); formatTag( 'Parameters', - getTagsByName( symbol.tags, 'param' ), + getSymbolTagsByName( symbol, 'param' ), ( tag ) => `\n- *${ tag.name }* \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, docs ); formatTag( 'Returns', - getTagsByName( symbol.tags, 'return' ), + getSymbolTagsByName( symbol, 'return' ), ( tag ) => `\n- \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, docs ); diff --git a/packages/docgen/src/markdown/index.js b/packages/docgen/src/markdown/index.js index 499e211e1873a4..b73041903bc7a2 100644 --- a/packages/docgen/src/markdown/index.js +++ b/packages/docgen/src/markdown/index.js @@ -24,10 +24,10 @@ const appendOrEmbedContents = ( { options, newContents } ) => { }; }; -module.exports = function( options, processDir, doc, filteredIr, headingTitle ) { +module.exports = function( options, processDir, doc, filteredIR, headingTitle ) { if ( options.toSection || options.toToken ) { const currentReadmeFile = fs.readFileSync( options.output, 'utf8' ); - const newContents = unified().use( remarkParser ).parse( formatter( processDir, doc, filteredIr, null ) ); + const newContents = unified().use( remarkParser ).parse( formatter( processDir, doc, filteredIR, null ) ); remark() .use( { settings: { commonmark: true } } ) .use( appendOrEmbedContents, { options, newContents } ) @@ -38,7 +38,7 @@ module.exports = function( options, processDir, doc, filteredIr, headingTitle ) fs.writeFileSync( doc, file ); } ); } else { - const output = formatter( processDir, doc, filteredIr, headingTitle ); + const output = formatter( processDir, doc, filteredIR, headingTitle ); fs.writeFileSync( doc, output ); } }; diff --git a/packages/docgen/src/test/fixtures/markdown/code.js b/packages/docgen/src/test/fixtures/markdown/code.js deleted file mode 100644 index 8d4261e0f63289..00000000000000 --- a/packages/docgen/src/test/fixtures/markdown/code.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * A function that adds two parameters. - * - * @deprecated Use native addition instead. - * @since v2 - * - * @see addition - * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators - * - * @param {number} firstParam The first param to add. - * @param {number} secondParam The second param to add. - * - * @example - * - * ```js - * const addResult = sum( 1, 3 ); - * console.log( addResult ); // will yield 4 - * ``` - * - * @return {number} The result of adding the two params. - */ -export const sum = ( firstParam, secondParam ) => { - return firstParam + secondParam; -}; diff --git a/packages/docgen/src/test/fixtures/markdown/docs.md b/packages/docgen/src/test/fixtures/markdown/docs.md deleted file mode 100644 index 12baa9046d0280..00000000000000 --- a/packages/docgen/src/test/fixtures/markdown/docs.md +++ /dev/null @@ -1,41 +0,0 @@ -# Package docs - -This is some package docs. - -## API Docs - -<!-- START TOKEN(Autogenerated API docs) --> - -### sum - -[code.js#L22-L24](code.js#L22-L24) - -> **Deprecated** Use native addition instead. - -A function that adds two parameters. - -**Related** - -- addition -- <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators> - -**Usage** - -```js -const addResult = sum( 1, 3 ); -console.log( addResult ); // will yield 4 -``` - -**Parameters** - -- **firstParam** `number`: The first param to add. -- **secondParam** `number`: The second param to add. - -**Returns** - -`number` The result of adding the two params. - - -<!-- END TOKEN(Autogenerated API docs) --> - -After token content. From b3712597ba4dfcab87b3dbb036f16ec0ff065f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 3 May 2019 23:35:17 +0200 Subject: [PATCH 022/664] Update docs for selectors & actions moved to block-editor (#15424) * Document selectors moved to block-editor * Document actions moved to block-editor * Ignore actions that do not have a description. This is to avoid adding any symbols to the generated handbook docs just yet. We'll add them back when switching to the new system. * Remove spaces * Give some breadth --- docs/tool/parser.js | 11 +- packages/editor/src/store/actions.js | 119 ++++++++++++++ packages/editor/src/store/selectors.js | 211 +++++++++++++++++++++++++ 3 files changed, 340 insertions(+), 1 deletion(-) diff --git a/docs/tool/parser.js b/docs/tool/parser.js index 25dc7e34d41f8d..12ed0e002b2615 100644 --- a/docs/tool/parser.js +++ b/docs/tool/parser.js @@ -103,6 +103,15 @@ const hasPrivateTag = ( docBlock ) => hasDocBlockTag( docBlock, 'private' ); */ const hasParamTag = ( docBlock ) => hasDocBlockTag( docBlock, 'param' ); +/** + * Returns true if the give DocBlock contains a description. + * + * @param {Object} docBlock Parsed DocBlock node. + * + * @return {boolean} Whether DocBlock contains a description. + */ +const hasDescription = ( docBlock ) => !! docBlock.description; + /** * Maps parse type to specific filtering logic by which to consider for * inclusion a parsed named export. @@ -129,7 +138,7 @@ const FILTER_PARSED_DOCBLOCK_BY_TYPE = { * * @return {boolean} Whether documented action should be included. */ - actions: negate( hasPrivateTag ), + actions: overEvery( [ hasDescription, negate( hasPrivateTag ) ] ), }; module.exports = function( config ) { diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 2f80e0d488309c..d7d5b06f2821fc 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -769,33 +769,152 @@ const getBlockEditorAction = ( name ) => function* ( ...args ) { yield dispatch( 'core/block-editor', name, ...args ); }; +/** + * @see resetBlocks in core/block-editor store. + */ export const resetBlocks = getBlockEditorAction( 'resetBlocks' ); + +/** + * @see receiveBlocks in core/block-editor store. + */ export const receiveBlocks = getBlockEditorAction( 'receiveBlocks' ); + +/** + * @see updateBlock in core/block-editor store. + */ export const updateBlock = getBlockEditorAction( 'updateBlock' ); + +/** + * @see updateBlockAttributes in core/block-editor store. + */ export const updateBlockAttributes = getBlockEditorAction( 'updateBlockAttributes' ); + +/** + * @see selectBlock in core/block-editor store. + */ export const selectBlock = getBlockEditorAction( 'selectBlock' ); + +/** + * @see startMultiSelect in core/block-editor store. + */ export const startMultiSelect = getBlockEditorAction( 'startMultiSelect' ); + +/** + * @see stopMultiSelect in core/block-editor store. + */ export const stopMultiSelect = getBlockEditorAction( 'stopMultiSelect' ); + +/** + * @see multiSelect in core/block-editor store. + */ export const multiSelect = getBlockEditorAction( 'multiSelect' ); + +/** + * @see clearSelectedBlock in core/block-editor store. + */ export const clearSelectedBlock = getBlockEditorAction( 'clearSelectedBlock' ); + +/** + * @see toggleSelection in core/block-editor store. + */ export const toggleSelection = getBlockEditorAction( 'toggleSelection' ); + +/** + * @see replaceBlocks in core/block-editor store. + */ export const replaceBlocks = getBlockEditorAction( 'replaceBlocks' ); + +/** + * @see moveBlocksDown in core/block-editor store. + */ export const moveBlocksDown = getBlockEditorAction( 'moveBlocksDown' ); + +/** + * @see moveBlocksUp in core/block-editor store. + */ export const moveBlocksUp = getBlockEditorAction( 'moveBlocksUp' ); + +/** + * @see moveBlockToPosition in core/block-editor store. + */ export const moveBlockToPosition = getBlockEditorAction( 'moveBlockToPosition' ); + +/** + * @see insertBlock in core/block-editor store. + */ export const insertBlock = getBlockEditorAction( 'insertBlock' ); + +/** + * @see insertBlocks in core/block-editor store. + */ export const insertBlocks = getBlockEditorAction( 'insertBlocks' ); + +/** + * @see showInsertionPoint in core/block-editor store. + */ export const showInsertionPoint = getBlockEditorAction( 'showInsertionPoint' ); + +/** + * @see hideInsertionPoint in core/block-editor store. + */ export const hideInsertionPoint = getBlockEditorAction( 'hideInsertionPoint' ); + +/** + * @see setTemplateValidity in core/block-editor store. + */ export const setTemplateValidity = getBlockEditorAction( 'setTemplateValidity' ); + +/** + * @see synchronizeTemplate in core/block-editor store. + */ export const synchronizeTemplate = getBlockEditorAction( 'synchronizeTemplate' ); + +/** + * @see mergeBlocks in core/block-editor store. + */ export const mergeBlocks = getBlockEditorAction( 'mergeBlocks' ); + +/** + * @see removeBlocks in core/block-editor store. + */ export const removeBlocks = getBlockEditorAction( 'removeBlocks' ); + +/** + * @see removeBlock in core/block-editor store. + */ export const removeBlock = getBlockEditorAction( 'removeBlock' ); + +/** + * @see toggleBlockMode in core/block-editor store. + */ export const toggleBlockMode = getBlockEditorAction( 'toggleBlockMode' ); + +/** + * @see startTyping in core/block-editor store. + */ export const startTyping = getBlockEditorAction( 'startTyping' ); + +/** + * @see stopTyping in core/block-editor store. + */ export const stopTyping = getBlockEditorAction( 'stopTyping' ); + +/** + * @see enterFormattedText in core/block-editor store. + */ export const enterFormattedText = getBlockEditorAction( 'enterFormattedText' ); + +/** + * @see exitFormattedText in core/block-editor store. + */ export const exitFormattedText = getBlockEditorAction( 'exitFormattedText' ); + +/** + * @see insertDefaultBlock in core/block-editor store. + */ export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' ); + +/** + * @see updateBlockListSettings in core/block-editor store. + */ export const updateBlockListSettings = getBlockEditorAction( 'updateBlockListSettings' ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 7ebb18b63832dc..094b47bc716774 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1164,56 +1164,267 @@ function getBlockEditorSelector( name ) { } ); } +/** + * @see getBlockDependantsCacheBust in core/block-editor store. + */ export const getBlockDependantsCacheBust = getBlockEditorSelector( 'getBlockDependantsCacheBust' ); + +/** + * @see getBlockName in core/block-editor store. + */ export const getBlockName = getBlockEditorSelector( 'getBlockName' ); + +/** + * @see isBlockValid in core/block-editor store. + */ export const isBlockValid = getBlockEditorSelector( 'isBlockValid' ); + +/** + * @see getBlockAttributes in core/block-editor store. + */ export const getBlockAttributes = getBlockEditorSelector( 'getBlockAttributes' ); + +/** + * @see getBlock in core/block-editor store. + */ export const getBlock = getBlockEditorSelector( 'getBlock' ); + +/** + * @see getBlocks in core/block-editor store. + */ export const getBlocks = getBlockEditorSelector( 'getBlocks' ); + +/** + * @see __unstableGetBlockWithoutInnerBlocks in core/block-editor store. + */ export const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector( '__unstableGetBlockWithoutInnerBlocks' ); + +/** + * @see getClientIdsOfDescendants in core/block-editor store. + */ export const getClientIdsOfDescendants = getBlockEditorSelector( 'getClientIdsOfDescendants' ); + +/** + * @see getClientIdsWithDescendants in core/block-editor store. + */ export const getClientIdsWithDescendants = getBlockEditorSelector( 'getClientIdsWithDescendants' ); + +/** + * @see getGlobalBlockCount in core/block-editor store. + */ export const getGlobalBlockCount = getBlockEditorSelector( 'getGlobalBlockCount' ); + +/** + * @see getBlocksByClientId in core/block-editor store. + */ export const getBlocksByClientId = getBlockEditorSelector( 'getBlocksByClientId' ); + +/** + * @see getBlockCount in core/block-editor store. + */ export const getBlockCount = getBlockEditorSelector( 'getBlockCount' ); + +/** + * @see getBlockSelectionStart in core/block-editor store. + */ export const getBlockSelectionStart = getBlockEditorSelector( 'getBlockSelectionStart' ); + +/** + * @see getBlockSelectionEnd in core/block-editor store. + */ export const getBlockSelectionEnd = getBlockEditorSelector( 'getBlockSelectionEnd' ); + +/** + * @see getSelectedBlockCount in core/block-editor store. + */ export const getSelectedBlockCount = getBlockEditorSelector( 'getSelectedBlockCount' ); + +/** + * @see hasSelectedBlock in core/block-editor store. + */ export const hasSelectedBlock = getBlockEditorSelector( 'hasSelectedBlock' ); + +/** + * @see getSelectedBlockClientId in core/block-editor store. + */ export const getSelectedBlockClientId = getBlockEditorSelector( 'getSelectedBlockClientId' ); + +/** + * @see getSelectedBlock in core/block-editor store. + */ export const getSelectedBlock = getBlockEditorSelector( 'getSelectedBlock' ); + +/** + * @see getBlockRootClientId in core/block-editor store. + */ export const getBlockRootClientId = getBlockEditorSelector( 'getBlockRootClientId' ); + +/** + * @see getBlockHierarchyRootClientId in core/block-editor store. + */ export const getBlockHierarchyRootClientId = getBlockEditorSelector( 'getBlockHierarchyRootClientId' ); + +/** + * @see getAdjacentBlockClientId in core/block-editor store. + */ export const getAdjacentBlockClientId = getBlockEditorSelector( 'getAdjacentBlockClientId' ); + +/** + * @see getPreviousBlockClientId in core/block-editor store. + */ export const getPreviousBlockClientId = getBlockEditorSelector( 'getPreviousBlockClientId' ); + +/** + * @see getNextBlockClientId in core/block-editor store. + */ export const getNextBlockClientId = getBlockEditorSelector( 'getNextBlockClientId' ); + +/** + * @see getSelectedBlocksInitialCaretPosition in core/block-editor store. + */ export const getSelectedBlocksInitialCaretPosition = getBlockEditorSelector( 'getSelectedBlocksInitialCaretPosition' ); + +/** + * @see getMultiSelectedBlockClientIds in core/block-editor store. + */ export const getMultiSelectedBlockClientIds = getBlockEditorSelector( 'getMultiSelectedBlockClientIds' ); + +/** + * @see getMultiSelectedBlocks in core/block-editor store. + */ export const getMultiSelectedBlocks = getBlockEditorSelector( 'getMultiSelectedBlocks' ); + +/** + * @see getFirstMultiSelectedBlockClientId in core/block-editor store. + */ export const getFirstMultiSelectedBlockClientId = getBlockEditorSelector( 'getFirstMultiSelectedBlockClientId' ); + +/** + * @see getLastMultiSelectedBlockClientId in core/block-editor store. + */ export const getLastMultiSelectedBlockClientId = getBlockEditorSelector( 'getLastMultiSelectedBlockClientId' ); + +/** + * @see isFirstMultiSelectedBlock in core/block-editor store. + */ export const isFirstMultiSelectedBlock = getBlockEditorSelector( 'isFirstMultiSelectedBlock' ); + +/** + * @see isBlockMultiSelected in core/block-editor store. + */ export const isBlockMultiSelected = getBlockEditorSelector( 'isBlockMultiSelected' ); + +/** + * @see isAncestorMultiSelected in core/block-editor store. + */ export const isAncestorMultiSelected = getBlockEditorSelector( 'isAncestorMultiSelected' ); + +/** + * @see getMultiSelectedBlocksStartClientId in core/block-editor store. + */ export const getMultiSelectedBlocksStartClientId = getBlockEditorSelector( 'getMultiSelectedBlocksStartClientId' ); + +/** + * @see getMultiSelectedBlocksEndClientId in core/block-editor store. + */ export const getMultiSelectedBlocksEndClientId = getBlockEditorSelector( 'getMultiSelectedBlocksEndClientId' ); + +/** + * @see getBlockOrder in core/block-editor store. + */ export const getBlockOrder = getBlockEditorSelector( 'getBlockOrder' ); + +/** + * @see getBlockIndex in core/block-editor store. + */ export const getBlockIndex = getBlockEditorSelector( 'getBlockIndex' ); + +/** + * @see isBlockSelected in core/block-editor store. + */ export const isBlockSelected = getBlockEditorSelector( 'isBlockSelected' ); + +/** + * @see hasSelectedInnerBlock in core/block-editor store. + */ export const hasSelectedInnerBlock = getBlockEditorSelector( 'hasSelectedInnerBlock' ); + +/** + * @see isBlockWithinSelection in core/block-editor store. + */ export const isBlockWithinSelection = getBlockEditorSelector( 'isBlockWithinSelection' ); + +/** + * @see hasMultiSelection in core/block-editor store. + */ export const hasMultiSelection = getBlockEditorSelector( 'hasMultiSelection' ); + +/** + * @see isMultiSelecting in core/block-editor store. + */ export const isMultiSelecting = getBlockEditorSelector( 'isMultiSelecting' ); + +/** + * @see isSelectionEnabled in core/block-editor store. + */ export const isSelectionEnabled = getBlockEditorSelector( 'isSelectionEnabled' ); + +/** + * @see getBlockMode in core/block-editor store. + */ export const getBlockMode = getBlockEditorSelector( 'getBlockMode' ); + +/** + * @see isTyping in core/block-editor store. + */ export const isTyping = getBlockEditorSelector( 'isTyping' ); + +/** + * @see isCaretWithinFormattedText in core/block-editor store. + */ export const isCaretWithinFormattedText = getBlockEditorSelector( 'isCaretWithinFormattedText' ); + +/** + * @see getBlockInsertionPoint in core/block-editor store. + */ export const getBlockInsertionPoint = getBlockEditorSelector( 'getBlockInsertionPoint' ); + +/** + * @see isBlockInsertionPointVisible in core/block-editor store. + */ export const isBlockInsertionPointVisible = getBlockEditorSelector( 'isBlockInsertionPointVisible' ); + +/** + * @see isValidTemplate in core/block-editor store. + */ export const isValidTemplate = getBlockEditorSelector( 'isValidTemplate' ); + +/** + * @see getTemplate in core/block-editor store. + */ export const getTemplate = getBlockEditorSelector( 'getTemplate' ); + +/** + * @see getTemplateLock in core/block-editor store. + */ export const getTemplateLock = getBlockEditorSelector( 'getTemplateLock' ); + +/** + * @see canInsertBlockType in core/block-editor store. + */ export const canInsertBlockType = getBlockEditorSelector( 'canInsertBlockType' ); + +/** + * @see getInserterItems in core/block-editor store. + */ export const getInserterItems = getBlockEditorSelector( 'getInserterItems' ); + +/** + * @see hasInserterItems in core/block-editor store. + */ export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); + +/** + * @see getBlockListSettings in core/block-editor store. + */ export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); From b612fe8616ce774af2ff1084af2e9af8b3de1de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Sat, 4 May 2019 00:10:00 +0200 Subject: [PATCH 023/664] Add private tag to INSERTER_UTILITY_* constants (#15423) --- packages/block-editor/src/store/selectors.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index be699167f3c6c7..6633611fbd9b19 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -27,13 +27,28 @@ import { hasChildBlocksWithInserterSupport, } from '@wordpress/blocks'; -/*** - * Module constants +// Module constants + +/** + * @private */ export const INSERTER_UTILITY_HIGH = 3; + +/** + * @private + */ export const INSERTER_UTILITY_MEDIUM = 2; + +/** + * @private + */ export const INSERTER_UTILITY_LOW = 1; + +/** + * @private + */ export const INSERTER_UTILITY_NONE = 0; + const MILLISECONDS_PER_HOUR = 3600 * 1000; const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; From 7addd763d89bd4f3c11debfec927c3725c630770 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Sat, 4 May 2019 09:51:59 +0800 Subject: [PATCH 024/664] Add preload paths for autosaves (#15067) * Add preload paths for autosaves Filter out any duplicate endpoints * update tests * Remove unused var * Add check for existence of post type object --- lib/client-assets.php | 36 +++++++++++++++++---- phpunit/class-extend-preload-paths-test.php | 30 +++++++++++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 47949cdccea4f0..4611cadab1172a 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -591,13 +591,33 @@ function gutenberg_extend_block_editor_styles( $settings ) { * additions here should be complemented with a corresponding core ticket to * reconcile the change upstream for future removal from Gutenberg. * - * @since 5.6.0 - * - * @param array $preload_paths $preload_paths Array of paths to preload. + * @param array $preload_paths Array of paths to preload. + * @param WP_Post $post Post being edited. * * @return array Filtered array of paths to preload. */ -function gutenberg_extend_block_editor_preload_paths( $preload_paths ) { +function gutenberg_extend_block_editor_preload_paths( $preload_paths, $post ) { + /* + * Preload any autosaves for the post. (see https://github.com/WordPress/gutenberg/pull/7945) + * + * Trac ticket: https://core.trac.wordpress.org/ticket/46974 + * + * At the time of writing, the change is not committed or released + * in core. This path should be removed from Gutenberg when the code is + * released in core, and the corresponding release version becomes + * the minimum supported version. + */ + $post_type_object = get_post_type_object( $post->post_type ); + + if ( isset( $post_type_object ) ) { + $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + $autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', $rest_base, $post->ID ); + + if ( ! in_array( $autosaves_path, $preload_paths ) ) { + $preload_paths[] = $autosaves_path; + } + } + /* * Used in considering user permissions for creating and updating blocks, * as condition for displaying relevant actions in the interface. @@ -607,10 +627,12 @@ function gutenberg_extend_block_editor_preload_paths( $preload_paths ) { * This is present in WordPress 5.2 and should be removed from Gutenberg * once WordPress 5.2 is the minimum supported version. */ - if ( ! in_array( array( '/wp/v2/blocks', 'OPTIONS' ), $preload_paths ) ) { - $preload_paths[] = array( '/wp/v2/blocks', 'OPTIONS' ); + $blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); + + if ( ! in_array( $blocks_path, $preload_paths ) ) { + $preload_paths[] = $blocks_path; } return $preload_paths; } -add_filter( 'block_editor_preload_paths', 'gutenberg_extend_block_editor_preload_paths' ); +add_filter( 'block_editor_preload_paths', 'gutenberg_extend_block_editor_preload_paths', 10, 2 ); diff --git a/phpunit/class-extend-preload-paths-test.php b/phpunit/class-extend-preload-paths-test.php index 5d683b87204a91..357eda859b06fa 100644 --- a/phpunit/class-extend-preload-paths-test.php +++ b/phpunit/class-extend-preload-paths-test.php @@ -7,20 +7,38 @@ class Extend_Preload_Paths_Test extends WP_UnitTestCase { /** - * Tests '/wp/v2/blocks' added if missing. + * Post object. + * + * @var WP_Post + */ + protected static $post; + + public static function wpSetUpBeforeClass( $factory ) { + self::$post = $factory->post->create_and_get(); + } + + /** + * Tests paths added if missing. */ function test_localizes_script() { - $preload_paths = gutenberg_extend_block_editor_preload_paths( array() ); + $preload_paths = gutenberg_extend_block_editor_preload_paths( array(), self::$post ); + + $expected_blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); + $expected_autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', 'posts', self::$post->ID ); - $this->assertEquals( array( array( '/wp/v2/blocks', 'OPTIONS' ) ), $preload_paths ); + $this->assertEquals( array( $expected_autosaves_path, $expected_blocks_path ), $preload_paths ); } /** - * Tests '/wp/v2/blocks' not added if present. + * Tests paths not added if present. */ function test_replaces_registered_properties() { - $preload_paths = gutenberg_extend_block_editor_preload_paths( array( array( '/wp/v2/blocks', 'OPTIONS' ) ) ); + $existing_blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); + $existing_autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', 'posts', self::$post->ID ); + $existing_preload_paths = array( $existing_blocks_path, $existing_autosaves_path ); + + $preload_paths = gutenberg_extend_block_editor_preload_paths( $existing_preload_paths, self::$post ); - $this->assertEquals( array( array( '/wp/v2/blocks', 'OPTIONS' ) ), $preload_paths ); + $this->assertEquals( $existing_preload_paths, $preload_paths ); } } From 03c5856992f9234679f3abd230f847e556db6c9e Mon Sep 17 00:00:00 2001 From: Brent Swisher <swisherb@gvsu.edu> Date: Sat, 4 May 2019 03:09:52 -0400 Subject: [PATCH 025/664] Add role to the copy all content menu item (#15383) It was previously only being reported as a button to screen readers, item GUT-67 of the WPCampus accessibility audit. --- packages/edit-post/src/plugins/copy-content-menu-item/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index 345d792c0e4d3a..06c83f73d10778 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -10,6 +10,7 @@ function CopyContentMenuItem( { editedPostContent, hasCopied, setState } ) { return ( <ClipboardButton text={ editedPostContent } + role="menuitem" className="components-menu-item__button" onCopy={ () => setState( { hasCopied: true } ) } onFinishCopy={ () => setState( { hasCopied: false } ) } From 1591471d62e5ddfdb6ef32d1cd635bba3eccd649 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Sat, 4 May 2019 10:18:38 +0100 Subject: [PATCH 026/664] Fix: SandBox component does not work outside WordPress context. (#15415) The sandbox component resizing logic required the iframe to have a style setting the widget to 100%. In the block editor context, things worked as expected because the block styles include a style that sets iframes to 100%. --- packages/components/src/sandbox/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/components/src/sandbox/style.scss b/packages/components/src/sandbox/style.scss index 49020202579cd7..f9850bb497965c 100644 --- a/packages/components/src/sandbox/style.scss +++ b/packages/components/src/sandbox/style.scss @@ -1,3 +1,7 @@ .components-sandbox { overflow: hidden; } + +iframe.components-sandbox { + width: 100%; +} From 4adbe72f846eff921630f101456d98d484df9fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Sat, 4 May 2019 12:38:39 +0200 Subject: [PATCH 027/664] Consolidate doc generation and fix next/prev links issue (#15421) * Use docgen to generate data docs * Consolidate * Do not expose INSERTER_UTILITY_* constants * Revert "Do not expose INSERTER_UTILITY_* constants" This reverts commit 3ac1b154ccf3264b45abe071d534e702103c48ea. We should merge this kind of changes before applying this PR. * Update docs to changes from rebase * Remove unused file --- .../developers/data/data-core-annotations.md | 14 +- .../developers/data/data-core-block-editor.md | 1342 +++++++++------- .../developers/data/data-core-blocks.md | 294 ++-- .../developers/data/data-core-edit-post.md | 463 +++--- .../developers/data/data-core-editor.md | 1396 ++++++++++++----- .../developers/data/data-core-notices.md | 145 +- .../developers/data/data-core-nux.md | 97 +- .../developers/data/data-core-viewport.md | 43 +- .../developers/data/data-core.md | 408 ++--- docs/manifest.json | 108 +- docs/toc.json | 12 +- docs/tool/config.js | 52 - docs/tool/generator.js | 100 -- docs/tool/index.js | 13 +- docs/tool/manifest.js | 22 +- docs/tool/parser.js | 201 --- docs/tool/update-data.js | 50 + 17 files changed, 2710 insertions(+), 2050 deletions(-) delete mode 100644 docs/tool/generator.js delete mode 100644 docs/tool/parser.js create mode 100755 docs/tool/update-data.js diff --git a/docs/designers-developers/developers/data/data-core-annotations.md b/docs/designers-developers/developers/data/data-core-annotations.md index 0aaf9963ba48aa..e71aefd27d2129 100644 --- a/docs/designers-developers/developers/data/data-core-annotations.md +++ b/docs/designers-developers/developers/data/data-core-annotations.md @@ -1,8 +1,20 @@ -# **core/annotations**: Annotations +# Annotations + +Namespace: `core/annotations`. ## Selectors +<!-- START TOKEN(Autogenerated selectors) --> + +Nothing to document. + +<!-- END TOKEN(Autogenerated selectors) --> ## Actions +<!-- START TOKEN(Autogenerated actions) --> + +Nothing to document. + +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index d152b4475f8b24..380b3366a64e00 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -1,8 +1,86 @@ -# **core/block-editor**: The Block Editor’s Data +# The Block Editor’s Data + +Namespace: `core/block-editor`. ## Selectors -### getBlockDependantsCacheBust +<!-- START TOKEN(Autogenerated selectors) --> + +<a name="canInsertBlockType" href="#canInsertBlockType">#</a> **canInsertBlockType** + +Determines if the given block type is allowed to be inserted into the block list. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _blockName_ `string`: The name of the block type, e.g.' core/paragraph'. +- _rootClientId_ `?string`: Optional root client ID of block list. + +_Returns_ + +- `boolean`: Whether the given block type is allowed to be inserted. + +<a name="getAdjacentBlockClientId" href="#getAdjacentBlockClientId">#</a> **getAdjacentBlockClientId** + +Returns the client ID of the block adjacent one at the given reference +startClientId and modifier directionality. Defaults start startClientId to +the selected block, and direction as next block. Returns null if there is no +adjacent block. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _startClientId_ `?string`: Optional client ID of block from which to search. +- _modifier_ `?number`: Directionality multiplier (1 next, -1 previous). + +_Returns_ + +- `?string`: Return the client ID of the block, or null if none exists. + +<a name="getBlock" href="#getBlock">#</a> **getBlock** + +Returns a block given its client ID. This is a parsed copy of the block, +containing its `blockName`, `clientId`, and current `attributes` state. This +is not the block's registration settings, which must be retrieved from the +blocks module registration store. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `Object`: Parsed block object. + +<a name="getBlockAttributes" href="#getBlockAttributes">#</a> **getBlockAttributes** + +Returns a block's attributes given its client ID, or null if no block exists with +the client ID. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `?Object`: Block attributes. + +<a name="getBlockCount" href="#getBlockCount">#</a> **getBlockCount** + +Returns the number of blocks currently present in the post. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. + +_Returns_ + +- `number`: Number of blocks in the post. + +<a name="getBlockDependantsCacheBust" href="#getBlockDependantsCacheBust">#</a> **getBlockDependantsCacheBust** Returns a new reference when the inner blocks of a given block client ID change. This is used exclusively as a memoized selector dependant, relying @@ -11,69 +89,128 @@ blocks defined as dependencies. This abuses mechanics of the selector memoization to return from the original selector function only when dependants change. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -### getBlockName +_Returns_ -Returns a block's name given its client ID, or null if no block exists with -the client ID. +- `*`: A value whose reference will change only when inner blocks of the given block client ID change. -*Parameters* +<a name="getBlockHierarchyRootClientId" href="#getBlockHierarchyRootClientId">#</a> **getBlockHierarchyRootClientId** - * state: Editor state. - * clientId: Block client ID. +Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. -*Returns* +_Parameters_ -Block name. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which to find root client ID. -### isBlockValid +_Returns_ -Returns whether a block is valid or not. +- `string`: Root client ID + +<a name="getBlockIndex" href="#getBlockIndex">#</a> **getBlockIndex** -*Parameters* +Returns the index at which the block corresponding to the specified client +ID occurs within the block order, or `-1` if the block does not exist. - * state: Editor state. - * clientId: Block client ID. +_Parameters_ -*Returns* +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. +- _rootClientId_ `?string`: Optional root client ID of block list. -Is Valid. +_Returns_ -### getBlockAttributes +- `number`: Index at which block exists in order. -Returns a block's attributes given its client ID, or null if no block exists with +<a name="getBlockInsertionPoint" href="#getBlockInsertionPoint">#</a> **getBlockInsertionPoint** + +Returns the insertion point, the index at which the new inserted block would +be placed. Defaults to the last index. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Object`: Insertion point object with `rootClientId`, `index`. + +<a name="getBlockListSettings" href="#getBlockListSettings">#</a> **getBlockListSettings** + +Returns the Block List settings of a block, if any exist. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `?string`: Block client ID. + +_Returns_ + +- `?Object`: Block settings of the block if set. + +<a name="getBlockMode" href="#getBlockMode">#</a> **getBlockMode** + +Returns the block's editing mode, defaulting to "visual" if not explicitly +assigned. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `Object`: Block editing mode. + +<a name="getBlockName" href="#getBlockName">#</a> **getBlockName** + +Returns a block's name given its client ID, or null if no block exists with the client ID. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Block attributes. +- `string`: Block name. -### getBlock +<a name="getBlockOrder" href="#getBlockOrder">#</a> **getBlockOrder** -Returns a block given its client ID. This is a parsed copy of the block, -containing its `blockName`, `clientId`, and current `attributes` state. This -is not the block's registration settings, which must be retrieved from the -blocks module registration store. +Returns an array containing all block client IDs in the editor in the order +they appear. Optionally accepts a root client ID of the block list for which +the order should be returned, defaulting to the top-level block order. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. +- `Array`: Ordered client IDs of editor blocks. -*Returns* +<a name="getBlockRootClientId" href="#getBlockRootClientId">#</a> **getBlockRootClientId** -Parsed block object. +Given a block client ID, returns the root block from which the block is +nested, an empty string for top-level blocks, or null if the block does not +exist. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which to find root client ID. + +_Returns_ -### getBlocks +- `?string`: Root client ID, if exists + +<a name="getBlocks" href="#getBlocks">#</a> **getBlocks** Returns all block objects for the current post being edited as an array in the order they appear in the post. @@ -81,1017 +218,1010 @@ the order they appear in the post. Note: It's important to memoize this selector to avoid return a new instance on each call -*Parameters* +_Parameters_ - * state: Editor state. - * rootClientId: Optional root client ID of block list. +- _state_ `Object`: Editor state. +- _rootClientId_ `?String`: Optional root client ID of block list. -*Returns* +_Returns_ -Post blocks. +- `Array<Object>`: Post blocks. -### getClientIdsOfDescendants +<a name="getBlocksByClientId" href="#getBlocksByClientId">#</a> **getBlocksByClientId** -Returns an array containing the clientIds of all descendants -of the blocks given. +Given an array of block client IDs, returns the corresponding array of block +objects. -*Parameters* +_Parameters_ - * state: Global application state. - * clientIds: Array of blocks to inspect. +- _state_ `Object`: Editor state. +- _clientIds_ `Array<string>`: Client IDs for which blocks are to be returned. -*Returns* +_Returns_ -ids of descendants. +- `Array<WPBlock>`: Block objects. -### getClientIdsWithDescendants +<a name="getBlockSelectionEnd" href="#getBlockSelectionEnd">#</a> **getBlockSelectionEnd** -Returns an array containing the clientIds of the top-level blocks -and their descendants of any depth (for nested blocks). +Returns the current block selection end. This value may be null, and it +may represent either a singular block selection or multi-selection end. +A selection is singular if its start and end match. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -ids of top-level and descendant blocks. +- `?string`: Client ID of block selection end. -### getGlobalBlockCount +<a name="getBlockSelectionStart" href="#getBlockSelectionStart">#</a> **getBlockSelectionStart** -Returns the total number of blocks, or the total number of blocks with a specific name in a post. -The number returned includes nested blocks. +Returns the current block selection start. This value may be null, and it +may represent either a singular block selection or multi-selection start. +A selection is singular if its start and end match. -*Parameters* +_Parameters_ - * state: Global application state. - * blockName: Optional block name, if specified only blocks of that type will be counted. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Number of blocks in the post, or number of blocks with name equal to blockName. +- `?string`: Client ID of block selection start. -### getBlocksByClientId +<a name="getClientIdsOfDescendants" href="#getClientIdsOfDescendants">#</a> **getClientIdsOfDescendants** -Given an array of block client IDs, returns the corresponding array of block -objects. +Returns an array containing the clientIds of all descendants +of the blocks given. -*Parameters* +_Parameters_ - * state: Editor state. - * clientIds: Client IDs for which blocks are to be returned. +- _state_ `Object`: Global application state. +- _clientIds_ `Array`: Array of blocks to inspect. -*Returns* +_Returns_ -Block objects. +- `Array`: ids of descendants. -### getBlockCount +<a name="getClientIdsWithDescendants" href="#getClientIdsWithDescendants">#</a> **getClientIdsWithDescendants** -Returns the number of blocks currently present in the post. +Returns an array containing the clientIds of the top-level blocks +and their descendants of any depth (for nested blocks). -*Parameters* +_Parameters_ - * state: Editor state. - * rootClientId: Optional root client ID of block list. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Number of blocks in the post. +- `Array`: ids of top-level and descendant blocks. -### getSelectionStart +<a name="getFirstMultiSelectedBlockClientId" href="#getFirstMultiSelectedBlockClientId">#</a> **getFirstMultiSelectedBlockClientId** -Returns the current selection start block client ID, attribute key and text -offset. +Returns the client ID of the first block in the multi-selection set, or null +if there is no multi-selection. -*Parameters* +_Parameters_ - * state: Block editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Selection start information. +- `?string`: First block client ID in the multi-selection set. -### getSelectionEnd +<a name="getGlobalBlockCount" href="#getGlobalBlockCount">#</a> **getGlobalBlockCount** -Returns the current selection end block client ID, attribute key and text -offset. +Returns the total number of blocks, or the total number of blocks with a specific name in a post. +The number returned includes nested blocks. -*Parameters* +_Parameters_ - * state: Block editor state. +- _state_ `Object`: Global application state. +- _blockName_ `?String`: Optional block name, if specified only blocks of that type will be counted. -*Returns* +_Returns_ -Selection end information. +- `number`: Number of blocks in the post, or number of blocks with name equal to blockName. -### getBlockSelectionStart +<a name="getInserterItems" href="#getInserterItems">#</a> **getInserterItems** -Returns the current block selection start. This value may be null, and it -may represent either a singular block selection or multi-selection start. -A selection is singular if its start and end match. - -*Parameters* +Determines the items that appear in the inserter. Includes both static +items (e.g. a regular block type) and dynamic items (e.g. a reusable block). - * state: Global application state. +Each item object contains what's necessary to display a button in the +inserter and handle its selection. -*Returns* +The 'utility' property indicates how useful we think an item will be to the +user. There are 4 levels of utility: -Client ID of block selection start. +1. Blocks that are contextually useful (utility = 3) +2. Blocks that have been previously inserted (utility = 2) +3. Blocks that are in the common category (utility = 1) +4. All other blocks (utility = 0) -### getBlockSelectionEnd +The 'frecency' property is a heuristic (<https://en.wikipedia.org/wiki/Frecency>) +that combines block usage frequenty and recency. -Returns the current block selection end. This value may be null, and it -may represent either a singular block selection or multi-selection end. -A selection is singular if its start and end match. +Items are returned ordered descendingly by their 'utility' and 'frecency'. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. -*Returns* +_Returns_ -Client ID of block selection end. +- `Array<Editor.InserterItem>`: Items that appear in inserter. -### getSelectedBlockCount +<a name="getLastMultiSelectedBlockClientId" href="#getLastMultiSelectedBlockClientId">#</a> **getLastMultiSelectedBlockClientId** -Returns the number of blocks currently selected in the post. +Returns the client ID of the last block in the multi-selection set, or null +if there is no multi-selection. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Number of blocks selected in the post. +- `?string`: Last block client ID in the multi-selection set. -### hasSelectedBlock +<a name="getMultiSelectedBlockClientIds" href="#getMultiSelectedBlockClientIds">#</a> **getMultiSelectedBlockClientIds** -Returns true if there is a single selected block, or false otherwise. +Returns the current multi-selection set of block client IDs, or an empty +array if there is no multi-selection. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Whether a single block is selected. +- `Array`: Multi-selected block client IDs. -### getSelectedBlockClientId +<a name="getMultiSelectedBlocks" href="#getMultiSelectedBlocks">#</a> **getMultiSelectedBlocks** -Returns the currently selected block client ID, or null if there is no -selected block. +Returns the current multi-selection set of blocks, or an empty array if +there is no multi-selection. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Selected block client ID. +- `Array`: Multi-selected block objects. -### getSelectedBlock +<a name="getMultiSelectedBlocksEndClientId" href="#getMultiSelectedBlocksEndClientId">#</a> **getMultiSelectedBlocksEndClientId** -Returns the currently selected block, or null if there is no selected block. +Returns the client ID of the block which ends the multi-selection set, or +null if there is no multi-selection. -*Parameters* +This is not necessarily the last client ID in the selection. - * state: Global application state. +_Related_ -*Returns* +- getLastMultiSelectedBlockClientId -Selected block. +_Parameters_ -### getBlockRootClientId +- _state_ `Object`: Editor state. -Given a block client ID, returns the root block from which the block is -nested, an empty string for top-level blocks, or null if the block does not -exist. +_Returns_ -*Parameters* +- `?string`: Client ID of block ending multi-selection. - * state: Editor state. - * clientId: Block from which to find root client ID. +<a name="getMultiSelectedBlocksStartClientId" href="#getMultiSelectedBlocksStartClientId">#</a> **getMultiSelectedBlocksStartClientId** -*Returns* +Returns the client ID of the block which begins the multi-selection set, or +null if there is no multi-selection. -Root client ID, if exists +This is not necessarily the first client ID in the selection. -### getBlockHierarchyRootClientId +_Related_ -Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. +- getFirstMultiSelectedBlockClientId -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block from which to find root client ID. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Root client ID +- `?string`: Client ID of block beginning multi-selection. -### getAdjacentBlockClientId +<a name="getNextBlockClientId" href="#getNextBlockClientId">#</a> **getNextBlockClientId** -Returns the client ID of the block adjacent one at the given reference -startClientId and modifier directionality. Defaults start startClientId to -the selected block, and direction as next block. Returns null if there is no -adjacent block. +Returns the next block's client ID from the given reference start ID. +Defaults start to the selected block. Returns null if there is no next +block. -*Parameters* +_Parameters_ - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. - * modifier: Directionality multiplier (1 next, -1 - previous). +- _state_ `Object`: Editor state. +- _startClientId_ `?string`: Optional client ID of block from which to search. -*Returns* +_Returns_ -Return the client ID of the block, or null if none exists. +- `?string`: Adjacent block's client ID, or null if none exists. -### getPreviousBlockClientId +<a name="getPreviousBlockClientId" href="#getPreviousBlockClientId">#</a> **getPreviousBlockClientId** Returns the previous block's client ID from the given reference start ID. Defaults start to the selected block. Returns null if there is no previous block. -*Parameters* +_Parameters_ - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. +- _state_ `Object`: Editor state. +- _startClientId_ `?string`: Optional client ID of block from which to search. -*Returns* +_Returns_ -Adjacent block's client ID, or null if none exists. +- `?string`: Adjacent block's client ID, or null if none exists. -### getNextBlockClientId +<a name="getSelectedBlock" href="#getSelectedBlock">#</a> **getSelectedBlock** -Returns the next block's client ID from the given reference start ID. -Defaults start to the selected block. Returns null if there is no next -block. +Returns the currently selected block, or null if there is no selected block. -*Parameters* +_Parameters_ - * state: Editor state. - * startClientId: Optional client ID of block from which to - search. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Adjacent block's client ID, or null if none exists. +- `?Object`: Selected block. -### getSelectedBlocksInitialCaretPosition +<a name="getSelectedBlockClientId" href="#getSelectedBlockClientId">#</a> **getSelectedBlockClientId** -Returns the initial caret position for the selected block. -This position is to used to position the caret properly when the selected block changes. +Returns the currently selected block client ID, or null if there is no +selected block. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Selected block. +- `?string`: Selected block client ID. -### getSelectedBlockClientIds +<a name="getSelectedBlockClientIds" href="#getSelectedBlockClientIds">#</a> **getSelectedBlockClientIds** Returns the current selection set of block client IDs (multiselection or single selection). -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Multi-selected block client IDs. +- `Array`: Multi-selected block client IDs. -### getMultiSelectedBlockClientIds +<a name="getSelectedBlockCount" href="#getSelectedBlockCount">#</a> **getSelectedBlockCount** -Returns the current multi-selection set of block client IDs, or an empty -array if there is no multi-selection. +Returns the number of blocks currently selected in the post. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Multi-selected block client IDs. +- `number`: Number of blocks selected in the post. -### getMultiSelectedBlocks +<a name="getSelectedBlocksInitialCaretPosition" href="#getSelectedBlocksInitialCaretPosition">#</a> **getSelectedBlocksInitialCaretPosition** -Returns the current multi-selection set of blocks, or an empty array if -there is no multi-selection. +Returns the initial caret position for the selected block. +This position is to used to position the caret properly when the selected block changes. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Multi-selected block objects. +- `?Object`: Selected block. -### getFirstMultiSelectedBlockClientId +<a name="getSelectionEnd" href="#getSelectionEnd">#</a> **getSelectionEnd** -Returns the client ID of the first block in the multi-selection set, or null -if there is no multi-selection. +Returns the current selection end block client ID, attribute key and text +offset. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Block editor state. -*Returns* +_Returns_ -First block client ID in the multi-selection set. +- `WPBlockSelection`: Selection end information. -### getLastMultiSelectedBlockClientId +<a name="getSelectionStart" href="#getSelectionStart">#</a> **getSelectionStart** -Returns the client ID of the last block in the multi-selection set, or null -if there is no multi-selection. +Returns the current selection start block client ID, attribute key and text +offset. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Block editor state. -*Returns* +_Returns_ -Last block client ID in the multi-selection set. +- `WPBlockSelection`: Selection start information. -### isFirstMultiSelectedBlock +<a name="getSettings" href="#getSettings">#</a> **getSettings** -Returns true if a multi-selection exists, and the block corresponding to the -specified client ID is the first block of the multi-selection set, or false -otherwise. +Returns the editor settings. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Whether block is first in multi-selection. +- `Object`: The editor settings object. -### isBlockMultiSelected +<a name="getTemplate" href="#getTemplate">#</a> **getTemplate** -Returns true if the client ID occurs within the block multi-selection, or -false otherwise. +Returns the defined block template -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `boolean`: -*Returns* +_Returns_ -Whether block is in multi-selection set. +- `?Array`: Block Template -### isAncestorMultiSelected +<a name="getTemplateLock" href="#getTemplateLock">#</a> **getTemplateLock** -Returns true if an ancestor of the block is multi-selected, or false -otherwise. +Returns the defined block template lock. Optionally accepts a root block +client ID as context, otherwise defaulting to the global context. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional block root client ID. -*Returns* +_Returns_ -Whether an ancestor of the block is in multi-selection - set. +- `?string`: Block Template Lock -### getMultiSelectedBlocksStartClientId +<a name="hasInserterItems" href="#hasInserterItems">#</a> **hasInserterItems** -Returns the client ID of the block which begins the multi-selection set, or -null if there is no multi-selection. +Determines whether there are items to show in the inserter. -This is not necessarily the first client ID in the selection. +_Parameters_ -*Parameters* +- _state_ `Object`: Editor state. +- _rootClientId_ `?string`: Optional root client ID of block list. - * state: Editor state. +_Returns_ -*Returns* +- `boolean`: Items that appear in inserter. -Client ID of block beginning multi-selection. +<a name="hasMultiSelection" href="#hasMultiSelection">#</a> **hasMultiSelection** -### getMultiSelectedBlocksEndClientId +Returns true if a multi-selection has been made, or false otherwise. -Returns the client ID of the block which ends the multi-selection set, or -null if there is no multi-selection. +_Parameters_ -This is not necessarily the last client ID in the selection. +- _state_ `Object`: Editor state. -*Parameters* +_Returns_ - * state: Editor state. +- `boolean`: Whether multi-selection has been made. -*Returns* +<a name="hasSelectedBlock" href="#hasSelectedBlock">#</a> **hasSelectedBlock** -Client ID of block ending multi-selection. +Returns true if there is a single selected block, or false otherwise. -### getBlockOrder +_Parameters_ -Returns an array containing all block client IDs in the editor in the order -they appear. Optionally accepts a root client ID of the block list for which -the order should be returned, defaulting to the top-level block order. +- _state_ `Object`: Editor state. -*Parameters* +_Returns_ - * state: Editor state. - * rootClientId: Optional root client ID of block list. +- `boolean`: Whether a single block is selected. -*Returns* +<a name="hasSelectedInnerBlock" href="#hasSelectedInnerBlock">#</a> **hasSelectedInnerBlock** -Ordered client IDs of editor blocks. +Returns true if one of the block's inner blocks is selected. -### getBlockIndex +_Parameters_ -Returns the index at which the block corresponding to the specified client -ID occurs within the block order, or `-1` if the block does not exist. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. +- _deep_ `boolean`: Perform a deep check. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. - * rootClientId: Optional root client ID of block list. +- `boolean`: Whether the block as an inner block selected -*Returns* +<a name="isAncestorMultiSelected" href="#isAncestorMultiSelected">#</a> **isAncestorMultiSelected** -Index at which block exists in order. +Returns true if an ancestor of the block is multi-selected, or false +otherwise. -### isBlockSelected +_Parameters_ -Returns true if the block corresponding to the specified client ID is -currently selected and no multi-selection exists, or false otherwise. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. +- `boolean`: Whether an ancestor of the block is in multi-selection set. -*Returns* +<a name="isBlockInsertionPointVisible" href="#isBlockInsertionPointVisible">#</a> **isBlockInsertionPointVisible** -Whether block is selected and multi-selection exists. +Returns true if we should show the block insertion point. -### hasSelectedInnerBlock +_Parameters_ -Returns true if one of the block's inner blocks is selected. +- _state_ `Object`: Global application state. -*Parameters* +_Returns_ - * state: Editor state. - * clientId: Block client ID. - * deep: Perform a deep check. +- `?boolean`: Whether the insertion point is visible or not. -*Returns* +<a name="isBlockMultiSelected" href="#isBlockMultiSelected">#</a> **isBlockMultiSelected** -Whether the block as an inner block selected +Returns true if the client ID occurs within the block multi-selection, or +false otherwise. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. + +_Returns_ + +- `boolean`: Whether block is in multi-selection set. -### isBlockWithinSelection +<a name="isBlockSelected" href="#isBlockSelected">#</a> **isBlockSelected** Returns true if the block corresponding to the specified client ID is -currently selected but isn't the last of the selected blocks. Here "last" -refers to the block sequence in the document, _not_ the sequence of -multi-selection, which is why `state.blockSelection.end` isn't used. +currently selected and no multi-selection exists, or false otherwise. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Whether block is selected and not the last in the - selection. +- `boolean`: Whether block is selected and multi-selection exists. -### hasMultiSelection +<a name="isBlockValid" href="#isBlockValid">#</a> **isBlockValid** -Returns true if a multi-selection has been made, or false otherwise. +Returns whether a block is valid or not. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Whether multi-selection has been made. +- `boolean`: Is Valid. -### isMultiSelecting +<a name="isBlockWithinSelection" href="#isBlockWithinSelection">#</a> **isBlockWithinSelection** -Whether in the process of multi-selecting or not. This flag is only true -while the multi-selection is being selected (by mouse move), and is false -once the multi-selection has been settled. +Returns true if the block corresponding to the specified client ID is +currently selected but isn't the last of the selected blocks. Here "last" +refers to the block sequence in the document, _not_ the sequence of +multi-selection, which is why `state.blockSelection.end` isn't used. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -True if multi-selecting, false if not. +- `boolean`: Whether block is selected and not the last in the selection. -### isSelectionEnabled +<a name="isCaretWithinFormattedText" href="#isCaretWithinFormattedText">#</a> **isCaretWithinFormattedText** -Selector that returns if multi-selection is enabled or not. +Returns true if the caret is within formatted text, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -True if it should be possible to multi-select blocks, false if multi-selection is disabled. +- `boolean`: Whether the caret is within formatted text. -### getBlockMode +<a name="isFirstMultiSelectedBlock" href="#isFirstMultiSelectedBlock">#</a> **isFirstMultiSelectedBlock** -Returns the block's editing mode, defaulting to "visual" if not explicitly -assigned. +Returns true if a multi-selection exists, and the block corresponding to the +specified client ID is the first block of the multi-selection set, or false +otherwise. -*Parameters* +_Parameters_ - * state: Editor state. - * clientId: Block client ID. +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block client ID. -*Returns* +_Returns_ -Block editing mode. +- `boolean`: Whether block is first in multi-selection. -### isTyping +<a name="isLastBlockChangePersistent" href="#isLastBlockChangePersistent">#</a> **isLastBlockChangePersistent** -Returns true if the user is typing, or false otherwise. +Returns true if the most recent block change is be considered persistent, or +false otherwise. A persistent change is one committed by BlockEditorProvider +via its `onChange` callback, in addition to `onInput`. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Block editor state. -*Returns* +_Returns_ -Whether user is typing. +- `boolean`: Whether the most recent block change was persistent. -### isCaretWithinFormattedText +<a name="isMultiSelecting" href="#isMultiSelecting">#</a> **isMultiSelecting** -Returns true if the caret is within formatted text, or false otherwise. +Whether in the process of multi-selecting or not. This flag is only true +while the multi-selection is being selected (by mouse move), and is false +once the multi-selection has been settled. -*Parameters* +_Related_ - * state: Global application state. +- hasMultiSelection -*Returns* +_Parameters_ -Whether the caret is within formatted text. +- _state_ `Object`: Global application state. -### getBlockInsertionPoint +_Returns_ -Returns the insertion point, the index at which the new inserted block would -be placed. Defaults to the last index. +- `boolean`: True if multi-selecting, false if not. -*Parameters* +<a name="isSelectionEnabled" href="#isSelectionEnabled">#</a> **isSelectionEnabled** - * state: Editor state. +Selector that returns if multi-selection is enabled or not. -*Returns* +_Parameters_ -Insertion point object with `rootClientId`, `index`. +- _state_ `Object`: Global application state. -### isBlockInsertionPointVisible +_Returns_ -Returns true if we should show the block insertion point. +- `boolean`: True if it should be possible to multi-select blocks, false if multi-selection is disabled. + +<a name="isTyping" href="#isTyping">#</a> **isTyping** + +Returns true if the user is typing, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the insertion point is visible or not. +- `boolean`: Whether user is typing. -### isValidTemplate +<a name="isValidTemplate" href="#isValidTemplate">#</a> **isValidTemplate** Returns whether the blocks matches the template or not. -*Parameters* +_Parameters_ - * state: null +- _state_ `boolean`: -*Returns* +_Returns_ -Whether the template is valid or not. +- `?boolean`: Whether the template is valid or not. -### getTemplate -Returns the defined block template +<!-- END TOKEN(Autogenerated selectors) --> -*Parameters* +## Actions - * state: null +<!-- START TOKEN(Autogenerated actions) --> -*Returns* +<a name="clearSelectedBlock" href="#clearSelectedBlock">#</a> **clearSelectedBlock** -Block Template +Returns an action object used in signalling that the block selection is cleared. -### getTemplateLock +_Returns_ -Returns the defined block template lock. Optionally accepts a root block -client ID as context, otherwise defaulting to the global context. +- `Object`: Action object. -*Parameters* +<a name="enterFormattedText" href="#enterFormattedText">#</a> **enterFormattedText** - * state: Editor state. - * rootClientId: Optional block root client ID. +Returns an action object used in signalling that the caret has entered formatted text. -*Returns* +_Returns_ -Block Template Lock +- `Object`: Action object. -### canInsertBlockType +<a name="exitFormattedText" href="#exitFormattedText">#</a> **exitFormattedText** -Determines if the given block type is allowed to be inserted into the block list. +Returns an action object used in signalling that the user caret has exited formatted text. -*Parameters* +_Returns_ - * state: Editor state. - * blockName: The name of the block type, e.g.' core/paragraph'. - * rootClientId: Optional root client ID of block list. +- `Object`: Action object. -*Returns* +<a name="hideInsertionPoint" href="#hideInsertionPoint">#</a> **hideInsertionPoint** -Whether the given block type is allowed to be inserted. +Returns an action object hiding the insertion point. -### getInserterItems +_Returns_ -Determines the items that appear in the inserter. Includes both static -items (e.g. a regular block type) and dynamic items (e.g. a reusable block). +- `Object`: Action object. -Each item object contains what's necessary to display a button in the -inserter and handle its selection. +<a name="insertBlock" href="#insertBlock">#</a> **insertBlock** -The 'utility' property indicates how useful we think an item will be to the -user. There are 4 levels of utility: +Returns an action object used in signalling that a single block should be +inserted, optionally at a specific index respective a root block list. -1. Blocks that are contextually useful (utility = 3) -2. Blocks that have been previously inserted (utility = 2) -3. Blocks that are in the common category (utility = 1) -4. All other blocks (utility = 0) +_Parameters_ -The 'frecency' property is a heuristic (https://en.wikipedia.org/wiki/Frecency) -that combines block usage frequenty and recency. +- _block_ `Object`: Block object to insert. +- _index_ `?number`: Index at which block should be inserted. +- _rootClientId_ `?string`: Optional root client ID of block list on which to insert. +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. -Items are returned ordered descendingly by their 'utility' and 'frecency'. +_Returns_ -*Parameters* +- `Object`: Action object. - * state: Editor state. - * rootClientId: Optional root client ID of block list. +<a name="insertBlocks" href="#insertBlocks">#</a> **insertBlocks** -*Returns* +Returns an action object used in signalling that an array of blocks should +be inserted, optionally at a specific index respective a root block list. -Items that appear in inserter. +_Parameters_ -### hasInserterItems +- _blocks_ `Array<Object>`: Block objects to insert. +- _index_ `?number`: Index at which block should be inserted. +- _rootClientId_ `?string`: Optional root client ID of block list on which to insert. +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. -Determines whether there are items to show in the inserter. +_Returns_ -*Parameters* +- `Object`: Action object. - * state: Editor state. - * rootClientId: Optional root client ID of block list. +<a name="insertDefaultBlock" href="#insertDefaultBlock">#</a> **insertDefaultBlock** -*Returns* +Returns an action object used in signalling that a new block of the default +type should be added to the block list. -Items that appear in inserter. +_Parameters_ -### getBlockListSettings +- _attributes_ `?Object`: Optional attributes of the block to assign. +- _rootClientId_ `?string`: Optional root client ID of block list on which to append. +- _index_ `?number`: Optional index where to insert the default block -Returns the Block List settings of a block, if any exist. +_Returns_ -*Parameters* +- `Object`: Action object - * state: Editor state. - * clientId: Block client ID. +<a name="mergeBlocks" href="#mergeBlocks">#</a> **mergeBlocks** -*Returns* +Returns an action object used in signalling that two blocks should be merged -Block settings of the block if set. +_Parameters_ -### getSettings +- _firstBlockClientId_ `string`: Client ID of the first block to merge. +- _secondBlockClientId_ `string`: Client ID of the second block to merge. -Returns the editor settings. +_Returns_ -*Parameters* +- `Object`: Action object. - * state: Editor state. +<a name="moveBlocksDown" href="#moveBlocksDown">#</a> **moveBlocksDown** -*Returns* +Undocumented declaration. -The editor settings object. +<a name="moveBlocksUp" href="#moveBlocksUp">#</a> **moveBlocksUp** -### isLastBlockChangePersistent +Undocumented declaration. -Returns true if the most recent block change is be considered persistent, or -false otherwise. A persistent change is one committed by BlockEditorProvider -via its `onChange` callback, in addition to `onInput`. +<a name="moveBlockToPosition" href="#moveBlockToPosition">#</a> **moveBlockToPosition** -*Parameters* +Returns an action object signalling that an indexed block should be moved +to a new index. - * state: Block editor state. +_Parameters_ -*Returns* +- _clientId_ `?string`: The client ID of the block. +- _fromRootClientId_ `?string`: Root client ID source. +- _toRootClientId_ `?string`: Root client ID destination. +- _index_ `number`: The index to move the block into. -Whether the most recent block change was persistent. +<a name="multiSelect" href="#multiSelect">#</a> **multiSelect** -## Actions +Returns an action object used in signalling that block multi-selection changed. -### resetBlocks +_Parameters_ -Returns an action object used in signalling that blocks state should be -reset to the specified array of blocks, taking precedence over any other -content reflected as an edit in state. +- _start_ `string`: First block of the multi selection. +- _end_ `string`: Last block of the multiselection. -*Parameters* +_Returns_ - * blocks: Array of blocks. +- `Object`: Action object. -### receiveBlocks +<a name="receiveBlocks" href="#receiveBlocks">#</a> **receiveBlocks** Returns an action object used in signalling that blocks have been received. Unlike resetBlocks, these should be appended to the existing known set, not replacing. -*Parameters* +_Parameters_ - * blocks: Array of block objects. +- _blocks_ `Array<Object>`: Array of block objects. -### updateBlockAttributes +_Returns_ -Returns an action object used in signalling that the block attributes with -the specified client ID has been updated. +- `Object`: Action object. -*Parameters* +<a name="removeBlock" href="#removeBlock">#</a> **removeBlock** - * clientId: Block client ID. - * attributes: Block attributes to be merged. +Returns an action object used in signalling that the block with the +specified client ID is to be removed. -### updateBlock +_Parameters_ -Returns an action object used in signalling that the block with the -specified client ID has been updated. +- _clientId_ `string`: Client ID of block to remove. +- _selectPrevious_ `boolean`: True if the previous block should be selected when a block is removed. -*Parameters* +_Returns_ - * clientId: Block client ID. - * updates: Block attributes to be merged. +- `Object`: Action object. -### selectBlock +<a name="removeBlocks" href="#removeBlocks">#</a> **removeBlocks** -Returns an action object used in signalling that the block with the -specified client ID has been selected, optionally accepting a position -value reflecting its selection directionality. An initialPosition of -1 -reflects a reverse selection. +Yields action objects used in signalling that the blocks corresponding to +the set of specified client IDs are to be removed. -*Parameters* +_Parameters_ - * clientId: Block client ID. - * initialPosition: Optional initial position. Pass as -1 to - reflect reverse selection. +- _clientIds_ `(string|Array<string>)`: Client IDs of blocks to remove. +- _selectPrevious_ `boolean`: True if the previous block should be selected when a block is removed. -### selectPreviousBlock +<a name="replaceBlock" href="#replaceBlock">#</a> **replaceBlock** -Yields action objects used in signalling that the block preceding the given -clientId should be selected. +Returns an action object signalling that a single block should be replaced +with one or more replacement blocks. -*Parameters* +_Parameters_ - * clientId: Block client ID. +- _clientId_ `(string|Array<string>)`: Block client ID to replace. +- _block_ `(Object|Array<Object>)`: Replacement block(s). -### selectNextBlock +_Returns_ -Yields action objects used in signalling that the block following the given -clientId should be selected. +- `Object`: Action object. -*Parameters* +<a name="replaceBlocks" href="#replaceBlocks">#</a> **replaceBlocks** - * clientId: Block client ID. +Returns an action object signalling that a blocks should be replaced with +one or more replacement blocks. -### startMultiSelect +_Parameters_ -Returns an action object used in signalling that a block multi-selection has started. +- _clientIds_ `(string|Array<string>)`: Block client ID(s) to replace. +- _blocks_ `(Object|Array<Object>)`: Replacement block(s). -### stopMultiSelect +<a name="replaceInnerBlocks" href="#replaceInnerBlocks">#</a> **replaceInnerBlocks** -Returns an action object used in signalling that block multi-selection stopped. +Returns an action object used in signalling that the inner blocks with the +specified client ID should be replaced. -### multiSelect +_Parameters_ -Returns an action object used in signalling that block multi-selection changed. +- _rootClientId_ `string`: Client ID of the block whose InnerBlocks will re replaced. +- _blocks_ `Array<Object>`: Block objects to insert as new InnerBlocks +- _updateSelection_ `?boolean`: If true block selection will be updated. If false, block selection will not change. Defaults to true. -*Parameters* +_Returns_ - * start: First block of the multi selection. - * end: Last block of the multiselection. +- `Object`: Action object. -### clearSelectedBlock +<a name="resetBlocks" href="#resetBlocks">#</a> **resetBlocks** -Returns an action object used in signalling that the block selection is cleared. +Returns an action object used in signalling that blocks state should be +reset to the specified array of blocks, taking precedence over any other +content reflected as an edit in state. -### toggleSelection +_Parameters_ -Returns an action object that enables or disables block selection. +- _blocks_ `Array`: Array of blocks. -*Parameters* +_Returns_ - * boolean: [isSelectionEnabled=true] Whether block selection should - be enabled. +- `Object`: Action object. -### replaceBlocks +<a name="selectBlock" href="#selectBlock">#</a> **selectBlock** -Returns an action object signalling that a blocks should be replaced with -one or more replacement blocks. +Returns an action object used in signalling that the block with the +specified client ID has been selected, optionally accepting a position +value reflecting its selection directionality. An initialPosition of -1 +reflects a reverse selection. -*Parameters* +_Parameters_ - * clientIds: Block client ID(s) to replace. - * blocks: Replacement block(s). +- _clientId_ `string`: Block client ID. +- _initialPosition_ `?number`: Optional initial position. Pass as -1 to reflect reverse selection. -### replaceBlock +_Returns_ -Returns an action object signalling that a single block should be replaced -with one or more replacement blocks. +- `Object`: Action object. -*Parameters* +<a name="selectionChange" href="#selectionChange">#</a> **selectionChange** - * clientId: Block client ID to replace. - * block: Replacement block(s). +Returns an action object used in signalling that the user caret has changed +position. -### moveBlockToPosition +_Parameters_ -Returns an action object signalling that an indexed block should be moved -to a new index. +- _clientId_ `string`: The selected block client ID. +- _attributeKey_ `string`: The selected block attribute key. +- _startOffset_ `number`: The start offset. +- _endOffset_ `number`: The end offset. -*Parameters* +_Returns_ - * clientId: The client ID of the block. - * fromRootClientId: Root client ID source. - * toRootClientId: Root client ID destination. - * index: The index to move the block into. +- `Object`: Action object. -### insertBlock +<a name="selectNextBlock" href="#selectNextBlock">#</a> **selectNextBlock** -Returns an action object used in signalling that a single block should be -inserted, optionally at a specific index respective a root block list. +Yields action objects used in signalling that the block following the given +clientId should be selected. -*Parameters* +_Parameters_ - * block: Block object to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _clientId_ `string`: Block client ID. -### insertBlocks +<a name="selectPreviousBlock" href="#selectPreviousBlock">#</a> **selectPreviousBlock** -Returns an action object used in signalling that an array of blocks should -be inserted, optionally at a specific index respective a root block list. +Yields action objects used in signalling that the block preceding the given +clientId should be selected. -*Parameters* +_Parameters_ - * blocks: Block objects to insert. - * index: Index at which block should be inserted. - * rootClientId: Optional root client ID of block list on which to insert. - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- _clientId_ `string`: Block client ID. -### showInsertionPoint +<a name="setTemplateValidity" href="#setTemplateValidity">#</a> **setTemplateValidity** -Returns an action object used in signalling that the insertion point should -be shown. +Returns an action object resetting the template validity. -*Parameters* +_Parameters_ - * rootClientId: Optional root client ID of block list on - which to insert. - * index: Index at which block should be inserted. +- _isValid_ `boolean`: template validity flag. -### hideInsertionPoint +_Returns_ -Returns an action object hiding the insertion point. +- `Object`: Action object. -### setTemplateValidity +<a name="showInsertionPoint" href="#showInsertionPoint">#</a> **showInsertionPoint** -Returns an action object resetting the template validity. +Returns an action object used in signalling that the insertion point should +be shown. -*Parameters* +_Parameters_ - * isValid: template validity flag. +- _rootClientId_ `?string`: Optional root client ID of block list on which to insert. +- _index_ `?number`: Index at which block should be inserted. -### synchronizeTemplate +_Returns_ -Returns an action object synchronize the template with the list of blocks +- `Object`: Action object. -### mergeBlocks +<a name="startMultiSelect" href="#startMultiSelect">#</a> **startMultiSelect** -Returns an action object used in signalling that two blocks should be merged +Returns an action object used in signalling that a block multi-selection has started. -*Parameters* +_Returns_ - * firstBlockClientId: Client ID of the first block to merge. - * secondBlockClientId: Client ID of the second block to merge. +- `Object`: Action object. -### removeBlocks +<a name="startTyping" href="#startTyping">#</a> **startTyping** -Yields action objects used in signalling that the blocks corresponding to -the set of specified client IDs are to be removed. +Returns an action object used in signalling that the user has begun to type. -*Parameters* +_Returns_ - * clientIds: Client IDs of blocks to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. +- `Object`: Action object. -### removeBlock +<a name="stopMultiSelect" href="#stopMultiSelect">#</a> **stopMultiSelect** -Returns an action object used in signalling that the block with the -specified client ID is to be removed. +Returns an action object used in signalling that block multi-selection stopped. -*Parameters* +_Returns_ - * clientId: Client ID of block to remove. - * selectPrevious: True if the previous block should be - selected when a block is removed. +- `Object`: Action object. -### replaceInnerBlocks +<a name="stopTyping" href="#stopTyping">#</a> **stopTyping** -Returns an action object used in signalling that the inner blocks with the -specified client ID should be replaced. +Returns an action object used in signalling that the user has stopped typing. + +_Returns_ + +- `Object`: Action object. + +<a name="synchronizeTemplate" href="#synchronizeTemplate">#</a> **synchronizeTemplate** + +Returns an action object synchronize the template with the list of blocks -*Parameters* +_Returns_ - * rootClientId: Client ID of the block whose InnerBlocks will re replaced. - * blocks: Block objects to insert as new InnerBlocks - * updateSelection: If true block selection will be updated. If false, block selection will not change. Defaults to true. +- `Object`: Action object. -### toggleBlockMode +<a name="toggleBlockMode" href="#toggleBlockMode">#</a> **toggleBlockMode** Returns an action object used to toggle the block editing mode between visual and HTML modes. -*Parameters* +_Parameters_ - * clientId: Block client ID. +- _clientId_ `string`: Block client ID. -### startTyping +_Returns_ -Returns an action object used in signalling that the user has begun to type. +- `Object`: Action object. -### stopTyping +<a name="toggleSelection" href="#toggleSelection">#</a> **toggleSelection** -Returns an action object used in signalling that the user has stopped typing. +Returns an action object that enables or disables block selection. -### enterFormattedText +_Parameters_ -Returns an action object used in signalling that the caret has entered formatted text. +- _isSelectionEnabled_ `[boolean]`: Whether block selection should be enabled. -### exitFormattedText +_Returns_ -Returns an action object used in signalling that the user caret has exited formatted text. +- `Object`: Action object. -### selectionChange +<a name="updateBlock" href="#updateBlock">#</a> **updateBlock** -Returns an action object used in signalling that the user caret has changed -position. +Returns an action object used in signalling that the block with the +specified client ID has been updated. -*Parameters* +_Parameters_ - * clientId: The selected block client ID. - * attributeKey: The selected block attribute key. - * startOffset: The start offset. - * endOffset: The end offset. +- _clientId_ `string`: Block client ID. +- _updates_ `Object`: Block attributes to be merged. -### insertDefaultBlock +_Returns_ -Returns an action object used in signalling that a new block of the default -type should be added to the block list. +- `Object`: Action object. + +<a name="updateBlockAttributes" href="#updateBlockAttributes">#</a> **updateBlockAttributes** -*Parameters* +Returns an action object used in signalling that the block attributes with +the specified client ID has been updated. + +_Parameters_ - * attributes: Optional attributes of the block to assign. - * rootClientId: Optional root client ID of block list on which - to append. - * index: Optional index where to insert the default block +- _clientId_ `string`: Block client ID. +- _attributes_ `Object`: Block attributes to be merged. -### updateBlockListSettings +_Returns_ + +- `Object`: Action object. + +<a name="updateBlockListSettings" href="#updateBlockListSettings">#</a> **updateBlockListSettings** Returns an action object that changes the nested settings of a given block. -*Parameters* +_Parameters_ + +- _clientId_ `string`: Client ID of the block whose nested setting are being received. +- _settings_ `Object`: Object with the new settings for the nested block. - * clientId: Client ID of the block whose nested setting are - being received. - * settings: Object with the new settings for the nested block. +_Returns_ -### updateSettings +- `Object`: Action object -Returns an action object used in signalling that the block editor settings have been updated. +<a name="updateSettings" href="#updateSettings">#</a> **updateSettings** -*Parameters* +Undocumented declaration. - * settings: Updated settings \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index c7a63e769f350f..5256587043602d 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -1,251 +1,299 @@ -# **core/blocks**: Block Types Data +# Block Types Data + +Namespace: `core/blocks`. ## Selectors -### getBlockTypes +<!-- START TOKEN(Autogenerated selectors) --> -Returns all the available block types. +<a name="getBlockStyles" href="#getBlockStyles">#</a> **getBlockStyles** + +Returns block styles by block name. + +_Parameters_ + +- _state_ `Object`: Data state. +- _name_ `string`: Block type name. + +_Returns_ + +- `?Array`: Block Styles. + +<a name="getBlockSupport" href="#getBlockSupport">#</a> **getBlockSupport** + +Returns the block support value for a feature, if defined. + +_Parameters_ + +- _state_ `Object`: Data state. +- _nameOrType_ `(string|Object)`: Block name or type object +- _feature_ `string`: Feature to retrieve +- _defaultSupports_ `*`: Default value to return if not explicitly defined -*Parameters* +_Returns_ - * state: Data state. +- `?*`: Block support value -### getBlockType +<a name="getBlockType" href="#getBlockType">#</a> **getBlockType** Returns a block type by name. -*Parameters* +_Parameters_ - * state: Data state. - * name: Block type name. +- _state_ `Object`: Data state. +- _name_ `string`: Block type name. -*Returns* +_Returns_ -Block Type. +- `?Object`: Block Type. -### getBlockStyles +<a name="getBlockTypes" href="#getBlockTypes">#</a> **getBlockTypes** -Returns block styles by block name. +Returns all the available block types. -*Parameters* +_Parameters_ - * state: Data state. - * name: Block type name. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Block Styles. +- `Array`: Block Types. -### getCategories +<a name="getCategories" href="#getCategories">#</a> **getCategories** Returns all the available categories. -*Parameters* +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ - * state: Data state. +- `Array`: Categories list. + +<a name="getChildBlockNames" href="#getChildBlockNames">#</a> **getChildBlockNames** + +Returns an array with the child blocks of a given block. -*Returns* +_Parameters_ -Categories list. +- _state_ `Object`: Data state. +- _blockName_ `string`: Block type name. -### getDefaultBlockName +_Returns_ + +- `Array`: Array of child block names. + +<a name="getDefaultBlockName" href="#getDefaultBlockName">#</a> **getDefaultBlockName** Returns the name of the default block name. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Default block name. +- `?string`: Default block name. -### getFreeformFallbackBlockName +<a name="getFreeformFallbackBlockName" href="#getFreeformFallbackBlockName">#</a> **getFreeformFallbackBlockName** Returns the name of the block for handling non-block content. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Name of the block for handling non-block content. +- `?string`: Name of the block for handling non-block content. -### getUnregisteredFallbackBlockName +<a name="getUnregisteredFallbackBlockName" href="#getUnregisteredFallbackBlockName">#</a> **getUnregisteredFallbackBlockName** Returns the name of the block for handling unregistered blocks. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Name of the block for handling unregistered blocks. +- `?string`: Name of the block for handling unregistered blocks. -### getChildBlockNames +<a name="hasBlockSupport" href="#hasBlockSupport">#</a> **hasBlockSupport** -Returns an array with the child blocks of a given block. +Returns true if the block defines support for a feature, or false otherwise. -*Parameters* +_Parameters_ - * state: Data state. - * blockName: Block type name. +- _state_ `Object`: Data state. +- _nameOrType_ `(string|Object)`: Block name or type object. +- _feature_ `string`: Feature to test. +- _defaultSupports_ `boolean`: Whether feature is supported by default if not explicitly defined. -*Returns* +_Returns_ -Array of child block names. +- `boolean`: Whether block supports feature. -### getBlockSupport +<a name="hasChildBlocks" href="#hasChildBlocks">#</a> **hasChildBlocks** -Returns the block support value for a feature, if defined. +Returns a boolean indicating if a block has child blocks or not. -*Parameters* +_Parameters_ - * state: Data state. - * nameOrType: Block name or type object - * feature: Feature to retrieve - * defaultSupports: Default value to return if not - explicitly defined +- _state_ `Object`: Data state. +- _blockName_ `string`: Block type name. -*Returns* +_Returns_ -Block support value +- `boolean`: True if a block contains child blocks and false otherwise. -### hasBlockSupport +<a name="hasChildBlocksWithInserterSupport" href="#hasChildBlocksWithInserterSupport">#</a> **hasChildBlocksWithInserterSupport** -Returns true if the block defines support for a feature, or false otherwise. +Returns a boolean indicating if a block has at least one child block with inserter support. -*Parameters* +_Parameters_ - * state: Data state. - * nameOrType: Block name or type object. - * feature: Feature to test. - * defaultSupports: Whether feature is supported by - default if not explicitly defined. +- _state_ `Object`: Data state. +- _blockName_ `string`: Block type name. -*Returns* +_Returns_ -Whether block supports feature. +- `boolean`: True if a block contains at least one child blocks with inserter support and false otherwise. -### isMatchingSearchTerm +<a name="isMatchingSearchTerm" href="#isMatchingSearchTerm">#</a> **isMatchingSearchTerm** Returns true if the block type by the given name or object value matches a search term, or false otherwise. -*Parameters* +_Parameters_ - * state: Blocks state. - * nameOrType: Block name or type object. - * searchTerm: Search term by which to filter. +- _state_ `Object`: Blocks state. +- _nameOrType_ `(string|Object)`: Block name or type object. +- _searchTerm_ `string`: Search term by which to filter. -*Returns* +_Returns_ -Wheter block type matches search term. +- `Array<Object>`: Wheter block type matches search term. -### hasChildBlocks -Returns a boolean indicating if a block has child blocks or not. +<!-- END TOKEN(Autogenerated selectors) --> -*Parameters* +## Actions - * state: Data state. - * blockName: Block type name. +<!-- START TOKEN(Autogenerated actions) --> -*Returns* +<a name="addBlockStyles" href="#addBlockStyles">#</a> **addBlockStyles** -True if a block contains child blocks and false otherwise. +Returns an action object used in signalling that new block styles have been added. -### hasChildBlocksWithInserterSupport +_Parameters_ -Returns a boolean indicating if a block has at least one child block with inserter support. +- _blockName_ `string`: Block name. +- _styles_ `(Array|Object)`: Block styles. -*Parameters* +_Returns_ - * state: Data state. - * blockName: Block type name. +- `Object`: Action object. -*Returns* +<a name="addBlockTypes" href="#addBlockTypes">#</a> **addBlockTypes** -True if a block contains at least one child blocks with inserter support - and false otherwise. +Returns an action object used in signalling that block types have been added. -## Actions +_Parameters_ -### addBlockTypes +- _blockTypes_ `(Array|Object)`: Block types received. -Returns an action object used in signalling that block types have been added. +_Returns_ + +- `Object`: Action object. + +<a name="removeBlockStyles" href="#removeBlockStyles">#</a> **removeBlockStyles** + +Returns an action object used in signalling that block styles have been removed. -*Parameters* +_Parameters_ - * blockTypes: Block types received. +- _blockName_ `string`: Block name. +- _styleNames_ `(Array|string)`: Block style names. -### removeBlockTypes +_Returns_ + +- `Object`: Action object. + +<a name="removeBlockTypes" href="#removeBlockTypes">#</a> **removeBlockTypes** Returns an action object used to remove a registered block type. -*Parameters* +_Parameters_ - * names: Block name. +- _names_ `(string|Array)`: Block name. -### addBlockStyles +_Returns_ -Returns an action object used in signalling that new block styles have been added. +- `Object`: Action object. -*Parameters* +<a name="setCategories" href="#setCategories">#</a> **setCategories** - * blockName: Block name. - * styles: Block styles. +Returns an action object used to set block categories. -### removeBlockStyles +_Parameters_ -Returns an action object used in signalling that block styles have been removed. +- _categories_ `Array<Object>`: Block categories. -*Parameters* +_Returns_ - * blockName: Block name. - * styleNames: Block style names. +- `Object`: Action object. -### setDefaultBlockName +<a name="setDefaultBlockName" href="#setDefaultBlockName">#</a> **setDefaultBlockName** Returns an action object used to set the default block name. -*Parameters* +_Parameters_ + +- _name_ `string`: Block name. + +_Returns_ - * name: Block name. +- `Object`: Action object. -### setFreeformFallbackBlockName +<a name="setFreeformFallbackBlockName" href="#setFreeformFallbackBlockName">#</a> **setFreeformFallbackBlockName** Returns an action object used to set the name of the block used as a fallback for non-block content. -*Parameters* +_Parameters_ - * name: Block name. +- _name_ `string`: Block name. -### setUnregisteredFallbackBlockName +_Returns_ + +- `Object`: Action object. + +<a name="setUnregisteredFallbackBlockName" href="#setUnregisteredFallbackBlockName">#</a> **setUnregisteredFallbackBlockName** Returns an action object used to set the name of the block used as a fallback for unregistered blocks. -*Parameters* +_Parameters_ - * name: Block name. +- _name_ `string`: Block name. -### setCategories +_Returns_ -Returns an action object used to set block categories. +- `Object`: Action object. -*Parameters* +<a name="updateCategory" href="#updateCategory">#</a> **updateCategory** - * categories: Block categories. +Returns an action object used to update a category. -### updateCategory +_Parameters_ -Returns an action object used to update a category. +- _slug_ `string`: Block category slug. +- _category_ `Object`: Object containing the category properties that should be updated. + +_Returns_ -*Parameters* +- `Object`: Action object. - * slug: Block category slug. - * category: Object containing the category properties that should be updated. \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index f36cf5bc47068f..00e60869dbb875 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -1,375 +1,462 @@ -# **core/edit-post**: The Editor’s UI Data +# The Editor’s UI Data + +Namespace: `core/edit-post`. ## Selectors -### getEditorMode +<!-- START TOKEN(Autogenerated selectors) --> -Returns the current editing mode. +<a name="getActiveGeneralSidebarName" href="#getActiveGeneralSidebarName">#</a> **getActiveGeneralSidebarName** -*Parameters* +Returns the current active general sidebar name, or null if there is no +general sidebar active. The active general sidebar is a unique name to +identify either an editor or plugin sidebar. - * state: Global application state. +Examples: -### isEditorSidebarOpened +- `edit-post/document` +- `my-plugin/insert-image-sidebar` -Returns true if the editor sidebar is opened. +_Parameters_ -*Parameters* +- _state_ `Object`: Global application state. - * state: Global application state +_Returns_ -*Returns* +- `?string`: Active general sidebar name. -Whether the editor sidebar is opened. +<a name="getActiveMetaBoxLocations" href="#getActiveMetaBoxLocations">#</a> **getActiveMetaBoxLocations** -### isPluginSidebarOpened +Returns an array of active meta box locations. -Returns true if the plugin sidebar is opened. +_Parameters_ -*Parameters* +- _state_ `Object`: Post editor state. - * state: Global application state +_Returns_ -*Returns* +- `Array<string>`: Active meta box locations. -Whether the plugin sidebar is opened. +<a name="getAllMetaBoxes" href="#getAllMetaBoxes">#</a> **getAllMetaBoxes** -### getActiveGeneralSidebarName +Returns the list of all the available meta boxes. -Returns the current active general sidebar name, or null if there is no -general sidebar active. The active general sidebar is a unique name to -identify either an editor or plugin sidebar. +_Parameters_ -Examples: +- _state_ `Object`: Global application state. + +_Returns_ - - `edit-post/document` - - `my-plugin/insert-image-sidebar` +- `Array`: List of meta boxes. -*Parameters* +<a name="getEditorMode" href="#getEditorMode">#</a> **getEditorMode** - * state: Global application state. +Returns the current editing mode. -*Returns* +_Parameters_ -Active general sidebar name. +- _state_ `Object`: Global application state. -### getPreferences +_Returns_ -Returns the preferences (these preferences are persisted locally). +- `string`: Editing mode. -*Parameters* +<a name="getMetaBoxesPerLocation" href="#getMetaBoxesPerLocation">#</a> **getMetaBoxesPerLocation** - * state: Global application state. +Returns the list of all the available meta boxes for a given location. -*Returns* +_Parameters_ -Preferences Object. +- _state_ `Object`: Global application state. +- _location_ `string`: Meta box location to test. -### getPreference +_Returns_ -*Parameters* +- `?Array`: List of meta boxes. - * state: Global application state. - * preferenceKey: Preference Key. - * defaultValue: Default Value. +<a name="getPreference" href="#getPreference">#</a> **getPreference** -*Returns* +_Parameters_ -Preference Value. +- _state_ `Object`: Global application state. +- _preferenceKey_ `string`: Preference Key. +- _defaultValue_ `Mixed`: Default Value. -### isPublishSidebarOpened +_Returns_ -Returns true if the publish sidebar is opened. +- `Mixed`: Preference Value. -*Parameters* +<a name="getPreferences" href="#getPreferences">#</a> **getPreferences** - * state: Global application state +Returns the preferences (these preferences are persisted locally). -*Returns* +_Parameters_ -Whether the publish sidebar is open. +- _state_ `Object`: Global application state. -### isEditorPanelRemoved +_Returns_ -Returns true if the given panel was programmatically removed, or false otherwise. -All panels are not removed by default. +- `Object`: Preferences Object. -*Parameters* +<a name="hasMetaBoxes" href="#hasMetaBoxes">#</a> **hasMetaBoxes** - * state: Global application state. - * panelName: A string that identifies the panel. +Returns true if the post is using Meta Boxes -*Returns* +_Parameters_ -Whether or not the panel is removed. +- _state_ `Object`: Global application state -### isEditorPanelEnabled +_Returns_ + +- `boolean`: Whether there are metaboxes or not. + +<a name="isEditorPanelEnabled" href="#isEditorPanelEnabled">#</a> **isEditorPanelEnabled** Returns true if the given panel is enabled, or false otherwise. Panels are enabled by default. -*Parameters* +_Parameters_ - * state: Global application state. - * panelName: A string that identifies the panel. +- _state_ `Object`: Global application state. +- _panelName_ `string`: A string that identifies the panel. -*Returns* +_Returns_ -Whether or not the panel is enabled. +- `boolean`: Whether or not the panel is enabled. -### isEditorPanelOpened +<a name="isEditorPanelOpened" href="#isEditorPanelOpened">#</a> **isEditorPanelOpened** Returns true if the given panel is open, or false otherwise. Panels are closed by default. -*Parameters* +_Parameters_ - * state: Global application state. - * panelName: A string that identifies the panel. +- _state_ `Object`: Global application state. +- _panelName_ `string`: A string that identifies the panel. -*Returns* +_Returns_ -Whether or not the panel is open. +- `boolean`: Whether or not the panel is open. -### isModalActive +<a name="isEditorPanelRemoved" href="#isEditorPanelRemoved">#</a> **isEditorPanelRemoved** -Returns true if a modal is active, or false otherwise. +Returns true if the given panel was programmatically removed, or false otherwise. +All panels are not removed by default. -*Parameters* +_Parameters_ - * state: Global application state. - * modalName: A string that uniquely identifies the modal. +- _state_ `Object`: Global application state. +- _panelName_ `string`: A string that identifies the panel. -*Returns* +_Returns_ -Whether the modal is active. +- `boolean`: Whether or not the panel is removed. -### isFeatureActive +<a name="isEditorSidebarOpened" href="#isEditorSidebarOpened">#</a> **isEditorSidebarOpened** -Returns whether the given feature is enabled or not. +Returns true if the editor sidebar is opened. -*Parameters* +_Parameters_ - * state: Global application state. - * feature: Feature slug. +- _state_ `Object`: Global application state -*Returns* +_Returns_ -Is active. +- `boolean`: Whether the editor sidebar is opened. -### isPluginItemPinned +<a name="isFeatureActive" href="#isFeatureActive">#</a> **isFeatureActive** -Returns true if the plugin item is pinned to the header. -When the value is not set it defaults to true. +Returns whether the given feature is enabled or not. -*Parameters* +_Parameters_ - * state: Global application state. - * pluginName: Plugin item name. +- _state_ `Object`: Global application state. +- _feature_ `string`: Feature slug. -*Returns* +_Returns_ -Whether the plugin item is pinned. +- `boolean`: Is active. -### getActiveMetaBoxLocations +<a name="isMetaBoxLocationActive" href="#isMetaBoxLocationActive">#</a> **isMetaBoxLocationActive** -Returns an array of active meta box locations. +Returns true if there is an active meta box in the given location, or false +otherwise. -*Parameters* +_Parameters_ - * state: Post editor state. +- _state_ `Object`: Post editor state. +- _location_ `string`: Meta box location to test. -*Returns* +_Returns_ -Active meta box locations. +- `boolean`: Whether the meta box location is active. -### isMetaBoxLocationVisible +<a name="isMetaBoxLocationVisible" href="#isMetaBoxLocationVisible">#</a> **isMetaBoxLocationVisible** Returns true if a metabox location is active and visible -*Parameters* +_Parameters_ - * state: Post editor state. - * location: Meta box location to test. +- _state_ `Object`: Post editor state. +- _location_ `string`: Meta box location to test. -*Returns* +_Returns_ -Whether the meta box location is active and visible. +- `boolean`: Whether the meta box location is active and visible. -### isMetaBoxLocationActive +<a name="isModalActive" href="#isModalActive">#</a> **isModalActive** -Returns true if there is an active meta box in the given location, or false -otherwise. +Returns true if a modal is active, or false otherwise. -*Parameters* +_Parameters_ - * state: Post editor state. - * location: Meta box location to test. +- _state_ `Object`: Global application state. +- _modalName_ `string`: A string that uniquely identifies the modal. -*Returns* +_Returns_ -Whether the meta box location is active. +- `boolean`: Whether the modal is active. -### getMetaBoxesPerLocation +<a name="isPluginItemPinned" href="#isPluginItemPinned">#</a> **isPluginItemPinned** -Returns the list of all the available meta boxes for a given location. +Returns true if the plugin item is pinned to the header. +When the value is not set it defaults to true. -*Parameters* +_Parameters_ - * state: Global application state. - * location: Meta box location to test. +- _state_ `Object`: Global application state. +- _pluginName_ `string`: Plugin item name. -*Returns* +_Returns_ -List of meta boxes. +- `boolean`: Whether the plugin item is pinned. -### getAllMetaBoxes +<a name="isPluginSidebarOpened" href="#isPluginSidebarOpened">#</a> **isPluginSidebarOpened** -Returns the list of all the available meta boxes. +Returns true if the plugin sidebar is opened. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state -*Returns* +_Returns_ -List of meta boxes. +- `boolean`: Whether the plugin sidebar is opened. -### hasMetaBoxes +<a name="isPublishSidebarOpened" href="#isPublishSidebarOpened">#</a> **isPublishSidebarOpened** -Returns true if the post is using Meta Boxes +Returns true if the publish sidebar is opened. -*Parameters* +_Parameters_ - * state: Global application state +- _state_ `Object`: Global application state -*Returns* +_Returns_ -Whether there are metaboxes or not. +- `boolean`: Whether the publish sidebar is open. -### isSavingMetaBoxes +<a name="isSavingMetaBoxes" href="#isSavingMetaBoxes">#</a> **isSavingMetaBoxes** Returns true if the Meta Boxes are being saved. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. - * state: Global application state. +_Returns_ -*Returns* +- `boolean`: Whether the metaboxes are being saved. -Whether the metaboxes are being saved. + +<!-- END TOKEN(Autogenerated selectors) --> ## Actions -### openGeneralSidebar +<!-- START TOKEN(Autogenerated actions) --> + +<a name="closeGeneralSidebar" href="#closeGeneralSidebar">#</a> **closeGeneralSidebar** + +Returns an action object signalling that the user closed the sidebar. + +_Returns_ + +- `Object`: Action object. + +<a name="closeModal" href="#closeModal">#</a> **closeModal** + +Returns an action object signalling that the user closed a modal. + +_Returns_ + +- `Object`: Action object. + +<a name="closePublishSidebar" href="#closePublishSidebar">#</a> **closePublishSidebar** + +Returns an action object used in signalling that the user closed the +publish sidebar. + +_Returns_ + +- `Object`: Action object. + +<a name="hideBlockTypes" href="#hideBlockTypes">#</a> **hideBlockTypes** + +Returns an action object used in signalling that block types by the given +name(s) should be hidden. + +_Parameters_ + +- _blockNames_ `Array<string>`: Names of block types to hide. + +_Returns_ + +- `Object`: Action object. + +<a name="metaBoxUpdatesSuccess" href="#metaBoxUpdatesSuccess">#</a> **metaBoxUpdatesSuccess** + +Returns an action object used signal a successful meta box update. + +_Returns_ + +- `Object`: Action object. + +<a name="openGeneralSidebar" href="#openGeneralSidebar">#</a> **openGeneralSidebar** Returns an action object used in signalling that the user opened an editor sidebar. -*Parameters* +_Parameters_ - * name: Sidebar name to be opened. +- _name_ `string`: Sidebar name to be opened. -### closeGeneralSidebar +_Returns_ -Returns an action object signalling that the user closed the sidebar. +- `Object`: Action object. -### openModal +<a name="openModal" href="#openModal">#</a> **openModal** Returns an action object used in signalling that the user opened a modal. -*Parameters* +_Parameters_ - * name: A string that uniquely identifies the modal. +- _name_ `string`: A string that uniquely identifies the modal. -### closeModal +_Returns_ -Returns an action object signalling that the user closed a modal. +- `Object`: Action object. -### openPublishSidebar +<a name="openPublishSidebar" href="#openPublishSidebar">#</a> **openPublishSidebar** Returns an action object used in signalling that the user opened the publish sidebar. -### closePublishSidebar +_Returns_ -Returns an action object used in signalling that the user closed the -publish sidebar. +- `Object`: Action object -### togglePublishSidebar +<a name="removeEditorPanel" href="#removeEditorPanel">#</a> **removeEditorPanel** -Returns an action object used in signalling that the user toggles the publish sidebar. +Returns an action object used to remove a panel from the editor. -### toggleEditorPanelEnabled +_Parameters_ -Returns an action object used to enable or disable a panel in the editor. +- _panelName_ `string`: A string that identifies the panel to remove. -*Parameters* +_Returns_ - * panelName: A string that identifies the panel to enable or disable. +- `Object`: Action object. -### toggleEditorPanelOpened +<a name="requestMetaBoxUpdates" href="#requestMetaBoxUpdates">#</a> **requestMetaBoxUpdates** -Returns an action object used to open or close a panel in the editor. +Returns an action object used to request meta box update. -*Parameters* +_Returns_ - * panelName: A string that identifies the panel to open or close. +- `Object`: Action object. -### removeEditorPanel +<a name="setAvailableMetaBoxesPerLocation" href="#setAvailableMetaBoxesPerLocation">#</a> **setAvailableMetaBoxesPerLocation** -Returns an action object used to remove a panel from the editor. +Returns an action object used in signaling +what Meta boxes are available in which location. -*Parameters* +_Parameters_ - * panelName: A string that identifies the panel to remove. +- _metaBoxesPerLocation_ `Object`: Meta boxes per location. -### toggleFeature +_Returns_ -Returns an action object used to toggle a feature flag. +- `Object`: Action object. -*Parameters* +<a name="showBlockTypes" href="#showBlockTypes">#</a> **showBlockTypes** - * feature: Feature name. +Returns an action object used in signalling that block types by the given +name(s) should be shown. -### togglePinnedPluginItem +_Parameters_ -Returns an action object used to toggle a plugin name flag. +- _blockNames_ `Array<string>`: Names of block types to show. -*Parameters* +_Returns_ - * pluginName: Plugin name. +- `Object`: Action object. -### hideBlockTypes +<a name="switchEditorMode" href="#switchEditorMode">#</a> **switchEditorMode** -Returns an action object used in signalling that block types by the given -name(s) should be hidden. +Undocumented declaration. -*Parameters* +<a name="toggleEditorPanelEnabled" href="#toggleEditorPanelEnabled">#</a> **toggleEditorPanelEnabled** - * blockNames: Names of block types to hide. +Returns an action object used to enable or disable a panel in the editor. -### showBlockTypes +_Parameters_ -Returns an action object used in signalling that block types by the given -name(s) should be shown. +- _panelName_ `string`: A string that identifies the panel to enable or disable. -*Parameters* +_Returns_ - * blockNames: Names of block types to show. +- `Object`: Action object. -### setAvailableMetaBoxesPerLocation +<a name="toggleEditorPanelOpened" href="#toggleEditorPanelOpened">#</a> **toggleEditorPanelOpened** -Returns an action object used in signaling -what Meta boxes are available in which location. +Returns an action object used to open or close a panel in the editor. -*Parameters* +_Parameters_ - * metaBoxesPerLocation: Meta boxes per location. +- _panelName_ `string`: A string that identifies the panel to open or close. -### requestMetaBoxUpdates +_Returns_ -Returns an action object used to request meta box update. +- `Object`: Action object. + +<a name="toggleFeature" href="#toggleFeature">#</a> **toggleFeature** + +Returns an action object used to toggle a feature flag. + +_Parameters_ + +- _feature_ `string`: Feature name. + +_Returns_ + +- `Object`: Action object. + +<a name="togglePinnedPluginItem" href="#togglePinnedPluginItem">#</a> **togglePinnedPluginItem** + +Returns an action object used to toggle a plugin name flag. + +_Parameters_ + +- _pluginName_ `string`: Plugin name. + +_Returns_ + +- `Object`: Action object. + +<a name="togglePublishSidebar" href="#togglePublishSidebar">#</a> **togglePublishSidebar** + +Returns an action object used in signalling that the user toggles the publish sidebar. + +_Returns_ -### metaBoxUpdatesSuccess +- `Object`: Action object -Returns an action object used signal a successful meta box update. \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index 8a770b01e30293..b5cc78a84c0870 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1,370 +1,795 @@ -# **core/editor**: The Post Editor’s Data +# The Post Editor’s Data + +Namespace: `core/editor`. ## Selectors -### hasEditorUndo +<!-- START TOKEN(Autogenerated selectors) --> -Returns true if any past editor history snapshots exist, or false otherwise. +<a name="canInsertBlockType" href="#canInsertBlockType">#</a> **canInsertBlockType** -*Parameters* +_Related_ - * state: Global application state. +- canInsertBlockType in core/block-editor store. -### hasEditorRedo +<a name="canUserUseUnfilteredHTML" href="#canUserUseUnfilteredHTML">#</a> **canUserUseUnfilteredHTML** -Returns true if any future editor history snapshots exist, or false +Returns whether or not the user has the unfiltered_html capability. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `boolean`: Whether the user can or can't post unfiltered HTML. + +<a name="didPostSaveRequestFail" href="#didPostSaveRequestFail">#</a> **didPostSaveRequestFail** + +Returns true if a previous post save was attempted but failed, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether redo history exists. +- `boolean`: Whether the post save failed. -### isEditedPostNew +<a name="didPostSaveRequestSucceed" href="#didPostSaveRequestSucceed">#</a> **didPostSaveRequestSucceed** -Returns true if the currently edited post is yet to be saved, or false if -the post has been saved. +Returns true if a previous post save was attempted successfully, or false +otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post is new. +- `boolean`: Whether the post was saved successfully. -### hasChangedContent +<a name="getActivePostLock" href="#getActivePostLock">#</a> **getActivePostLock** -Returns true if content includes unsaved changes, or false otherwise. +Returns the active post lock. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether content includes unsaved changes. +- `Object`: The lock object. -### isEditedPostDirty +<a name="getAdjacentBlockClientId" href="#getAdjacentBlockClientId">#</a> **getAdjacentBlockClientId** -Returns true if there are unsaved values for the current edit session, or -false if the editing state matches the saved or new post. +_Related_ + +- getAdjacentBlockClientId in core/block-editor store. + +<a name="getAutosave" href="#getAutosave">#</a> **getAutosave** + +> **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package. -*Parameters* +Returns the current autosave, or null if one is not set (i.e. if the post +has yet to be autosaved, or has been saved or published since the last +autosave). - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Editor state. -Whether unsaved values exist. +_Returns_ -### isCleanNewPost +- `?Object`: Current autosave, if exists. -Returns true if there are no unsaved values for the current edit session and -if the currently edited post is new (has never been saved before). +<a name="getAutosaveAttribute" href="#getAutosaveAttribute">#</a> **getAutosaveAttribute** + +> **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package and access properties on the returned autosave object using getPostRawValue. + +Returns an attribute value of the current autosave revision for a post, or +null if there is no autosave for the post. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _attributeName_ `string`: Autosave attribute name. + +_Returns_ + +- `*`: Autosave attribute value. + +<a name="getBlock" href="#getBlock">#</a> **getBlock** + +_Related_ + +- getBlock in core/block-editor store. + +<a name="getBlockAttributes" href="#getBlockAttributes">#</a> **getBlockAttributes** + +_Related_ + +- getBlockAttributes in core/block-editor store. + +<a name="getBlockCount" href="#getBlockCount">#</a> **getBlockCount** + +_Related_ + +- getBlockCount in core/block-editor store. + +<a name="getBlockDependantsCacheBust" href="#getBlockDependantsCacheBust">#</a> **getBlockDependantsCacheBust** + +_Related_ + +- getBlockDependantsCacheBust in core/block-editor store. + +<a name="getBlockHierarchyRootClientId" href="#getBlockHierarchyRootClientId">#</a> **getBlockHierarchyRootClientId** + +_Related_ + +- getBlockHierarchyRootClientId in core/block-editor store. + +<a name="getBlockIndex" href="#getBlockIndex">#</a> **getBlockIndex** + +_Related_ + +- getBlockIndex in core/block-editor store. + +<a name="getBlockInsertionPoint" href="#getBlockInsertionPoint">#</a> **getBlockInsertionPoint** + +_Related_ + +- getBlockInsertionPoint in core/block-editor store. + +<a name="getBlockListSettings" href="#getBlockListSettings">#</a> **getBlockListSettings** + +_Related_ -*Parameters* +- getBlockListSettings in core/block-editor store. - * state: Global application state. +<a name="getBlockMode" href="#getBlockMode">#</a> **getBlockMode** -*Returns* +_Related_ -Whether new post and unsaved values exist. +- getBlockMode in core/block-editor store. -### getCurrentPost +<a name="getBlockName" href="#getBlockName">#</a> **getBlockName** + +_Related_ + +- getBlockName in core/block-editor store. + +<a name="getBlockOrder" href="#getBlockOrder">#</a> **getBlockOrder** + +_Related_ + +- getBlockOrder in core/block-editor store. + +<a name="getBlockRootClientId" href="#getBlockRootClientId">#</a> **getBlockRootClientId** + +_Related_ + +- getBlockRootClientId in core/block-editor store. + +<a name="getBlocks" href="#getBlocks">#</a> **getBlocks** + +_Related_ + +- getBlocks in core/block-editor store. + +<a name="getBlocksByClientId" href="#getBlocksByClientId">#</a> **getBlocksByClientId** + +_Related_ + +- getBlocksByClientId in core/block-editor store. + +<a name="getBlockSelectionEnd" href="#getBlockSelectionEnd">#</a> **getBlockSelectionEnd** + +_Related_ + +- getBlockSelectionEnd in core/block-editor store. + +<a name="getBlockSelectionStart" href="#getBlockSelectionStart">#</a> **getBlockSelectionStart** + +_Related_ + +- getBlockSelectionStart in core/block-editor store. + +<a name="getBlocksForSerialization" href="#getBlocksForSerialization">#</a> **getBlocksForSerialization** + +Returns a set of blocks which are to be used in consideration of the post's +generated save content. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Array<WPBlock>`: Filtered set of blocks for save. + +<a name="getClientIdsOfDescendants" href="#getClientIdsOfDescendants">#</a> **getClientIdsOfDescendants** + +_Related_ + +- getClientIdsOfDescendants in core/block-editor store. + +<a name="getClientIdsWithDescendants" href="#getClientIdsWithDescendants">#</a> **getClientIdsWithDescendants** + +_Related_ + +- getClientIdsWithDescendants in core/block-editor store. + +<a name="getCurrentPost" href="#getCurrentPost">#</a> **getCurrentPost** Returns the post currently being edited in its last known saved state, not including unsaved edits. Returns an object containing relevant default post values if the post has not yet been saved. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Post object. +- `Object`: Post object. -### getCurrentPostType +<a name="getCurrentPostAttribute" href="#getCurrentPostAttribute">#</a> **getCurrentPostAttribute** -Returns the post type of the post currently being edited. +Returns an attribute value of the saved post. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. +- _attributeName_ `string`: Post attribute name. -*Returns* +_Returns_ -Post type. +- `*`: Post attribute value. -### getCurrentPostId +<a name="getCurrentPostId" href="#getCurrentPostId">#</a> **getCurrentPostId** Returns the ID of the post currently being edited, or null if the post has not yet been saved. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `?number`: ID of current post. + +<a name="getCurrentPostLastRevisionId" href="#getCurrentPostLastRevisionId">#</a> **getCurrentPostLastRevisionId** + +Returns the last revision ID of the post currently being edited, +or null if the post has no revisions. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -ID of current post. +_Returns_ -### getCurrentPostRevisionsCount +- `?number`: ID of the last revision. + +<a name="getCurrentPostRevisionsCount" href="#getCurrentPostRevisionsCount">#</a> **getCurrentPostRevisionsCount** Returns the number of revisions of the post currently being edited. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Number of revisions. +- `number`: Number of revisions. -### getCurrentPostLastRevisionId +<a name="getCurrentPostType" href="#getCurrentPostType">#</a> **getCurrentPostType** -Returns the last revision ID of the post currently being edited, -or null if the post has no revisions. +Returns the post type of the post currently being edited. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string`: Post type. + +<a name="getEditedPostAttribute" href="#getEditedPostAttribute">#</a> **getEditedPostAttribute** + +Returns a single attribute of the post being edited, preferring the unsaved +edit if one exists, but falling back to the attribute for the last known +saved state of the post. + +_Parameters_ -*Parameters* +- _state_ `Object`: Global application state. +- _attributeName_ `string`: Post attribute name. - * state: Global application state. +_Returns_ -*Returns* +- `*`: Post attribute value. -ID of the last revision. +<a name="getEditedPostContent" href="#getEditedPostContent">#</a> **getEditedPostContent** -### getPostEdits +Returns the content of the post being edited, preferring raw string edit +before falling back to serialization of block state. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string`: Post content. + +<a name="getEditedPostPreviewLink" href="#getEditedPostPreviewLink">#</a> **getEditedPostPreviewLink** + +Returns the post preview link + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `?string`: Preview Link. + +<a name="getEditedPostVisibility" href="#getEditedPostVisibility">#</a> **getEditedPostVisibility** + +Returns the current visibility of the post being edited, preferring the +unsaved value if different than the saved post. The return value is one of +"private", "password", or "public". + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `string`: Post visibility. + +<a name="getEditorBlocks" href="#getEditorBlocks">#</a> **getEditorBlocks** + +Return the current block list. + +_Parameters_ + +- _state_ `Object`: + +_Returns_ + +- `Array`: Block list. + +<a name="getEditorSettings" href="#getEditorSettings">#</a> **getEditorSettings** + +Returns the post editor settings. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Object`: The editor settings object. + +<a name="getFirstMultiSelectedBlockClientId" href="#getFirstMultiSelectedBlockClientId">#</a> **getFirstMultiSelectedBlockClientId** + +_Related_ + +- getFirstMultiSelectedBlockClientId in core/block-editor store. + +<a name="getGlobalBlockCount" href="#getGlobalBlockCount">#</a> **getGlobalBlockCount** + +_Related_ + +- getGlobalBlockCount in core/block-editor store. + +<a name="getInserterItems" href="#getInserterItems">#</a> **getInserterItems** + +_Related_ + +- getInserterItems in core/block-editor store. + +<a name="getLastMultiSelectedBlockClientId" href="#getLastMultiSelectedBlockClientId">#</a> **getLastMultiSelectedBlockClientId** + +_Related_ + +- getLastMultiSelectedBlockClientId in core/block-editor store. + +<a name="getMultiSelectedBlockClientIds" href="#getMultiSelectedBlockClientIds">#</a> **getMultiSelectedBlockClientIds** + +_Related_ + +- getMultiSelectedBlockClientIds in core/block-editor store. + +<a name="getMultiSelectedBlocks" href="#getMultiSelectedBlocks">#</a> **getMultiSelectedBlocks** + +_Related_ + +- getMultiSelectedBlocks in core/block-editor store. + +<a name="getMultiSelectedBlocksEndClientId" href="#getMultiSelectedBlocksEndClientId">#</a> **getMultiSelectedBlocksEndClientId** + +_Related_ + +- getMultiSelectedBlocksEndClientId in core/block-editor store. + +<a name="getMultiSelectedBlocksStartClientId" href="#getMultiSelectedBlocksStartClientId">#</a> **getMultiSelectedBlocksStartClientId** + +_Related_ + +- getMultiSelectedBlocksStartClientId in core/block-editor store. + +<a name="getNextBlockClientId" href="#getNextBlockClientId">#</a> **getNextBlockClientId** + +_Related_ + +- getNextBlockClientId in core/block-editor store. + +<a name="getPermalink" href="#getPermalink">#</a> **getPermalink** + +Returns the permalink for the post. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `?string`: The permalink, or null if the post is not viewable. + +<a name="getPermalinkParts" href="#getPermalinkParts">#</a> **getPermalinkParts** + +Returns the permalink for a post, split into it's three parts: the prefix, +the postName, and the suffix. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `Object`: An object containing the prefix, postName, and suffix for the permalink, or null if the post is not viewable. + +<a name="getPostEdits" href="#getPostEdits">#</a> **getPostEdits** Returns any post values which have been changed in the editor but not yet been saved. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `Object`: Object of key value pairs comprising unsaved edits. + +<a name="getPostLockUser" href="#getPostLockUser">#</a> **getPostLockUser** + +Returns details about the post lock user. + +_Parameters_ + +- _state_ `Object`: Global application state. - * state: Global application state. +_Returns_ -*Returns* +- `Object`: A user object. -Object of key value pairs comprising unsaved edits. +<a name="getPreviousBlockClientId" href="#getPreviousBlockClientId">#</a> **getPreviousBlockClientId** -### getReferenceByDistinctEdits +_Related_ + +- getPreviousBlockClientId in core/block-editor store. + +<a name="getReferenceByDistinctEdits" href="#getReferenceByDistinctEdits">#</a> **getReferenceByDistinctEdits** Returns a new reference when edited values have changed. This is useful in inferring where an edit has been made between states by comparison of the return values using strict equality. -*Parameters* +_Usage_ - * state: Editor state. + const hasEditOccurred = ( + getReferenceByDistinctEdits( beforeState ) !== + getReferenceByDistinctEdits( afterState ) + ); -*Returns* +_Parameters_ -A value whose reference will change only when an edit occurs. +- _state_ `Object`: Editor state. -### getCurrentPostAttribute +_Returns_ -Returns an attribute value of the saved post. +- `*`: A value whose reference will change only when an edit occurs. -*Parameters* +<a name="getSelectedBlock" href="#getSelectedBlock">#</a> **getSelectedBlock** - * state: Global application state. - * attributeName: Post attribute name. +_Related_ -*Returns* +- getSelectedBlock in core/block-editor store. -Post attribute value. +<a name="getSelectedBlockClientId" href="#getSelectedBlockClientId">#</a> **getSelectedBlockClientId** -### getEditedPostAttribute +_Related_ -Returns a single attribute of the post being edited, preferring the unsaved -edit if one exists, but falling back to the attribute for the last known -saved state of the post. +- getSelectedBlockClientId in core/block-editor store. -*Parameters* +<a name="getSelectedBlockCount" href="#getSelectedBlockCount">#</a> **getSelectedBlockCount** - * state: Global application state. - * attributeName: Post attribute name. +_Related_ -*Returns* +- getSelectedBlockCount in core/block-editor store. -Post attribute value. +<a name="getSelectedBlocksInitialCaretPosition" href="#getSelectedBlocksInitialCaretPosition">#</a> **getSelectedBlocksInitialCaretPosition** -### getAutosaveAttribute (deprecated) +_Related_ -Returns an attribute value of the current autosave revision for a post, or -null if there is no autosave for the post. +- getSelectedBlocksInitialCaretPosition in core/block-editor store. -*Deprecated* +<a name="getStateBeforeOptimisticTransaction" href="#getStateBeforeOptimisticTransaction">#</a> **getStateBeforeOptimisticTransaction** -Deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector - from the '@wordpress/core-data' package and access properties on the returned - autosave object using getPostRawValue. +Returns state object prior to a specified optimist transaction ID, or `null` +if the transaction corresponding to the given ID cannot be found. -*Parameters* +_Parameters_ - * state: Global application state. - * attributeName: Autosave attribute name. +- _state_ `Object`: Current global application state. +- _transactionId_ `Object`: Optimist transaction ID. -*Returns* +_Returns_ -Autosave attribute value. +- `Object`: Global application state prior to transaction. -### getEditedPostVisibility +<a name="getSuggestedPostFormat" href="#getSuggestedPostFormat">#</a> **getSuggestedPostFormat** -Returns the current visibility of the post being edited, preferring the -unsaved value if different than the saved post. The return value is one of -"private", "password", or "public". +Returns a suggested post format for the current post, inferred only if there +is a single block within the post and it is of a type known to match a +default post format. Returns null if the format cannot be determined. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Post visibility. +- `?string`: Suggested post format. -### isCurrentPostPending +<a name="getTemplate" href="#getTemplate">#</a> **getTemplate** -Returns true if post is pending review. +_Related_ -*Parameters* +- getTemplate in core/block-editor store. - * state: Global application state. +<a name="getTemplateLock" href="#getTemplateLock">#</a> **getTemplateLock** -*Returns* +_Related_ -Whether current post is pending review. +- getTemplateLock in core/block-editor store. -### isCurrentPostPublished +<a name="hasAutosave" href="#hasAutosave">#</a> **hasAutosave** -Return true if the current post has already been published. +> **Deprecated** since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector from the '@wordpress/core-data' package and check for a truthy value. -*Parameters* +Returns the true if there is an existing autosave, otherwise false. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -Whether the post has been published. +_Returns_ -### isCurrentPostScheduled +- `boolean`: Whether there is an existing autosave. -Returns true if post is already scheduled. +<a name="hasChangedContent" href="#hasChangedContent">#</a> **hasChangedContent** -*Parameters* +Returns true if content includes unsaved changes, or false otherwise. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Editor state. -Whether current post is scheduled to be posted. +_Returns_ -### isEditedPostPublishable +- `boolean`: Whether content includes unsaved changes. -Return true if the post being edited can be published. +<a name="hasEditorRedo" href="#hasEditorRedo">#</a> **hasEditorRedo** -*Parameters* +Returns true if any future editor history snapshots exist, or false +otherwise. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -Whether the post can been published. +_Returns_ -### isEditedPostSaveable +- `boolean`: Whether redo history exists. -Returns true if the post can be saved, or false otherwise. A post must -contain a title, an excerpt, or non-empty content to be valid for save. +<a name="hasEditorUndo" href="#hasEditorUndo">#</a> **hasEditorUndo** -*Parameters* +Returns true if any past editor history snapshots exist, or false otherwise. - * state: Global application state. +_Parameters_ -*Returns* +- _state_ `Object`: Global application state. -Whether the post can be saved. +_Returns_ -### isEditedPostEmpty +- `boolean`: Whether undo history exists. -Returns true if the edited post has content. A post has content if it has at -least one saveable block or otherwise has a non-empty content property -assigned. +<a name="hasInserterItems" href="#hasInserterItems">#</a> **hasInserterItems** -*Parameters* +_Related_ - * state: Global application state. +- hasInserterItems in core/block-editor store. -*Returns* +<a name="hasMultiSelection" href="#hasMultiSelection">#</a> **hasMultiSelection** -Whether post has content. +_Related_ -### isEditedPostAutosaveable +- hasMultiSelection in core/block-editor store. -Returns true if the post can be autosaved, or false otherwise. +<a name="hasSelectedBlock" href="#hasSelectedBlock">#</a> **hasSelectedBlock** -*Parameters* +_Related_ - * state: Global application state. - * autosave: A raw autosave object from the REST API. +- hasSelectedBlock in core/block-editor store. -*Returns* +<a name="hasSelectedInnerBlock" href="#hasSelectedInnerBlock">#</a> **hasSelectedInnerBlock** -Whether the post can be autosaved. +_Related_ -### getAutosave (deprecated) +- hasSelectedInnerBlock in core/block-editor store. -Returns the current autosave, or null if one is not set (i.e. if the post -has yet to be autosaved, or has been saved or published since the last -autosave). +<a name="inSomeHistory" href="#inSomeHistory">#</a> **inSomeHistory** -*Deprecated* +Returns true if an optimistic transaction is pending commit, for which the +before state satisfies the given predicate function. -Deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` - selector from the '@wordpress/core-data' package. +_Parameters_ -*Parameters* +- _state_ `Object`: Editor state. +- _predicate_ `Function`: Function given state, returning true if match. - * state: Editor state. +_Returns_ -*Returns* +- `boolean`: Whether predicate matches for some history. -Current autosave, if exists. +<a name="isAncestorMultiSelected" href="#isAncestorMultiSelected">#</a> **isAncestorMultiSelected** -### hasAutosave (deprecated) +_Related_ -Returns the true if there is an existing autosave, otherwise false. +- isAncestorMultiSelected in core/block-editor store. + +<a name="isAutosavingPost" href="#isAutosavingPost">#</a> **isAutosavingPost** + +Returns true if the post is autosaving, or false otherwise. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the post is autosaving. + +<a name="isBlockInsertionPointVisible" href="#isBlockInsertionPointVisible">#</a> **isBlockInsertionPointVisible** + +_Related_ + +- isBlockInsertionPointVisible in core/block-editor store. + +<a name="isBlockMultiSelected" href="#isBlockMultiSelected">#</a> **isBlockMultiSelected** + +_Related_ + +- isBlockMultiSelected in core/block-editor store. + +<a name="isBlockSelected" href="#isBlockSelected">#</a> **isBlockSelected** + +_Related_ + +- isBlockSelected in core/block-editor store. + +<a name="isBlockValid" href="#isBlockValid">#</a> **isBlockValid** + +_Related_ + +- isBlockValid in core/block-editor store. + +<a name="isBlockWithinSelection" href="#isBlockWithinSelection">#</a> **isBlockWithinSelection** + +_Related_ + +- isBlockWithinSelection in core/block-editor store. + +<a name="isCaretWithinFormattedText" href="#isCaretWithinFormattedText">#</a> **isCaretWithinFormattedText** + +_Related_ + +- isCaretWithinFormattedText in core/block-editor store. + +<a name="isCleanNewPost" href="#isCleanNewPost">#</a> **isCleanNewPost** + +Returns true if there are no unsaved values for the current edit session and +if the currently edited post is new (has never been saved before). + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether new post and unsaved values exist. + +<a name="isCurrentPostPending" href="#isCurrentPostPending">#</a> **isCurrentPostPending** + +Returns true if post is pending review. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether current post is pending review. + +<a name="isCurrentPostPublished" href="#isCurrentPostPublished">#</a> **isCurrentPostPublished** + +Return true if the current post has already been published. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the post has been published. + +<a name="isCurrentPostScheduled" href="#isCurrentPostScheduled">#</a> **isCurrentPostScheduled** + +Returns true if post is already scheduled. -*Deprecated* +_Parameters_ -Deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector - from the '@wordpress/core-data' package and check for a truthy value. +- _state_ `Object`: Global application state. -*Parameters* +_Returns_ - * state: Global application state. +- `boolean`: Whether current post is scheduled to be posted. -*Returns* +<a name="isEditedPostAutosaveable" href="#isEditedPostAutosaveable">#</a> **isEditedPostAutosaveable** -Whether there is an existing autosave. +Returns true if the post can be autosaved, or false otherwise. + +_Parameters_ + +- _state_ `Object`: Global application state. +- _autosave_ `Object`: A raw autosave object from the REST API. + +_Returns_ -### isEditedPostBeingScheduled +- `boolean`: Whether the post can be autosaved. + +<a name="isEditedPostBeingScheduled" href="#isEditedPostBeingScheduled">#</a> **isEditedPostBeingScheduled** Return true if the post being edited is being scheduled. Preferring the unsaved status values. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post has been published. +- `boolean`: Whether the post has been published. -### isEditedPostDateFloating +<a name="isEditedPostDateFloating" href="#isEditedPostDateFloating">#</a> **isEditedPostDateFloating** Returns whether the current post should be considered to have a "floating" date (i.e. that it would publish "Immediately" rather than at a set time). @@ -374,463 +799,590 @@ where the 0000-00-00T00:00:00 placeholder is present in the database. To infer that a post is set to publish "Immediately" we check whether the date and modified date are the same. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Whether the edited post has a floating date value. +- `boolean`: Whether the edited post has a floating date value. -### isSavingPost +<a name="isEditedPostDirty" href="#isEditedPostDirty">#</a> **isEditedPostDirty** -Returns true if the post is currently being saved, or false otherwise. +Returns true if there are unsaved values for the current edit session, or +false if the editing state matches the saved or new post. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether post is being saved. +- `boolean`: Whether unsaved values exist. -### didPostSaveRequestSucceed +<a name="isEditedPostEmpty" href="#isEditedPostEmpty">#</a> **isEditedPostEmpty** -Returns true if a previous post save was attempted successfully, or false -otherwise. +Returns true if the edited post has content. A post has content if it has at +least one saveable block or otherwise has a non-empty content property +assigned. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post was saved successfully. +- `boolean`: Whether post has content. -### didPostSaveRequestFail +<a name="isEditedPostNew" href="#isEditedPostNew">#</a> **isEditedPostNew** -Returns true if a previous post save was attempted but failed, or false -otherwise. +Returns true if the currently edited post is yet to be saved, or false if +the post has been saved. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post save failed. +- `boolean`: Whether the post is new. -### isAutosavingPost +<a name="isEditedPostPublishable" href="#isEditedPostPublishable">#</a> **isEditedPostPublishable** -Returns true if the post is autosaving, or false otherwise. +Return true if the post being edited can be published. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post is autosaving. +- `boolean`: Whether the post can been published. -### isPreviewingPost +<a name="isEditedPostSaveable" href="#isEditedPostSaveable">#</a> **isEditedPostSaveable** -Returns true if the post is being previewed, or false otherwise. +Returns true if the post can be saved, or false otherwise. A post must +contain a title, an excerpt, or non-empty content to be valid for save. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether the post is being previewed. +- `boolean`: Whether the post can be saved. -### getEditedPostPreviewLink +<a name="isFirstMultiSelectedBlock" href="#isFirstMultiSelectedBlock">#</a> **isFirstMultiSelectedBlock** -Returns the post preview link +_Related_ -*Parameters* +- isFirstMultiSelectedBlock in core/block-editor store. - * state: Global application state. +<a name="isMultiSelecting" href="#isMultiSelecting">#</a> **isMultiSelecting** -*Returns* +_Related_ -Preview Link. +- isMultiSelecting in core/block-editor store. -### getSuggestedPostFormat +<a name="isPermalinkEditable" href="#isPermalinkEditable">#</a> **isPermalinkEditable** -Returns a suggested post format for the current post, inferred only if there -is a single block within the post and it is of a type known to match a -default post format. Returns null if the format cannot be determined. +Returns whether the permalink is editable or not. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Editor state. -*Returns* +_Returns_ -Suggested post format. +- `boolean`: Whether or not the permalink is editable. -### getBlocksForSerialization +<a name="isPostLocked" href="#isPostLocked">#</a> **isPostLocked** -Returns a set of blocks which are to be used in consideration of the post's -generated save content. +Returns whether the post is locked. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Filtered set of blocks for save. +- `boolean`: Is locked. -### getEditedPostContent +<a name="isPostLockTakeover" href="#isPostLockTakeover">#</a> **isPostLockTakeover** -Returns the content of the post being edited, preferring raw string edit -before falling back to serialization of block state. +Returns whether the edition of the post has been taken over. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Post content. +- `boolean`: Is post lock takeover. -### getStateBeforeOptimisticTransaction +<a name="isPostSavingLocked" href="#isPostSavingLocked">#</a> **isPostSavingLocked** -Returns state object prior to a specified optimist transaction ID, or `null` -if the transaction corresponding to the given ID cannot be found. +Returns whether post saving is locked. -*Parameters* +_Parameters_ - * state: Current global application state. - * transactionId: Optimist transaction ID. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Global application state prior to transaction. +- `boolean`: Is locked. -### isPublishingPost +<a name="isPreviewingPost" href="#isPreviewingPost">#</a> **isPreviewingPost** + +Returns true if the post is being previewed, or false otherwise. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether the post is being previewed. + +<a name="isPublishingPost" href="#isPublishingPost">#</a> **isPublishingPost** Returns true if the post is being published, or false otherwise. -*Parameters* +_Parameters_ - * state: Global application state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether post is being published. +- `boolean`: Whether post is being published. -### isPermalinkEditable +<a name="isPublishSidebarEnabled" href="#isPublishSidebarEnabled">#</a> **isPublishSidebarEnabled** -Returns whether the permalink is editable or not. +Returns whether the pre-publish panel should be shown +or skipped when the user clicks the "publish" button. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -Whether or not the permalink is editable. +- `boolean`: Whether the pre-publish panel should be shown or not. -### getPermalink +<a name="isSavingPost" href="#isSavingPost">#</a> **isSavingPost** -Returns the permalink for the post. +Returns true if the post is currently being saved, or false otherwise. -*Parameters* +_Parameters_ - * state: Editor state. +- _state_ `Object`: Global application state. -*Returns* +_Returns_ -The permalink, or null if the post is not viewable. +- `boolean`: Whether post is being saved. -### getPermalinkParts +<a name="isSelectionEnabled" href="#isSelectionEnabled">#</a> **isSelectionEnabled** -Returns the permalink for a post, split into it's three parts: the prefix, -the postName, and the suffix. +_Related_ -*Parameters* +- isSelectionEnabled in core/block-editor store. - * state: Editor state. +<a name="isTyping" href="#isTyping">#</a> **isTyping** -*Returns* +_Related_ -An object containing the prefix, postName, and suffix for - the permalink, or null if the post is not viewable. +- isTyping in core/block-editor store. -### inSomeHistory +<a name="isValidTemplate" href="#isValidTemplate">#</a> **isValidTemplate** -Returns true if an optimistic transaction is pending commit, for which the -before state satisfies the given predicate function. +_Related_ -*Parameters* +- isValidTemplate in core/block-editor store. - * state: Editor state. - * predicate: Function given state, returning true if match. -*Returns* +<!-- END TOKEN(Autogenerated selectors) --> -Whether predicate matches for some history. +## Actions -### isPostLocked +<!-- START TOKEN(Autogenerated actions) --> -Returns whether the post is locked. +<a name="autosave" href="#autosave">#</a> **autosave** -*Parameters* +Action generator used in signalling that the post should autosave. - * state: Global application state. +_Parameters_ -*Returns* +- _options_ `?Object`: Extra flags to identify the autosave. -Is locked. +<a name="clearSelectedBlock" href="#clearSelectedBlock">#</a> **clearSelectedBlock** -### isPostSavingLocked +_Related_ -Returns whether post saving is locked. +- clearSelectedBlock in core/block-editor store. + +<a name="createUndoLevel" href="#createUndoLevel">#</a> **createUndoLevel** + +Returns an action object used in signalling that undo history record should +be created. -*Parameters* +_Returns_ - * state: Global application state. +- `Object`: Action object. -*Returns* +<a name="disablePublishSidebar" href="#disablePublishSidebar">#</a> **disablePublishSidebar** -Is locked. +Returns an action object used in signalling that the user has disabled the +publish sidebar. -### isPostLockTakeover +_Returns_ -Returns whether the edition of the post has been taken over. +- `Object`: Action object -*Parameters* +<a name="editPost" href="#editPost">#</a> **editPost** - * state: Global application state. +Returns an action object used in signalling that attributes of the post have +been edited. -*Returns* +_Parameters_ -Is post lock takeover. +- _edits_ `Object`: Post attributes to edit. -### getPostLockUser +_Returns_ -Returns details about the post lock user. +- `Object`: Action object. -*Parameters* +<a name="enablePublishSidebar" href="#enablePublishSidebar">#</a> **enablePublishSidebar** - * state: Global application state. +Returns an action object used in signalling that the user has enabled the +publish sidebar. -*Returns* +_Returns_ -A user object. +- `Object`: Action object -### getActivePostLock +<a name="enterFormattedText" href="#enterFormattedText">#</a> **enterFormattedText** -Returns the active post lock. +_Related_ -*Parameters* +- enterFormattedText in core/block-editor store. - * state: Global application state. +<a name="exitFormattedText" href="#exitFormattedText">#</a> **exitFormattedText** -*Returns* +_Related_ -The lock object. +- exitFormattedText in core/block-editor store. -### canUserUseUnfilteredHTML +<a name="hideInsertionPoint" href="#hideInsertionPoint">#</a> **hideInsertionPoint** -Returns whether or not the user has the unfiltered_html capability. +_Related_ -*Parameters* +- hideInsertionPoint in core/block-editor store. - * state: Editor state. +<a name="insertBlock" href="#insertBlock">#</a> **insertBlock** -*Returns* +_Related_ -Whether the user can or can't post unfiltered HTML. +- insertBlock in core/block-editor store. -### isPublishSidebarEnabled +<a name="insertBlocks" href="#insertBlocks">#</a> **insertBlocks** -Returns whether the pre-publish panel should be shown -or skipped when the user clicks the "publish" button. +_Related_ -*Parameters* +- insertBlocks in core/block-editor store. - * state: Global application state. +<a name="insertDefaultBlock" href="#insertDefaultBlock">#</a> **insertDefaultBlock** -*Returns* +_Related_ -Whether the pre-publish panel should be shown or not. +- insertDefaultBlock in core/block-editor store. -### getEditorBlocks +<a name="lockPostSaving" href="#lockPostSaving">#</a> **lockPostSaving** -Return the current block list. +Returns an action object used to signal that post saving is locked. -*Parameters* +_Parameters_ - * state: null +- _lockName_ `string`: The lock name. -*Returns* +_Returns_ -Block list. +- `Object`: Action object -### getEditorSettings +<a name="mergeBlocks" href="#mergeBlocks">#</a> **mergeBlocks** -Returns the post editor settings. +_Related_ -*Parameters* +- mergeBlocks in core/block-editor store. - * state: Editor state. +<a name="moveBlocksDown" href="#moveBlocksDown">#</a> **moveBlocksDown** -*Returns* +_Related_ -The editor settings object. +- moveBlocksDown in core/block-editor store. -## Actions +<a name="moveBlocksUp" href="#moveBlocksUp">#</a> **moveBlocksUp** -### setupEditor +_Related_ -Returns an action generator used in signalling that editor has initialized with -the specified post object and editor settings. +- moveBlocksUp in core/block-editor store. -*Parameters* +<a name="moveBlockToPosition" href="#moveBlockToPosition">#</a> **moveBlockToPosition** - * post: Post object. - * edits: Initial edited attributes object. - * template: Block Template. +_Related_ -### resetPost +- moveBlockToPosition in core/block-editor store. -Returns an action object used in signalling that the latest version of the -post has been received, either by initialization or save. +<a name="multiSelect" href="#multiSelect">#</a> **multiSelect** + +_Related_ + +- multiSelect in core/block-editor store. + +<a name="receiveBlocks" href="#receiveBlocks">#</a> **receiveBlocks** + +_Related_ + +- receiveBlocks in core/block-editor store. + +<a name="redo" href="#redo">#</a> **redo** + +Returns an action object used in signalling that undo history should +restore last popped state. + +_Returns_ + +- `Object`: Action object. + +<a name="refreshPost" href="#refreshPost">#</a> **refreshPost** + +Action generator for handling refreshing the current post. + +<a name="removeBlock" href="#removeBlock">#</a> **removeBlock** + +_Related_ + +- removeBlock in core/block-editor store. + +<a name="removeBlocks" href="#removeBlocks">#</a> **removeBlocks** + +_Related_ + +- removeBlocks in core/block-editor store. + +<a name="replaceBlocks" href="#replaceBlocks">#</a> **replaceBlocks** + +_Related_ -*Parameters* +- replaceBlocks in core/block-editor store. - * post: Post object. +<a name="resetAutosave" href="#resetAutosave">#</a> **resetAutosave** -### resetAutosave (deprecated) +> **Deprecated** since 5.6. Callers should use the `receiveAutosaves( postId, autosave )` selector from the '@wordpress/core-data' package. Returns an action object used in signalling that the latest autosave of the post has been received, by initialization or autosave. -*Deprecated* +_Parameters_ -Deprecated since 5.6. Callers should use the `receiveAutosaves( postId, autosave )` - selector from the '@wordpress/core-data' package. +- _newAutosave_ `Object`: Autosave post object. -*Parameters* +_Returns_ - * newAutosave: Autosave post object. +- `Object`: Action object. -### updatePost +<a name="resetBlocks" href="#resetBlocks">#</a> **resetBlocks** -Returns an action object used in signalling that a patch of updates for the -latest version of the post have been received. +_Related_ + +- resetBlocks in core/block-editor store. + +<a name="resetEditorBlocks" href="#resetEditorBlocks">#</a> **resetEditorBlocks** + +Returns an action object used to signal that the blocks have been updated. + +_Parameters_ + +- _blocks_ `Array`: Block Array. +- _options_ `?Object`: Optional options. + +_Returns_ + +- `Object`: Action object + +<a name="resetPost" href="#resetPost">#</a> **resetPost** + +Returns an action object used in signalling that the latest version of the +post has been received, either by initialization or save. -*Parameters* +_Parameters_ - * edits: Updated post fields. +- _post_ `Object`: Post object. -### setupEditorState +_Returns_ + +- `Object`: Action object. + +<a name="savePost" href="#savePost">#</a> **savePost** + +Action generator for saving the current post in the editor. + +_Parameters_ + +- _options_ `Object`: + +<a name="selectBlock" href="#selectBlock">#</a> **selectBlock** + +_Related_ + +- selectBlock in core/block-editor store. + +<a name="setTemplateValidity" href="#setTemplateValidity">#</a> **setTemplateValidity** + +_Related_ + +- setTemplateValidity in core/block-editor store. + +<a name="setupEditor" href="#setupEditor">#</a> **setupEditor** + +Returns an action generator used in signalling that editor has initialized with +the specified post object and editor settings. + +_Parameters_ + +- _post_ `Object`: Post object. +- _edits_ `Object`: Initial edited attributes object. +- _template_ `?Array`: Block Template. + +<a name="setupEditorState" href="#setupEditorState">#</a> **setupEditorState** Returns an action object used to setup the editor state when first opening an editor. -*Parameters* +_Parameters_ - * post: Post object. +- _post_ `Object`: Post object. -### editPost +_Returns_ -Returns an action object used in signalling that attributes of the post have -been edited. +- `Object`: Action object. -*Parameters* +<a name="showInsertionPoint" href="#showInsertionPoint">#</a> **showInsertionPoint** - * edits: Post attributes to edit. +_Related_ -### savePost +- showInsertionPoint in core/block-editor store. -Action generator for saving the current post in the editor. +<a name="startMultiSelect" href="#startMultiSelect">#</a> **startMultiSelect** -*Parameters* +_Related_ - * options: null +- startMultiSelect in core/block-editor store. -### refreshPost +<a name="startTyping" href="#startTyping">#</a> **startTyping** -Action generator for handling refreshing the current post. +_Related_ -### trashPost +- startTyping in core/block-editor store. -Action generator for trashing the current post in the editor. +<a name="stopMultiSelect" href="#stopMultiSelect">#</a> **stopMultiSelect** -### autosave +_Related_ -Action generator used in signalling that the post should autosave. +- stopMultiSelect in core/block-editor store. -*Parameters* +<a name="stopTyping" href="#stopTyping">#</a> **stopTyping** - * options: Extra flags to identify the autosave. +_Related_ -### redo +- stopTyping in core/block-editor store. -Returns an action object used in signalling that undo history should -restore last popped state. +<a name="synchronizeTemplate" href="#synchronizeTemplate">#</a> **synchronizeTemplate** + +_Related_ + +- synchronizeTemplate in core/block-editor store. -### undo +<a name="toggleBlockMode" href="#toggleBlockMode">#</a> **toggleBlockMode** + +_Related_ + +- toggleBlockMode in core/block-editor store. + +<a name="toggleSelection" href="#toggleSelection">#</a> **toggleSelection** + +_Related_ + +- toggleSelection in core/block-editor store. + +<a name="trashPost" href="#trashPost">#</a> **trashPost** + +Action generator for trashing the current post in the editor. + +<a name="undo" href="#undo">#</a> **undo** Returns an action object used in signalling that undo history should pop. -### createUndoLevel +_Returns_ -Returns an action object used in signalling that undo history record should -be created. +- `Object`: Action object. -### updatePostLock +<a name="unlockPostSaving" href="#unlockPostSaving">#</a> **unlockPostSaving** -Returns an action object used to lock the editor. +Returns an action object used to signal that post saving is unlocked. -*Parameters* +_Parameters_ - * lock: Details about the post lock status, user, and nonce. +- _lockName_ `string`: The lock name. -### enablePublishSidebar +_Returns_ -Returns an action object used in signalling that the user has enabled the -publish sidebar. +- `Object`: Action object -### disablePublishSidebar +<a name="updateBlock" href="#updateBlock">#</a> **updateBlock** -Returns an action object used in signalling that the user has disabled the -publish sidebar. +_Related_ -### lockPostSaving +- updateBlock in core/block-editor store. -Returns an action object used to signal that post saving is locked. +<a name="updateBlockAttributes" href="#updateBlockAttributes">#</a> **updateBlockAttributes** -*Parameters* +_Related_ - * lockName: The lock name. +- updateBlockAttributes in core/block-editor store. -### unlockPostSaving +<a name="updateBlockListSettings" href="#updateBlockListSettings">#</a> **updateBlockListSettings** -Returns an action object used to signal that post saving is unlocked. +_Related_ -*Parameters* +- updateBlockListSettings in core/block-editor store. - * lockName: The lock name. +<a name="updateEditorSettings" href="#updateEditorSettings">#</a> **updateEditorSettings** -### resetEditorBlocks +Undocumented declaration. -Returns an action object used to signal that the blocks have been updated. +<a name="updatePost" href="#updatePost">#</a> **updatePost** + +Returns an action object used in signalling that a patch of updates for the +latest version of the post have been received. + +_Parameters_ + +- _edits_ `Object`: Updated post fields. + +_Returns_ -*Parameters* +- `Object`: Action object. + +<a name="updatePostLock" href="#updatePostLock">#</a> **updatePostLock** + +Returns an action object used to lock the editor. - * blocks: Block Array. - * options: Optional options. +_Parameters_ -### updateEditorSettings +- _lock_ `Object`: Details about the post lock status, user, and nonce. -Returns an action object used in signalling that the post editor settings have been updated. +_Returns_ -*Parameters* +- `Object`: Action object. - * settings: Updated settings \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-notices.md b/docs/designers-developers/developers/data/data-core-notices.md index 5ae7f1fb000679..bb8d4c0e140818 100644 --- a/docs/designers-developers/developers/data/data-core-notices.md +++ b/docs/designers-developers/developers/data/data-core-notices.md @@ -1,91 +1,130 @@ -# **core/notices**: Notices Data +# Notices Data + +Namespace: `core/notices`. ## Selectors -### getNotices +<!-- START TOKEN(Autogenerated selectors) --> + +<a name="getNotices" href="#getNotices">#</a> **getNotices** Returns all notices as an array, optionally for a given context. Defaults to the global context. -*Parameters* +_Parameters_ - * state: Notices state. - * context: Optional grouping context. +- _state_ `Object`: Notices state. +- _context_ `?string`: Optional grouping context. -## Actions +_Returns_ -### createNotice +- `Array<WPNotice>`: Array of notices. -Yields action objects used in signalling that a notice is to be created. -*Parameters* - - * status: Notice status. - Defaults to `info`. - * content: Notice message. - * options: Notice options. - * options.context: Context under which to - group notice. - * options.id: Identifier for notice. - Automatically assigned - if not specified. - * options.isDismissible: Whether the notice can - be dismissed by user. - Defaults to `true`. - * options.speak: Whether the notice - content should be - announced to screen - readers. Defaults to - `true`. - * options.actions: User actions to be - presented with notice. - -### createSuccessNotice +<!-- END TOKEN(Autogenerated selectors) --> -Returns an action object used in signalling that a success notice is to be +## Actions + +<!-- START TOKEN(Autogenerated actions) --> + +<a name="createErrorNotice" href="#createErrorNotice">#</a> **createErrorNotice** + +Returns an action object used in signalling that an error notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ + +- createNotice + +_Parameters_ + +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. + +_Returns_ - * content: Notice message. - * options: Optional notice options. +- `Object`: Action object. -### createInfoNotice +<a name="createInfoNotice" href="#createInfoNotice">#</a> **createInfoNotice** Returns an action object used in signalling that an info notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ - * content: Notice message. - * options: Optional notice options. +- createNotice -### createErrorNotice +_Parameters_ -Returns an action object used in signalling that an error notice is to be +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. + +_Returns_ + +- `Object`: Action object. + +<a name="createNotice" href="#createNotice">#</a> **createNotice** + +Yields action objects used in signalling that a notice is to be created. + +_Parameters_ + +- _status_ `?string`: Notice status. Defaults to `info`. +- _content_ `string`: Notice message. +- _options_ `?Object`: Notice options. +- _options.context_ `?string`: Context under which to group notice. +- _options.id_ `?string`: Identifier for notice. Automatically assigned if not specified. +- _options.isDismissible_ `?boolean`: Whether the notice can be dismissed by user. Defaults to `true`. +- _options.speak_ `?boolean`: Whether the notice content should be announced to screen readers. Defaults to `true`. +- _options.actions_ `?Array<WPNoticeAction>`: User actions to be presented with notice. + +<a name="createSuccessNotice" href="#createSuccessNotice">#</a> **createSuccessNotice** + +Returns an action object used in signalling that a success notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ + +- createNotice + +_Parameters_ - * content: Notice message. - * options: Optional notice options. +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. -### createWarningNotice +_Returns_ + +- `Object`: Action object. + +<a name="createWarningNotice" href="#createWarningNotice">#</a> **createWarningNotice** Returns an action object used in signalling that a warning notice is to be created. Refer to `createNotice` for options documentation. -*Parameters* +_Related_ + +- createNotice + +_Parameters_ + +- _content_ `string`: Notice message. +- _options_ `?Object`: Optional notice options. - * content: Notice message. - * options: Optional notice options. +_Returns_ -### removeNotice +- `Object`: Action object. + +<a name="removeNotice" href="#removeNotice">#</a> **removeNotice** Returns an action object used in signalling that a notice is to be removed. -*Parameters* +_Parameters_ + +- _id_ `string`: Notice unique identifier. +- _context_ `?string`: Optional context (grouping) in which the notice is intended to appear. Defaults to default context. + +_Returns_ + +- `Object`: Action object. - * id: Notice unique identifier. - * context: Optional context (grouping) in which the notice is - intended to appear. Defaults to default context. \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-nux.md b/docs/designers-developers/developers/data/data-core-nux.md index 51f1219f994a9b..e937601ec864b6 100644 --- a/docs/designers-developers/developers/data/data-core-nux.md +++ b/docs/designers-developers/developers/data/data-core-nux.md @@ -1,69 +1,100 @@ -# **core/nux**: The NUX (New User Experience) Data +# The NUX (New User Experience) Data + +Namespace: `core/nux`. ## Selectors -### getAssociatedGuide +<!-- START TOKEN(Autogenerated selectors) --> + +<a name="areTipsEnabled" href="#areTipsEnabled">#</a> **areTipsEnabled** + +Returns whether or not tips are globally enabled. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether tips are globally enabled. + +<a name="getAssociatedGuide" href="#getAssociatedGuide">#</a> **getAssociatedGuide** Returns an object describing the guide, if any, that the given tip is a part of. -*Parameters* +_Parameters_ + +- _state_ `Object`: Global application state. +- _tipId_ `string`: The tip to query. - * state: Global application state. - * tipId: The tip to query. +_Returns_ -### isTipVisible +- `?NUX.GuideInfo`: Information about the associated guide. + +<a name="isTipVisible" href="#isTipVisible">#</a> **isTipVisible** Determines whether or not the given tip is showing. Tips are hidden if they are disabled, have been dismissed, or are not the current tip in any guide that they have been added to. -*Parameters* +_Parameters_ - * state: Global application state. - * tipId: The tip to query. +- _state_ `Object`: Global application state. +- _tipId_ `string`: The tip to query. -*Returns* +_Returns_ -Whether or not the given tip is showing. +- `boolean`: Whether or not the given tip is showing. -### areTipsEnabled -Returns whether or not tips are globally enabled. +<!-- END TOKEN(Autogenerated selectors) --> -*Parameters* +## Actions - * state: Global application state. +<!-- START TOKEN(Autogenerated actions) --> -*Returns* +<a name="disableTips" href="#disableTips">#</a> **disableTips** -Whether tips are globally enabled. +Returns an action object that, when dispatched, prevents all tips from +showing again. -## Actions +_Returns_ -### triggerGuide +- `Object`: Action object. -Returns an action object that, when dispatched, presents a guide that takes -the user through a series of tips step by step. +<a name="dismissTip" href="#dismissTip">#</a> **dismissTip** -*Parameters* +Returns an action object that, when dispatched, dismisses the given tip. A +dismissed tip will not show again. - * tipIds: Which tips to show in the guide. +_Parameters_ -### dismissTip +- _id_ `string`: The tip to dismiss. -Returns an action object that, when dispatched, dismisses the given tip. A -dismissed tip will not show again. +_Returns_ -*Parameters* +- `Object`: Action object. - * id: The tip to dismiss. +<a name="enableTips" href="#enableTips">#</a> **enableTips** -### disableTips +Returns an action object that, when dispatched, makes all tips show again. -Returns an action object that, when dispatched, prevents all tips from -showing again. +_Returns_ + +- `Object`: Action object. + +<a name="triggerGuide" href="#triggerGuide">#</a> **triggerGuide** + +Returns an action object that, when dispatched, presents a guide that takes +the user through a series of tips step by step. + +_Parameters_ + +- _tipIds_ `Array<string>`: Which tips to show in the guide. + +_Returns_ -### enableTips +- `Object`: Action object. -Returns an action object that, when dispatched, makes all tips show again. \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core-viewport.md b/docs/designers-developers/developers/data/data-core-viewport.md index c294c7f3468507..b8f133834ec594 100644 --- a/docs/designers-developers/developers/data/data-core-viewport.md +++ b/docs/designers-developers/developers/data/data-core-viewport.md @@ -1,25 +1,50 @@ -# **core/viewport**: The Viewport Data +# The Viewport Data + +Namespace: `core/viewport`. ## Selectors -### isViewportMatch +<!-- START TOKEN(Autogenerated selectors) --> + +<a name="isViewportMatch" href="#isViewportMatch">#</a> **isViewportMatch** Returns true if the viewport matches the given query, or false otherwise. -*Parameters* +_Usage_ + +```js +isViewportMatch( state, '< huge' ); +isViewPortMatch( state, 'medium' ); +``` + +_Parameters_ + +- _state_ `Object`: Viewport state object. +- _query_ `string`: Query string. Includes operator and breakpoint name, space separated. Operator defaults to >=. + +_Returns_ - * state: Viewport state object. - * query: Query string. Includes operator and breakpoint name, - space separated. Operator defaults to >=. +- `boolean`: Whether viewport matches query. + + +<!-- END TOKEN(Autogenerated selectors) --> ## Actions -### setIsMatching +<!-- START TOKEN(Autogenerated actions) --> + +<a name="setIsMatching" href="#setIsMatching">#</a> **setIsMatching** Returns an action object used in signalling that viewport queries have been updated. Values are specified as an object of breakpoint query keys where value represents whether query matches. -*Parameters* +_Parameters_ + +- _values_ `Object`: Breakpoint query matches. + +_Returns_ + +- `Object`: Action object. - * values: Breakpoint query matches. \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index 57308660d4d630..3f823475fcca1c 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -1,335 +1,385 @@ -# **core**: WordPress Core Data +# WordPress Core Data + +Namespace: `core`. ## Selectors -### isRequestingEmbedPreview +<!-- START TOKEN(Autogenerated selectors) --> -Returns true if a request is in progress for embed preview data, or false -otherwise. +<a name="canUser" href="#canUser">#</a> **canUser** + +Returns whether the current user can perform the given action on the given +REST resource. + +Calling this may trigger an OPTIONS request to the REST API via the +`canUser()` resolver. + +<https://developer.wordpress.org/rest-api/reference/> + +_Parameters_ -*Parameters* +- _state_ `Object`: Data state. +- _action_ `string`: Action to check. One of: 'create', 'read', 'update', 'delete'. +- _resource_ `string`: REST resource to check, e.g. 'media' or 'posts'. +- _id_ `[string]`: Optional ID of the rest resource to check. - * state: Data state. - * url: URL the preview would be for. +_Returns_ -### getAuthors +- `(boolean|undefined)`: Whether or not the user can perform the action, or `undefined` if the OPTIONS request is still being made. + +<a name="getAuthors" href="#getAuthors">#</a> **getAuthors** Returns all available authors. -*Parameters* +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `Array`: Authors list. + +<a name="getAutosave" href="#getAutosave">#</a> **getAutosave** + +Returns the autosave for the post and author. + +_Parameters_ + +- _state_ `Object`: State tree. +- _postType_ `string`: The type of the parent post. +- _postId_ `number`: The id of the parent post. +- _authorId_ `number`: The id of the author. + +_Returns_ + +- `?Object`: The autosave for the post and author. - * state: Data state. +<a name="getAutosaves" href="#getAutosaves">#</a> **getAutosaves** -*Returns* +Returns the latest autosaves for the post. + +May return multiple autosaves since the backend stores one autosave per +author for each post. + +_Parameters_ + +- _state_ `Object`: State tree. +- _postType_ `string`: The type of the parent post. +- _postId_ `number`: The id of the parent post. -Authors list. +_Returns_ -### getCurrentUser +- `?Array`: An array of autosaves for the post, or undefined if there is none. + +<a name="getCurrentUser" href="#getCurrentUser">#</a> **getCurrentUser** Returns the current user. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Current user object. +- `Object`: Current user object. -### getUserQueryResults +<a name="getEmbedPreview" href="#getEmbedPreview">#</a> **getEmbedPreview** -Returns all the users returned by a query ID. +Returns the embed preview for the given URL. -*Parameters* +_Parameters_ - * state: Data state. - * queryID: Query ID. +- _state_ `Object`: Data state. +- _url_ `string`: Embedded URL. -*Returns* +_Returns_ -Users list. +- `*`: Undefined if the preview has not been fetched, otherwise, the preview fetched from the embed preview API. -### getEntitiesByKind +<a name="getEntitiesByKind" href="#getEntitiesByKind">#</a> **getEntitiesByKind** Returns whether the entities for the give kind are loaded. -*Parameters* +_Parameters_ - * state: Data state. - * kind: Entity kind. +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. -*Returns* +_Returns_ -Whether the entities are loaded +- `boolean`: Whether the entities are loaded -### getEntity +<a name="getEntity" href="#getEntity">#</a> **getEntity** Returns the entity object given its kind and name. -*Parameters* +_Parameters_ - * state: Data state. - * kind: Entity kind. - * name: Entity name. +- _state_ `Object`: Data state. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. -*Returns* +_Returns_ -Entity +- `Object`: Entity -### getEntityRecord +<a name="getEntityRecord" href="#getEntityRecord">#</a> **getEntityRecord** Returns the Entity's record object by key. -*Parameters* +_Parameters_ - * state: State tree - * kind: Entity kind. - * name: Entity name. - * key: Record's key +- _state_ `Object`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _key_ `number`: Record's key -*Returns* +_Returns_ -Record. +- `?Object`: Record. -### getEntityRecords +<a name="getEntityRecords" href="#getEntityRecords">#</a> **getEntityRecords** Returns the Entity's records. -*Parameters* +_Parameters_ - * state: State tree - * kind: Entity kind. - * name: Entity name. - * query: Optional terms query. +- _state_ `Object`: State tree +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _query_ `?Object`: Optional terms query. -*Returns* +_Returns_ -Records. +- `Array`: Records. -### getThemeSupports +<a name="getThemeSupports" href="#getThemeSupports">#</a> **getThemeSupports** Return theme supports data in the index. -*Parameters* +_Parameters_ - * state: Data state. +- _state_ `Object`: Data state. -*Returns* +_Returns_ -Index data. +- `*`: Index data. -### getEmbedPreview +<a name="getUserQueryResults" href="#getUserQueryResults">#</a> **getUserQueryResults** -Returns the embed preview for the given URL. +Returns all the users returned by a query ID. -*Parameters* +_Parameters_ - * state: Data state. - * url: Embedded URL. +- _state_ `Object`: Data state. +- _queryID_ `string`: Query ID. -*Returns* +_Returns_ -Undefined if the preview has not been fetched, otherwise, the preview fetched from the embed preview API. +- `Array`: Users list. -### isPreviewEmbedFallback +<a name="hasFetchedAutosaves" href="#hasFetchedAutosaves">#</a> **hasFetchedAutosaves** -Determines if the returned preview is an oEmbed link fallback. +Returns true if the REST request for autosaves has completed. -WordPress can be configured to return a simple link to a URL if it is not embeddable. -We need to be able to determine if a URL is embeddable or not, based on what we -get back from the oEmbed preview API. +_Parameters_ -*Parameters* +- _state_ `Object`: State tree. +- _postType_ `string`: The type of the parent post. +- _postId_ `number`: The id of the parent post. - * state: Data state. - * url: Embedded URL. +_Returns_ -*Returns* +- `boolean`: True if the REST request was completed. False otherwise. -Is the preview for the URL an oEmbed link fallback. +<a name="hasUploadPermissions" href="#hasUploadPermissions">#</a> **hasUploadPermissions** -### hasUploadPermissions (deprecated) +> **Deprecated** since 5.0. Callers should use the more generic `canUser()` selector instead of `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. Returns whether the current user can upload media. Calling this may trigger an OPTIONS request to the REST API via the `canUser()` resolver. -https://developer.wordpress.org/rest-api/reference/ - -*Deprecated* +<https://developer.wordpress.org/rest-api/reference/> -Deprecated since 5.0. Callers should use the more generic `canUser()` selector instead of - `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. +_Parameters_ -*Parameters* +- _state_ `Object`: Data state. - * state: Data state. +_Returns_ -*Returns* +- `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made. -Whether or not the user can upload media. Defaults to `true` if the OPTIONS - request is being made. +<a name="isPreviewEmbedFallback" href="#isPreviewEmbedFallback">#</a> **isPreviewEmbedFallback** -### canUser +Determines if the returned preview is an oEmbed link fallback. -Returns whether the current user can perform the given action on the given -REST resource. +WordPress can be configured to return a simple link to a URL if it is not embeddable. +We need to be able to determine if a URL is embeddable or not, based on what we +get back from the oEmbed preview API. -Calling this may trigger an OPTIONS request to the REST API via the -`canUser()` resolver. +_Parameters_ -https://developer.wordpress.org/rest-api/reference/ +- _state_ `Object`: Data state. +- _url_ `string`: Embedded URL. -*Parameters* +_Returns_ - * state: Data state. - * action: Action to check. One of: 'create', 'read', 'update', 'delete'. - * resource: REST resource to check, e.g. 'media' or 'posts'. - * id: Optional ID of the rest resource to check. +- `booleans`: Is the preview for the URL an oEmbed link fallback. -*Returns* +<a name="isRequestingEmbedPreview" href="#isRequestingEmbedPreview">#</a> **isRequestingEmbedPreview** -Whether or not the user can perform the action, - or `undefined` if the OPTIONS request is still being made. +Returns true if a request is in progress for embed preview data, or false +otherwise. -### getAutosaves +_Parameters_ -Returns the latest autosaves for the post. +- _state_ `Object`: Data state. +- _url_ `string`: URL the preview would be for. -May return multiple autosaves since the backend stores one autosave per -author for each post. +_Returns_ -*Parameters* +- `boolean`: Whether a request is in progress for an embed preview. - * state: State tree. - * postType: The type of the parent post. - * postId: The id of the parent post. -*Returns* +<!-- END TOKEN(Autogenerated selectors) --> -An array of autosaves for the post, or undefined if there is none. +## Actions -### getAutosave +<!-- START TOKEN(Autogenerated actions) --> -Returns the autosave for the post and author. +<a name="addEntities" href="#addEntities">#</a> **addEntities** -*Parameters* +Returns an action object used in adding new entities. - * state: State tree. - * postType: The type of the parent post. - * postId: The id of the parent post. - * authorId: The id of the author. +_Parameters_ -*Returns* +- _entities_ `Array`: Entities received. -The autosave for the post and author. +_Returns_ -### hasFetchedAutosaves +- `Object`: Action object. -Returns true if the REST request for autosaves has completed. +<a name="receiveAutosaves" href="#receiveAutosaves">#</a> **receiveAutosaves** -*Parameters* +Returns an action object used in signalling that the autosaves for a +post have been received. - * state: State tree. - * postType: The type of the parent post. - * postId: The id of the parent post. +_Parameters_ -*Returns* +- _postId_ `number`: The id of the post that is parent to the autosave. +- _autosaves_ `(Array|Object)`: An array of autosaves or singular autosave object. -True if the REST request was completed. False otherwise. +_Returns_ -## Actions +- `Object`: Action object. -### receiveUserQuery +<a name="receiveCurrentUser" href="#receiveCurrentUser">#</a> **receiveCurrentUser** -Returns an action object used in signalling that authors have been received. +Returns an action used in signalling that the current user has been received. -*Parameters* +_Parameters_ - * queryID: Query ID. - * users: Users received. +- _currentUser_ `Object`: Current user object. -### receiveCurrentUser +_Returns_ -Returns an action used in signalling that the current user has been received. +- `Object`: Action object. -*Parameters* +<a name="receiveEmbedPreview" href="#receiveEmbedPreview">#</a> **receiveEmbedPreview** - * currentUser: Current user object. +Returns an action object used in signalling that the preview data for +a given URl has been received. -### addEntities +_Parameters_ -Returns an action object used in adding new entities. +- _url_ `string`: URL to preview the embed for. +- _preview_ `Mixed`: Preview data. -*Parameters* +_Returns_ - * entities: Entities received. +- `Object`: Action object. -### receiveEntityRecords +<a name="receiveEntityRecords" href="#receiveEntityRecords">#</a> **receiveEntityRecords** Returns an action object used in signalling that entity records have been received. -*Parameters* +_Parameters_ + +- _kind_ `string`: Kind of the received entity. +- _name_ `string`: Name of the received entity. +- _records_ `(Array|Object)`: Records received. +- _query_ `?Object`: Query Object. +- _invalidateCache_ `?boolean`: Should invalidate query caches - * kind: Kind of the received entity. - * name: Name of the received entity. - * records: Records received. - * query: Query Object. - * invalidateCache: Should invalidate query caches +_Returns_ -### receiveThemeSupports +- `Object`: Action object. + +<a name="receiveThemeSupports" href="#receiveThemeSupports">#</a> **receiveThemeSupports** Returns an action object used in signalling that the index has been received. -*Parameters* +_Parameters_ - * themeSupports: Theme support for the current theme. +- _themeSupports_ `Object`: Theme support for the current theme. -### receiveEmbedPreview +_Returns_ -Returns an action object used in signalling that the preview data for -a given URl has been received. +- `Object`: Action object. -*Parameters* +<a name="receiveUploadPermissions" href="#receiveUploadPermissions">#</a> **receiveUploadPermissions** - * url: URL to preview the embed for. - * preview: Preview data. +Returns an action object used in signalling that Upload permissions have been received. -### saveEntityRecord +_Parameters_ -Action triggered to save an entity record. +- _hasUploadPermissions_ `boolean`: Does the user have permission to upload files? -*Parameters* +_Returns_ - * kind: Kind of the received entity. - * name: Name of the received entity. - * record: Record to be saved. +- `Object`: Action object. -### receiveUploadPermissions +<a name="receiveUserPermission" href="#receiveUserPermission">#</a> **receiveUserPermission** -Returns an action object used in signalling that Upload permissions have been received. +Returns an action object used in signalling that the current user has +permission to perform an action on a REST resource. -*Parameters* +_Parameters_ - * hasUploadPermissions: Does the user have permission to upload files? +- _key_ `string`: A key that represents the action and REST resource. +- _isAllowed_ `boolean`: Whether or not the user can perform the action. -### receiveUserPermission +_Returns_ -Returns an action object used in signalling that the current user has -permission to perform an action on a REST resource. +- `Object`: Action object. -*Parameters* +<a name="receiveUserQuery" href="#receiveUserQuery">#</a> **receiveUserQuery** - * key: A key that represents the action and REST resource. - * isAllowed: Whether or not the user can perform the action. +Returns an action object used in signalling that authors have been received. -### receiveAutosaves +_Parameters_ -Returns an action object used in signalling that the autosaves for a -post have been received. +- _queryID_ `string`: Query ID. +- _users_ `(Array|Object)`: Users received. + +_Returns_ + +- `Object`: Action object. + +<a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord** + +Action triggered to save an entity record. + +_Parameters_ + +- _kind_ `string`: Kind of the received entity. +- _name_ `string`: Name of the received entity. +- _record_ `Object`: Record to be saved. + +_Returns_ -*Parameters* +- `Object`: Updated record. - * postId: The id of the post that is parent to the autosave. - * autosaves: An array of autosaves or singular autosave object. \ No newline at end of file +<!-- END TOKEN(Autogenerated actions) --> diff --git a/docs/manifest.json b/docs/manifest.json index 9e3806f91b1160..b9f03974dcee07 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -119,6 +119,60 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/README.md", "parent": "developers" }, + { + "title": "WordPress Core Data", + "slug": "data-core", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core.md", + "parent": "data" + }, + { + "title": "Annotations", + "slug": "data-core-annotations", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-annotations.md", + "parent": "data" + }, + { + "title": "Block Types Data", + "slug": "data-core-blocks", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-blocks.md", + "parent": "data" + }, + { + "title": "The Block Editor’s Data", + "slug": "data-core-block-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-block-editor.md", + "parent": "data" + }, + { + "title": "The Post Editor’s Data", + "slug": "data-core-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-editor.md", + "parent": "data" + }, + { + "title": "The Editor’s UI Data", + "slug": "data-core-edit-post", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-edit-post.md", + "parent": "data" + }, + { + "title": "Notices Data", + "slug": "data-core-notices", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-notices.md", + "parent": "data" + }, + { + "title": "The NUX (New User Experience) Data", + "slug": "data-core-nux", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-nux.md", + "parent": "data" + }, + { + "title": "The Viewport Data", + "slug": "data-core-viewport", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-viewport.md", + "parent": "data" + }, { "title": "Packages", "slug": "packages", @@ -1270,59 +1324,5 @@ "slug": "tree-select", "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tree-select/README.md", "parent": "components" - }, - { - "title": "WordPress Core Data", - "slug": "data-core", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core.md", - "parent": "data" - }, - { - "title": "Annotations", - "slug": "data-core-annotations", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-annotations.md", - "parent": "data" - }, - { - "title": "Block Types Data", - "slug": "data-core-blocks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-blocks.md", - "parent": "data" - }, - { - "title": "The Block Editor’s Data", - "slug": "data-core-block-editor", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-block-editor.md", - "parent": "data" - }, - { - "title": "The Post Editor’s Data", - "slug": "data-core-editor", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-editor.md", - "parent": "data" - }, - { - "title": "The Editor’s UI Data", - "slug": "data-core-edit-post", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-edit-post.md", - "parent": "data" - }, - { - "title": "Notices Data", - "slug": "data-core-notices", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-notices.md", - "parent": "data" - }, - { - "title": "The NUX (New User Experience) Data", - "slug": "data-core-nux", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-nux.md", - "parent": "data" - }, - { - "title": "The Viewport Data", - "slug": "data-core-viewport", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/data/data-core-viewport.md", - "parent": "data" } ] \ No newline at end of file diff --git a/docs/toc.json b/docs/toc.json index 774796da6a4fba..e8bbe9f0a6419e 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -20,7 +20,17 @@ {"docs/designers-developers/developers/internationalization.md": []}, {"docs/designers-developers/developers/accessibility.md": []}, {"docs/designers-developers/developers/feature-flags.md": []}, - {"docs/designers-developers/developers/data/README.md": "{{data}}"}, + {"docs/designers-developers/developers/data/README.md": [ + {"docs/designers-developers/developers/data/data-core.md": []}, + {"docs/designers-developers/developers/data/data-core-annotations.md": []}, + {"docs/designers-developers/developers/data/data-core-blocks.md": []}, + {"docs/designers-developers/developers/data/data-core-block-editor.md": []}, + {"docs/designers-developers/developers/data/data-core-editor.md": []}, + {"docs/designers-developers/developers/data/data-core-edit-post.md": []}, + {"docs/designers-developers/developers/data/data-core-notices.md": []}, + {"docs/designers-developers/developers/data/data-core-nux.md": []}, + {"docs/designers-developers/developers/data/data-core-viewport.md": []} + ]}, {"docs/designers-developers/developers/packages.md": "{{packages}}"}, {"packages/components/README.md": "{{components}}"}, {"docs/designers-developers/developers/themes/README.md": [ diff --git a/docs/tool/config.js b/docs/tool/config.js index 6dd2341e3d3c85..70ddebf14bcd30 100644 --- a/docs/tool/config.js +++ b/docs/tool/config.js @@ -4,60 +4,8 @@ const glob = require( 'glob' ).sync; const path = require( 'path' ); -const root = path.resolve( __dirname, '../../' ); - module.exports = { componentPaths: glob( 'packages/components/src/*/**/README.md' ), - dataNamespaces: { - core: { - title: 'WordPress Core Data', - // TODO: Figure out a way to generate docs for dynamic actions/selectors - selectors: [ path.resolve( root, 'packages/core-data/src/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/core-data/src/actions.js' ) ], - }, - 'core/annotations': { - title: 'Annotations', - selectors: [ path.resolve( root, 'packages/annotations/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/annotations/src/store/actions.js' ) ], - }, - 'core/blocks': { - title: 'Block Types Data', - selectors: [ path.resolve( root, 'packages/blocks/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/blocks/src/store/actions.js' ) ], - }, - 'core/block-editor': { - title: 'The Block Editor’s Data', - selectors: [ path.resolve( root, 'packages/block-editor/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/block-editor/src/store/actions.js' ) ], - }, - 'core/editor': { - title: 'The Post Editor’s Data', - selectors: [ path.resolve( root, 'packages/editor/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/editor/src/store/actions.js' ) ], - }, - 'core/edit-post': { - title: 'The Editor’s UI Data', - selectors: [ path.resolve( root, 'packages/edit-post/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/edit-post/src/store/actions.js' ) ], - }, - 'core/notices': { - title: 'Notices Data', - selectors: [ path.resolve( root, 'packages/notices/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/notices/src/store/actions.js' ) ], - }, - 'core/nux': { - title: 'The NUX (New User Experience) Data', - selectors: [ path.resolve( root, 'packages/nux/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/nux/src/store/actions.js' ) ], - }, - 'core/viewport': { - title: 'The Viewport Data', - selectors: [ path.resolve( root, 'packages/viewport/src/store/selectors.js' ) ], - actions: [ path.resolve( root, 'packages/viewport/src/store/actions.js' ) ], - }, - }, - dataDocsOutput: path.resolve( __dirname, '../designers-developers/developers/data' ), - packageFileNames: glob( 'packages/*/package.json' ) .map( ( fileName ) => fileName.split( '/' )[ 1 ] ), diff --git a/docs/tool/generator.js b/docs/tool/generator.js deleted file mode 100644 index f5ee6f584e78f7..00000000000000 --- a/docs/tool/generator.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Node dependencies - */ -const path = require( 'path' ); -const fs = require( 'fs' ); -const { kebabCase } = require( 'lodash' ); - -/** - * Generates the table of contents' markdown. - * - * @param {Object} parsedNamespaces Parsed Namespace Object - * - * @return {string} Markdown string - */ -function generateTableOfContent( parsedNamespaces ) { - return [ - '# Data Module Reference', - '', - Object.values( parsedNamespaces ).map( ( parsedNamespace ) => { - return ` - [**${ parsedNamespace.name }**: ${ parsedNamespace.title }](/docs/designers-developers/developers/data/data-${ kebabCase( parsedNamespace.name ) }.md)`; - } ).join( '\n' ), - ].join( '\n' ); -} - -/** - * Generates the table of contents' markdown. - * - * @param {Object} parsedFunc Parsed Function - * @param {boolean} generateDocsForReturn Whether to generate docs for the return value. - * - * @return {string} Markdown string - */ -function generateFunctionDocs( parsedFunc, generateDocsForReturn = true ) { - return [ - `### ${ parsedFunc.name }${ parsedFunc.deprecated ? ' (deprecated)' : '' }`, - parsedFunc.description ? [ - '', - parsedFunc.description, - ].join( '\n' ) : null, - parsedFunc.deprecated ? [ - '', - '*Deprecated*', - '', - `Deprecated ${ parsedFunc.deprecated.description }`, - ].join( '\n' ) : null, - parsedFunc.params.length ? [ - '', - '*Parameters*', - '', - parsedFunc.params.map( ( param ) => ( - ` * ${ param.name }: ${ param.description }` - ) ).join( '\n' ), - ].join( '\n' ) : null, - parsedFunc.return && generateDocsForReturn ? [ - '', - '*Returns*', - '', - parsedFunc.return.description, - ].join( '\n' ) : null, - ].filter( ( row ) => row !== null ).join( '\n' ); -} - -/** - * Generates the namespace selectors/actions markdown. - * - * @param {Object} parsedNamespace Parsed Namespace - * - * @return {string} Markdown string - */ -function generateNamespaceDocs( parsedNamespace ) { - return [ - `# **${ parsedNamespace.name }**: ${ parsedNamespace.title }`, - '', - '## Selectors', - '', - ( parsedNamespace.selectors.map( generateFunctionDocs ) ).join( '\n\n' ), - '', - '## Actions', - '', - parsedNamespace.actions.map( - ( action ) => generateFunctionDocs( action, false ) - ).join( '\n\n' ), - ].join( '\n' ); -} - -module.exports = function( parsedNamespaces, rootFolder ) { - const tableOfContent = generateTableOfContent( parsedNamespaces ); - fs.writeFileSync( - path.join( rootFolder, 'README.md' ), - tableOfContent - ); - - Object.values( parsedNamespaces ).forEach( ( parsedNamespace ) => { - const namespaceDocs = generateNamespaceDocs( parsedNamespace ); - fs.writeFileSync( - path.join( rootFolder, 'data-' + kebabCase( parsedNamespace.name ) + '.md' ), - namespaceDocs - ); - } ); -}; diff --git a/docs/tool/index.js b/docs/tool/index.js index c0bb2c1101c7e8..5e4244f88437c8 100644 --- a/docs/tool/index.js +++ b/docs/tool/index.js @@ -2,24 +2,23 @@ * Node dependencies */ const fs = require( 'fs' ); +const { join } = require( 'path' ); +const { execSync } = require( 'child_process' ); /** * Internal dependencies */ const config = require( './config' ); -const parser = require( './parser' ); -const generator = require( './generator' ); -const { getPackageManifest, getComponentManifest, getDataManifest, getRootManifest } = require( './manifest' ); +const { getPackageManifest, getComponentManifest, getRootManifest } = require( './manifest' ); -const parsedModules = parser( config.dataNamespaces ); -generator( parsedModules, config.dataDocsOutput ); +// Update data files from code +execSync( join( __dirname, 'update-data.js' ) ); const rootManifest = getRootManifest( config.tocFileName ); const packageManifest = getPackageManifest( config.packageFileNames ); const componentManifest = getComponentManifest( config.componentPaths ); -const dataManifest = getDataManifest( parsedModules ); fs.writeFileSync( config.manifestOutput, - JSON.stringify( rootManifest.concat( packageManifest, componentManifest, dataManifest ), undefined, '\t' ) + JSON.stringify( rootManifest.concat( packageManifest, componentManifest ), undefined, '\t' ) ); diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index f21130e448e111..d6dcfeebc577fd 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -1,7 +1,7 @@ /** * Node dependencies */ -const { camelCase, kebabCase, nth, upperFirst } = require( 'lodash' ); +const { camelCase, nth, upperFirst } = require( 'lodash' ); const fs = require( 'fs' ); @@ -45,25 +45,6 @@ function getComponentManifest( componentPaths ) { } ); } -/** - * Generates the data manifest. - * - * @param {Object} parsedNamespaces Parsed Namespace Object - * - * @return {Array} Manifest - */ -function getDataManifest( parsedNamespaces ) { - return Object.values( parsedNamespaces ).map( ( parsedNamespace ) => { - const slug = `data-${ kebabCase( parsedNamespace.name ) }`; - return { - title: parsedNamespace.title, - slug, - markdown_source: `${ baseRepoUrl }/docs/designers-developers/developers/data/${ slug }.md`, - parent: 'data', - }; - } ); -} - function getRootManifest( tocFileName ) { return generateRootManifestFromTOCItems( require( tocFileName ) ); } @@ -106,6 +87,5 @@ function generateRootManifestFromTOCItems( items, parent = null ) { module.exports = { getPackageManifest, getComponentManifest, - getDataManifest, getRootManifest, }; diff --git a/docs/tool/parser.js b/docs/tool/parser.js deleted file mode 100644 index 12ed0e002b2615..00000000000000 --- a/docs/tool/parser.js +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Node dependencies - */ -const fs = require( 'fs' ); - -/** - * External dependencies - */ -const { last, size, first, some, overEvery, negate, isEmpty } = require( 'lodash' ); -const espree = require( 'espree' ); -const doctrine = require( 'doctrine' ); - -/** - * Returns true if the given Espree parse result node is a documented named - * export declaration, or false otherwise. - * - * @param {Object} node Node to test. - * - * @return {boolean} Whether node is a documented named export declaration. - */ -function isDocumentedNamedExport( node ) { - return ( - node.type === 'ExportNamedDeclaration' && - size( node.leadingComments ) > 0 - ); -} - -/** - * Returns true if the given exported declaration name is considered stable for - * documentation, or false otherwise. - * - * @see https://github.com/WordPress/gutenberg/blob/master/docs/contributors/coding-guidelines.md#experimental-and-unstable-apis - * - * @param {string} name Name to test. - * - * @return {boolean} Whether the provided name describes a stable API. - */ -const isStableExportName = ( name ) => ! /^__(unstable|experimental)/.test( name ); - -/** - * Returns true if the given export name is eligible to be included in - * generated output, or false otherwise. - * - * @type {boolean} Whether name is eligible for documenting. - */ -const isEligibleExportedName = overEvery( [ - negate( isEmpty ), - isStableExportName, -] ); - -/** - * Returns the assigned name for a given declaration node type, or undefined if - * it cannot be determined. - * - * @param {Object} declaration Declaration node. - * - * @return {?string} Exported declaration name. - */ -function getDeclarationExportedName( declaration ) { - let declarator; - switch ( declaration.type ) { - case 'FunctionDeclaration': - declarator = declaration; - break; - - case 'VariableDeclaration': - declarator = first( declaration.declarations ); - } - - if ( declarator ) { - return declarator.id.name; - } -} - -/** - * Returns true if the given DocBlock contains at least one reference to the - * tag named by the provided title, or false otherwise. - * - * @param {Object} docBlock Parsed DocBlock node. - * @param {string} title Title to search. - * - * @return {boolean} Whether DocBlock contains tag by title. - */ -const hasDocBlockTag = ( docBlock, title ) => some( docBlock.tags, { title } ); - -/** - * Returns true if the given DocBlock contains at least one reference to a - * private tag. - * - * @param {Object} docBlock Parsed DocBlock node. - * - * @return {boolean} Whether DocBlock contains private tag. - */ -const hasPrivateTag = ( docBlock ) => hasDocBlockTag( docBlock, 'private' ); - -/** - * Returns true if the given DocBlock contains at least one reference to a - * param tag. - * - * @param {Object} docBlock Parsed DocBlock node. - * - * @return {boolean} Whether DocBlock contains param tag. - */ -const hasParamTag = ( docBlock ) => hasDocBlockTag( docBlock, 'param' ); - -/** - * Returns true if the give DocBlock contains a description. - * - * @param {Object} docBlock Parsed DocBlock node. - * - * @return {boolean} Whether DocBlock contains a description. - */ -const hasDescription = ( docBlock ) => !! docBlock.description; - -/** - * Maps parse type to specific filtering logic by which to consider for - * inclusion a parsed named export. - * - * @type {Object} - */ -const FILTER_PARSED_DOCBLOCK_BY_TYPE = { - /** - * Selectors filter. Excludes documented exports either marked as private - * or which do not include at least one `@param` DocBlock tag. This is used - * to distinguish between selectors (which at least receive state as an - * argument) and exported constant values. - * - * @param {Object} docBlock DocBlock object to test. - * - * @return {boolean} Whether documented selector should be included. - */ - selectors: overEvery( [ hasParamTag, negate( hasPrivateTag ) ] ), - - /** - * Actions filter. Excludes documented exports marked as private. - * - * @param {Object} docBlock DocBlock object to test. - * - * @return {boolean} Whether documented action should be included. - */ - actions: overEvery( [ hasDescription, negate( hasPrivateTag ) ] ), -}; - -module.exports = function( config ) { - const result = {}; - Object.entries( config ).forEach( ( [ namespace, namespaceConfig ] ) => { - const namespaceResult = { - name: namespace, - title: namespaceConfig.title, - selectors: [], - actions: [], - }; - - [ 'selectors', 'actions' ].forEach( ( type ) => { - namespaceConfig[ type ].forEach( ( file ) => { - const code = fs.readFileSync( file, 'utf8' ); - const parsedCode = espree.parse( code, { - attachComment: true, - // This should ideally match our babel config, but espree doesn't support it. - ecmaVersion: 9, - sourceType: 'module', - } ); - - parsedCode.body.forEach( ( node ) => { - if ( ! isDocumentedNamedExport( node ) ) { - return; - } - - const docBlock = doctrine.parse( - last( node.leadingComments ).value, - { unwrap: true, recoverable: true } - ); - - const filter = FILTER_PARSED_DOCBLOCK_BY_TYPE[ type ]; - if ( typeof filter === 'function' && ! filter( docBlock ) ) { - return; - } - - const name = getDeclarationExportedName( node.declaration ); - if ( ! isEligibleExportedName( name ) ) { - return; - } - - const func = { - name, - description: docBlock.description, - deprecated: docBlock.tags.find( ( tag ) => tag.title === 'deprecated' ), - params: docBlock.tags.filter( ( tag ) => tag.title === 'param' ), - return: docBlock.tags.find( ( tag ) => tag.title === 'return' ), - }; - - namespaceResult[ type ].push( func ); - } ); - } ); - } ); - - result[ namespace ] = namespaceResult; - } ); - - return result; -}; diff --git a/docs/tool/update-data.js b/docs/tool/update-data.js new file mode 100755 index 00000000000000..d689fc173fd4f2 --- /dev/null +++ b/docs/tool/update-data.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node + +const { join } = require( 'path' ); +const spawnSync = require( 'child_process' ).spawnSync; + +const modules = [ + [ 'core', { + 'Autogenerated actions': 'packages/core-data/src/actions.js', + 'Autogenerated selectors': 'packages/core-data/src/selectors.js', + } ], + 'core/annotations', + 'core/blocks', + 'core/block-editor', + 'core/editor', + 'core/edit-post', + 'core/notices', + 'core/nux', + 'core/viewport', +]; + +modules.forEach( ( entry ) => { + if ( ! Array.isArray( entry ) ) { + entry = [ entry, { + 'Autogenerated actions': `packages/${ entry.replace( 'core/', '' ) }/src/store/actions.js`, + 'Autogenerated selectors': `packages/${ entry.replace( 'core/', '' ) }/src/store/selectors.js`, + } ]; + } + const [ namespace, targets ] = entry; + + Object.entries( targets ).forEach( ( [ token, target ] ) => { + // Note that this needs to be a sync process for each output file that is updated: + // until docgen provides a way to update many tokens at once, we need to make sure + // the output file is updated before starting the second pass for the next token. + const { status, stderr } = spawnSync( + join( __dirname, '..', '..', 'node_modules', '.bin', 'docgen' ), + [ + target, + `--output docs/designers-developers/developers/data/data-${ namespace.replace( '/', '-' ) }.md`, + '--to-token', + `--use-token "${ token }"`, + '--ignore "/unstable|experimental/i"', + ], + { shell: true }, + ); + + if ( status !== 0 ) { + throw stderr.toString(); + } + } ); +} ); From a2c78a4a1ba590a48f46f796f16d3632cb5f4680 Mon Sep 17 00:00:00 2001 From: ahamed07sajeeb <sajeeb07ahamed@gmail.com> Date: Mon, 6 May 2019 12:21:16 +0600 Subject: [PATCH 028/664] Update readme.md add comma(,) after BlockEditorProvider (#15448) --- packages/block-editor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index d5d432fa944826..40f395faecf3cb 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -16,7 +16,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ```js import { - BlockEditorProvider + BlockEditorProvider, BlockList } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; From ccd5317e8bc76e78109336e724d1a48003702a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Mon, 6 May 2019 11:06:23 +0200 Subject: [PATCH 029/664] Docs: Link the issue for Common Block Functionality --- docs/roadmap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index 869b8273085449..10b92083e2bd0a 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -15,7 +15,7 @@ Gutenberg is already in use by millions of sites through WordPress, so in order - **Block Areas** — build support for areas of blocks that fall outside the content (including relationship with templates, registration, storage, and so on). ([See overview](https://github.com/WordPress/gutenberg/issues/13489).) - **Multi-Block Editing** — allow modifying attributes of multiple blocks of the same kind at once. - **Rich Text Roadmap** — continue to develop the capabilities of the rich text package. ([See overview](https://github.com/WordPress/gutenberg/issues/13778).) -- **Common Block Functionality** — coalesce into a preferred mechanism for creating and sharing chunks of functionality (block alignment, color tools, etc) across blocks with a simple and intuitive code interface. (Suggested exploration: React Hooks.) +- **Common Block Functionality** — coalesce into a preferred mechanism for creating and sharing chunks of functionality (block alignment, color tools, etc) across blocks with a simple and intuitive code interface. (Suggested exploration: React Hooks, [see overview](https://github.com/WordPress/gutenberg/issues/15450).) - **Responsive Images** — propose mechanisms for handling flexible image sources that can be optimized for loading and takes into account their placement on a page (within main content, a column, sidebar, etc). - **Async Loading** — propose a strategy for loading block code only when necessary in the editor without overhead for the developer or disrupting the user experience. - **Styles** — continue to develop the mechanisms for managing block style variations and other styling solutions. (See overview at [#7551](https://github.com/WordPress/gutenberg/issues/7551) and [#9534](https://github.com/WordPress/gutenberg/issues/9534).) From a797ec5567d786604081785cf0e15620dee903dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Mon, 6 May 2019 11:42:33 +0200 Subject: [PATCH 030/664] Testing: Add partial support for Axe verification in e2e tests (#15018) * Testing: Add partial support for Axe verification in e2e tests * Execute build earlier on Travis to make ensure jest-puppeteer-axe can be used * Exclude TinyMCE toolbar from Axe verification in e2e tests * Update tests setup to make all tests pass * Respond to the test instagram URL with a 404 * Revert "Respond to the test instagram URL with a 404" This reverts commit 35b5a5c5b6714d08694a0fae5ce1ba3fc81a0e2f. * Update index.js --- .travis.yml | 17 ++++---- package-lock.json | 1 + packages/e2e-tests/CHANGELOG.md | 6 +++ .../e2e-tests/config/setup-test-framework.js | 40 +++++++++++++++++-- packages/e2e-tests/jest.config.js | 2 + packages/e2e-tests/package.json | 1 + .../e2e-tests/plugins/format-api/index.js | 2 +- .../__snapshots__/format-api.test.js.snap | 2 +- .../plugins/meta-attribute-block.test.js | 9 +++-- packages/jest-puppeteer-axe/CHANGELOG.md | 6 +++ packages/jest-puppeteer-axe/README.md | 6 ++- packages/jest-puppeteer-axe/src/index.js | 24 ++++++----- 12 files changed, 88 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index d0de440677a9f2..b7158c74a1dfe4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,8 +69,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/4) @@ -78,8 +78,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (3/4) @@ -87,8 +87,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (4/4) @@ -96,8 +96,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/4) @@ -105,8 +105,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/4) @@ -114,8 +114,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (3/4) @@ -123,8 +123,8 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (4/4) @@ -132,9 +132,10 @@ jobs: install: - ./bin/setup-local-env.sh script: - - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run build + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) + allow_failures: - name: PHP unit tests (PHP 5.3) env: WP_VERSION=latest SWITCH_TO_PHP=5.3 diff --git a/package-lock.json b/package-lock.json index 2ea228aa9e96f7..e7af47a4f0dbdf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3271,6 +3271,7 @@ "requires": { "@wordpress/e2e-test-utils": "file:packages/e2e-test-utils", "@wordpress/jest-console": "file:packages/jest-console", + "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", "@wordpress/scripts": "file:packages/scripts", "expect-puppeteer": "^4.0.0", "lodash": "^4.17.11" diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 75b6278875283f..397c64f9d17a80 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### New features + +- Added Axe (the Accessibility Engine) API integration with e2e tests suite. + ## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 4800da23a860fd..368f0540920d09 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -6,15 +6,14 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import '@wordpress/jest-console'; import { + activatePlugin, clearLocalStorage, enablePageDialogAccept, setBrowserViewport, - visitAdminPage, - activatePlugin, switchUserToAdmin, switchUserToTest, + visitAdminPage, } from '@wordpress/e2e-test-utils'; /** @@ -148,6 +147,40 @@ function observeConsoleLogging() { } ); } +/** + * Runs Axe tests when the block editor is found on the current page. + * + * @return {?Promise} Promise resolving once Axe texts are finished. + */ +async function runAxeTestsForBlockEditor() { + if ( ! await page.$( '.block-editor' ) ) { + return; + } + + await expect( page ).toPassAxeTests( { + // Temporary disabled rules to enable initial integration. + // See: https://github.com/WordPress/gutenberg/pull/15018. + disabledRules: [ + 'aria-allowed-role', + 'aria-valid-attr-value', + 'button-name', + 'color-contrast', + 'dlitem', + 'duplicate-id', + 'label', + 'link-name', + 'listitem', + 'region', + ], + exclude: [ + // Ignores elements created by metaboxes. + '.edit-post-layout__metaboxes', + // Ignores elements created by TinyMCE. + '.mce-container', + ], + } ); +} + // Before every test suite run, delete all content created by the test. This ensures // other posts/comments/etc. aren't dirtying tests and tests don't depend on // each other's side-effects. @@ -162,6 +195,7 @@ beforeAll( async () => { } ); afterEach( async () => { + await runAxeTestsForBlockEditor(); await setupBrowser(); } ); diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index ea4d99ce56ac13..eeb88d9aa0daf0 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -5,6 +5,8 @@ module.exports = { ], setupFilesAfterEnv: [ '<rootDir>/config/setup-test-framework.js', + '@wordpress/jest-console', + '@wordpress/jest-puppeteer-axe', 'expect-puppeteer', ], }; diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 98a3e325e3a0ce..302aa7eaf57fb3 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -24,6 +24,7 @@ "dependencies": { "@wordpress/e2e-test-utils": "file:../e2e-test-utils", "@wordpress/jest-console": "file:../jest-console", + "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", "@wordpress/scripts": "file:../scripts", "expect-puppeteer": "^4.0.0", "lodash": "^4.17.11" diff --git a/packages/e2e-tests/plugins/format-api/index.js b/packages/e2e-tests/plugins/format-api/index.js index 4d41f7c26c4b3a..671767e2049269 100644 --- a/packages/e2e-tests/plugins/format-api/index.js +++ b/packages/e2e-tests/plugins/format-api/index.js @@ -18,7 +18,7 @@ props.value, { type: 'my-plugin/link', attributes: { - url: '#test', + url: 'https://example.com', } } ) diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap index 20a79ccc33efb0..e5a006328d0971 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap @@ -2,6 +2,6 @@ exports[`Using Format API Clicking the control wraps the selected text properly with HTML code 1`] = ` "<!-- wp:paragraph --> -<p>First <a href=\\"#test\\" class=\\"my-plugin-link\\">paragraph</a></p> +<p>First <a href=\\"https://example.com\\" class=\\"my-plugin-link\\">paragraph</a></p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js index 45c82561599797..5de9ea1f6c619b 100644 --- a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js +++ b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js @@ -41,13 +41,14 @@ describe( 'Block with a meta attribute', () => { await page.keyboard.type( 'Meta Value' ); const inputs = await page.$$( '.my-meta-input' ); - await inputs.forEach( async ( input ) => { + await Promise.all( inputs.map( async ( input ) => { // Clicking the input selects the block, // and selecting the block enables the sync data mode - // as otherwise the asynchronous rerendering of unselected blocks + // as otherwise the asynchronous re-rendering of unselected blocks // may cause the input to have not yet been updated for the other blocks await input.click(); - expect( await input.getProperty( 'value' ) ).toBe( 'Meta Value' ); - } ); + const inputValue = await input.getProperty( 'value' ); + expect( await inputValue.jsonValue() ).toBe( 'Meta Value' ); + } ) ); } ); } ); diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index 2256a6340e4fcc..c8e5368f48496e 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### New features + +- Added optional `disabledRules` option to use with `toPassAxeTests` matcher. + ## 1.0.0 (2019-03-06) - Initial release. diff --git a/packages/jest-puppeteer-axe/README.md b/packages/jest-puppeteer-axe/README.md index 29fd06538ed475..c28b7ed96f752c 100644 --- a/packages/jest-puppeteer-axe/README.md +++ b/packages/jest-puppeteer-axe/README.md @@ -38,8 +38,9 @@ test( 'checks the test page with Axe', async () => { ``` It is also possible to pass optional Axe API options to perform customized check: -- `include` - CSS selector to to add the list of elements to include in analysis. -- `exclude` - CSS selector to to add the list of elements to exclude from analysis. +- `include` - CSS selector(s) to to add the list of elements to include in analysis. +- `exclude` - CSS selector(s) to to add the list of elements to exclude from analysis. +- `disabledRules` - the list of [Axe rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) to skip from verification. ```js test( 'checks the test component with Axe excluding some button', async () => { @@ -50,6 +51,7 @@ test( 'checks the test component with Axe excluding some button', async () => { await expect( page ).toPassAxeTests( { include: '.test-component', exclude: '.some-button', + disabledRules: [ 'aria-allowed-role' ], } ); } ); ``` diff --git a/packages/jest-puppeteer-axe/src/index.js b/packages/jest-puppeteer-axe/src/index.js index ccf1be7cf0a641..336cec9bcf8161 100644 --- a/packages/jest-puppeteer-axe/src/index.js +++ b/packages/jest-puppeteer-axe/src/index.js @@ -11,8 +11,9 @@ import AxePuppeteer from 'axe-puppeteer'; * @return {string} The user friendly message to display when the matcher fails. */ function formatViolations( violations ) { - return violations.map( ( { help, id, nodes } ) => { - let output = `Rule: ${ id } (${ help })\n` + + return violations.map( ( { help, helpUrl, id, nodes } ) => { + let output = `Rule: "${ id }" (${ help })\n` + + `Help: ${ helpUrl }\n` + 'Affected Nodes:\n'; nodes.forEach( ( node ) => { @@ -52,16 +53,17 @@ function formatViolations( violations ) { * * @see https://github.com/dequelabs/axe-puppeteer * - * @param {Page} page Puppeteer's page instance. - * @param {?Object} params Optional Axe API options. - * @param {?string} params.include CSS selector to add to the list of elements - * to include in analysis. - * @param {?string} params.exclude CSS selector to add to the list of elements - * to exclude from analysis. + * @param {Page} page Puppeteer's page instance. + * @param {?Object} params Optional Axe API options. + * @param {?string|Array} params.include CSS selector(s) to add to the list of elements + * to include in analysis. + * @param {?string|Array} params.exclude CSS selector(s) to add to the list of elements + * to exclude from analysis. + * @param {?Array} params.disabledRules The list of Axe rules to skip from verification. * * @return {Object} A matcher object with two keys `pass` and `message`. */ -async function toPassAxeTests( page, { include, exclude } = {} ) { +async function toPassAxeTests( page, { include, exclude, disabledRules } = {} ) { const axe = new AxePuppeteer( page ); if ( include ) { @@ -72,6 +74,10 @@ async function toPassAxeTests( page, { include, exclude } = {} ) { axe.exclude( exclude ); } + if ( disabledRules ) { + axe.disableRules( disabledRules ); + } + const { violations } = await axe.analyze(); const pass = violations.length === 0; From d137b09f7e987abe13a4a6913c5d75e9882c86bf Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Mon, 6 May 2019 18:55:47 +0800 Subject: [PATCH 031/664] Reinstate correct cover block attributes for deprecations (#15449) * Reinstate correct cover block attributes for deprecations * Add fixtures for deprecated cover block * Add fixtures for all deprecations of cover block * Upgrade second deprecation to migrate to inner blocks version of cover block * Fix oldest cover block deprecation --- .../block-library/src/cover/deprecated.js | 74 ++++++++++++++----- .../blocks/core__cover__deprecated-1.html | 3 + .../blocks/core__cover__deprecated-1.json | 30 ++++++++ .../core__cover__deprecated-1.parsed.json | 23 ++++++ .../core__cover__deprecated-1.serialized.html | 5 ++ .../blocks/core__cover__deprecated-2.html | 3 + .../blocks/core__cover__deprecated-2.json | 30 ++++++++ .../core__cover__deprecated-2.parsed.json | 23 ++++++ .../core__cover__deprecated-2.serialized.html | 5 ++ .../blocks/core__cover__deprecated-3.html | 3 + .../blocks/core__cover__deprecated-3.json | 30 ++++++++ .../core__cover__deprecated-3.parsed.json | 23 ++++++ .../core__cover__deprecated-3.serialized.html | 5 ++ 13 files changed, 237 insertions(+), 20 deletions(-) create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index 8dbbede5c788de..ef1a854918af14 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -27,32 +27,30 @@ import { const blockAttributes = { url: { type: 'string', - source: 'attribute', - selector: 'a', - attribute: 'href', }, - title: { - type: 'string', - source: 'attribute', - selector: 'a', - attribute: 'title', + id: { + type: 'number', }, - text: { - type: 'string', - source: 'html', - selector: 'a', + hasParallax: { + type: 'boolean', + default: false, }, - backgroundColor: { - type: 'string', + dimRatio: { + type: 'number', + default: 50, }, - textColor: { + overlayColor: { type: 'string', }, - customBackgroundColor: { + customOverlayColor: { type: 'string', }, - customTextColor: { + backgroundType: { type: 'string', + default: 'image', + }, + focalPoint: { + type: 'object', }, }; @@ -184,26 +182,46 @@ const deprecated = [ </div> ); }, + migrate( attributes ) { + return [ + omit( attributes, [ 'title', 'contentAlign', 'align' ] ), + [ + createBlock( + 'core/paragraph', + { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } + ), + ], + ]; + }, }, { attributes: { ...blockAttributes, - align: { - type: 'string', - }, title: { type: 'string', source: 'html', selector: 'h2', }, + align: { + type: 'string', + }, contentAlign: { type: 'string', default: 'center', }, }, + supports: { + className: false, + }, save( { attributes } ) { const { url, title, hasParallax, dimRatio, align } = attributes; const style = backgroundImageStyles( url ); const classes = classnames( + 'wp-block-cover-image', dimRatioToClass( dimRatio ), { 'has-background-dim': dimRatio !== 0, @@ -218,6 +236,22 @@ const deprecated = [ </section> ); }, + migrate( attributes ) { + return [ + omit( attributes, [ 'title', 'contentAlign', 'align' ] ), + [ + createBlock( + 'core/paragraph', + { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } + ), + ], + ]; + }, }, ]; diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html new file mode 100644 index 00000000000000..6eab178d72268d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.html @@ -0,0 +1,3 @@ +<!-- wp:cover-image {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<section class="wp-block-cover-image has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><h2><strong>Cover Image</strong></h2></section> +<!-- /wp:cover-image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json new file mode 100644 index 00000000000000..204a94f1b475c4 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34, + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "<strong>Cover Image</strong>", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [] + } + ], + "originalContent": "<section class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><h2><strong>Cover Image</strong></h2></section>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json new file mode 100644 index 00000000000000..793fcc3ec4dda8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/cover-image", + "attrs": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34 + }, + "innerBlocks": [], + "innerHTML": "\n<section class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><h2><strong>Cover Image</strong></h2></section>\n", + "innerContent": [ + "\n<section class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><h2><strong>Cover Image</strong></h2></section>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html new file mode 100644 index 00000000000000..8c80c4c53a580b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><strong>Cover Image</strong></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html new file mode 100644 index 00000000000000..efbb570a741902 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.html @@ -0,0 +1,3 @@ +<!-- wp:cover-image {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<div class="wp-block-cover-image has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><p class="wp-block-cover-image-text"><strong>Cover Block</strong></p></div> +<!-- /wp:cover-image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json new file mode 100644 index 00000000000000..52c013899822f7 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34, + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "<strong>Cover Block</strong>", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [] + } + ], + "originalContent": "<div class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-image-text\"><strong>Cover Block</strong></p></div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json new file mode 100644 index 00000000000000..12397136f925fa --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/cover-image", + "attrs": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 34 + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-image-text\"><strong>Cover Block</strong></p></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover-image has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-image-text\"><strong>Cover Block</strong></p></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html new file mode 100644 index 00000000000000..90849ff27c4565 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><strong>Cover Block</strong></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html new file mode 100644 index 00000000000000..1867ebcc9f60f9 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.html @@ -0,0 +1,3 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":35} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><p class="wp-block-cover-text"><strong>Cover Block</strong></p></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json new file mode 100644 index 00000000000000..b43390fb0c165c --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 35, + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "<strong>Cover Block</strong>", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [] + } + ], + "originalContent": "<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-text\"><strong>Cover Block</strong></p></div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json new file mode 100644 index 00000000000000..208b4f812ab7b6 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/cover", + "attrs": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "id": 35 + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-text\"><strong>Cover Block</strong></p></div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover has-background-dim\" style=\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\"><p class=\"wp-block-cover-text\"><strong>Cover Block</strong></p></div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html new file mode 100644 index 00000000000000..a85404c31646ad --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":35} --> +<div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p style="text-align:center" class="has-large-font-size"><strong>Cover Block</strong></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> From c6d855aa990a7399a1e95a2b37e74b9fef257a94 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal <mukeshpanchal27@users.noreply.github.com> Date: Mon, 6 May 2019 16:40:18 +0530 Subject: [PATCH 032/664] Fix end tag (#15454) --- .../block-tutorial/applying-styles-with-stylesheets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md index dff770151e8e9c..76100adfcd404e 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md @@ -50,7 +50,7 @@ registerBlockType( 'gutenberg-examples/example-02-stylesheets', { }, save() { - return <p>Hello World, step 2 (from the frontend, in red)./p>; + return <p>Hello World, step 2 (from the frontend, in red).</p>; } } ); ``` From 4021055868d9a9a1cc2481a55899ef22e3e021ef Mon Sep 17 00:00:00 2001 From: Jon Surrell <jon.surrell@automattic.com> Date: Mon, 6 May 2019 13:37:19 +0200 Subject: [PATCH 033/664] Webpack dependency plugin: Fix filename function handling (#15430) * Add failing test for #15410 * Move .js to .deps.json replacement to path result Replacement in the output.filename template works fine with a string filename. However, webpack also accepts a function filename, which causes errors when the replacement is attempted. Move the replacement to the result of `getPath`, when we should reliably have a string. Fixes #15410 --- .../index.js | 9 +++--- .../test/__snapshots__/build.js.snap | 29 +++++++++++++++++++ .../function-output-filename/index.js | 11 +++++++ .../webpack.config.js | 10 +++++++ 4 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js create mode 100644 packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js diff --git a/packages/dependency-extraction-webpack-plugin/index.js b/packages/dependency-extraction-webpack-plugin/index.js index f99ec61fa27baf..677f67e34817b3 100644 --- a/packages/dependency-extraction-webpack-plugin/index.js +++ b/packages/dependency-extraction-webpack-plugin/index.js @@ -104,9 +104,8 @@ class DependencyExtractionWebpackPlugin { // Determine a filename for the `[entrypoint].deps.json` file. const [ filename, query ] = entrypointName.split( '?', 2 ); - const depsFilename = compilation.getPath( - outputFilename.replace( /\.js$/i, '.deps.json' ), - { + const depsFilename = compilation + .getPath( outputFilename, { chunk: entrypoint.getRuntimeChunk(), filename, query, @@ -114,8 +113,8 @@ class DependencyExtractionWebpackPlugin { contentHash: createHash( 'md4' ) .update( depsString ) .digest( 'hex' ), - } - ); + } ) + .replace( /\.js$/i, '.deps.json' ); // Add source and file into compilation for webpack to output. compilation.assets[ depsFilename ] = new RawSource( depsString ); diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index 37d144bb4a6397..b9759ab6ae8dfd 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -29,6 +29,35 @@ Array [ ] `; +exports[`Webpack \`function-output-filename\` should produce expected output: Dependencies JSON should match snapshot 1`] = ` +Array [ + "lodash", + "wp-blob", +] +`; + +exports[`Webpack \`function-output-filename\` should produce expected output: External modules should match snapshot 1`] = ` +Array [ + Object { + "externalType": "this", + "request": Object { + "this": Array [ + "wp", + "blob", + ], + }, + "userRequest": "@wordpress/blob", + }, + Object { + "externalType": "this", + "request": Object { + "this": "lodash", + }, + "userRequest": "lodash", + }, +] +`; + exports[`Webpack \`no-default\` should produce expected output: Dependencies JSON should match snapshot 1`] = `Array []`; exports[`Webpack \`no-default\` should produce expected output: External modules should match snapshot 1`] = `Array []`; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js new file mode 100644 index 00000000000000..917b4cd7e204bf --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/index.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { isBlobURL } from '@wordpress/blob'; + +/** + * External dependencies + */ +import _ from 'lodash'; + +_.isEmpty( isBlobURL( '' ) ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js new file mode 100644 index 00000000000000..1d533980ad52be --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js @@ -0,0 +1,10 @@ +const DependencyExtractionWebpackPlugin = require( '../../..' ); + +module.exports = { + output: { + filename( chunkData ) { + return `chunk--${ chunkData.chunk.name }--[name].js`; + }, + }, + plugins: [ new DependencyExtractionWebpackPlugin() ], +}; From be8add923f23f202a308efd7c8d9fd9086bef7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Mon, 6 May 2019 16:01:46 +0200 Subject: [PATCH 034/664] Refactor File block to use block.json metadata (#14862) * Refactor File block to use block.json metadata * Refactor edit to set the default value for download button label --- packages/block-library/src/file/block.json | 36 ++- packages/block-library/src/file/edit.js | 12 +- packages/block-library/src/file/index.js | 222 +----------------- packages/block-library/src/file/save.js | 42 ++++ packages/block-library/src/file/transforms.js | 138 +++++++++++ 5 files changed, 229 insertions(+), 221 deletions(-) create mode 100644 packages/block-library/src/file/save.js create mode 100644 packages/block-library/src/file/transforms.js diff --git a/packages/block-library/src/file/block.json b/packages/block-library/src/file/block.json index df285cfb60541d..0c36a06aa9a55e 100644 --- a/packages/block-library/src/file/block.json +++ b/packages/block-library/src/file/block.json @@ -1,4 +1,38 @@ { "name": "core/file", - "category": "common" + "category": "common", + "attributes": { + "id": { + "type": "number" + }, + "href": { + "type": "string" + }, + "fileName": { + "type": "string", + "source": "html", + "selector": "a:not([download])" + }, + "textLinkHref": { + "type": "string", + "source": "attribute", + "selector": "a:not([download])", + "attribute": "href" + }, + "textLinkTarget": { + "type": "string", + "source": "attribute", + "selector": "a:not([download])", + "attribute": "target" + }, + "showDownloadButton": { + "type": "boolean", + "default": true + }, + "downloadButtonText": { + "type": "string", + "source": "html", + "selector": "a[download]" + } + } } diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 3ce7211eceb854..9aa76977cc2cec 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -29,7 +29,7 @@ import { } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; import { Component, Fragment } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; /** * Internal dependencies @@ -55,8 +55,8 @@ class FileEdit extends Component { } componentDidMount() { - const { attributes, noticeOperations } = this.props; - const { href } = attributes; + const { attributes, noticeOperations, setAttributes } = this.props; + const { downloadButtonText, href } = attributes; // Upload a file drag-and-dropped into the editor if ( isBlobURL( href ) ) { @@ -73,6 +73,12 @@ class FileEdit extends Component { revokeBlobURL( href ); } + + if ( downloadButtonText === undefined ) { + setAttributes( { + downloadButtonText: _x( 'Download', 'button label' ), + } ); + } } componentDidUpdate( prevProps ) { diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 57d6fe5bc2c2b8..86efc4291d56f9 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -1,16 +1,7 @@ -/** - * External dependencies - */ -import { includes } from 'lodash'; - /** * WordPress dependencies */ -import { __, _x } from '@wordpress/i18n'; -import { createBlobURL } from '@wordpress/blob'; -import { createBlock } from '@wordpress/blocks'; -import { select } from '@wordpress/data'; -import { RichText } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -18,6 +9,8 @@ import { RichText } from '@wordpress/block-editor'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import save from './save'; +import transforms from './transforms'; const { name } = metadata; @@ -25,218 +18,13 @@ export { metadata, name }; export const settings = { title: __( 'File' ), - description: __( 'Add a link to a downloadable file.' ), - icon, - keywords: [ __( 'document' ), __( 'pdf' ) ], - - attributes: { - id: { - type: 'number', - }, - href: { - type: 'string', - }, - fileName: { - type: 'string', - source: 'html', - selector: 'a:not([download])', - }, - // Differs to the href when the block is configured to link to the attachment page - textLinkHref: { - type: 'string', - source: 'attribute', - selector: 'a:not([download])', - attribute: 'href', - }, - // e.g. `_blank` when the block is configured to open in a new tab - textLinkTarget: { - type: 'string', - source: 'attribute', - selector: 'a:not([download])', - attribute: 'target', - }, - showDownloadButton: { - type: 'boolean', - default: true, - }, - downloadButtonText: { - type: 'string', - source: 'html', - selector: 'a[download]', - default: _x( 'Download', 'button label' ), - }, - }, - supports: { align: true, }, - - transforms: { - from: [ - { - type: 'files', - isMatch( files ) { - return files.length > 0; - }, - // We define a lower priorty (higher number) than the default of 10. This - // ensures that the File block is only created as a fallback. - priority: 15, - transform: ( files ) => { - const blocks = []; - - files.forEach( ( file ) => { - const blobURL = createBlobURL( file ); - - // File will be uploaded in componentDidMount() - blocks.push( createBlock( 'core/file', { - href: blobURL, - fileName: file.name, - textLinkHref: blobURL, - } ) ); - } ); - - return blocks; - }, - }, - { - type: 'block', - blocks: [ 'core/audio' ], - transform: ( attributes ) => { - return createBlock( 'core/file', { - href: attributes.src, - fileName: attributes.caption, - textLinkHref: attributes.src, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/video' ], - transform: ( attributes ) => { - return createBlock( 'core/file', { - href: attributes.src, - fileName: attributes.caption, - textLinkHref: attributes.src, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/image' ], - transform: ( attributes ) => { - return createBlock( 'core/file', { - href: attributes.url, - fileName: attributes.caption, - textLinkHref: attributes.url, - id: attributes.id, - } ); - }, - }, - ], - to: [ - { - type: 'block', - blocks: [ 'core/audio' ], - isMatch: ( { id } ) => { - if ( ! id ) { - return false; - } - const { getMedia } = select( 'core' ); - const media = getMedia( id ); - return !! media && includes( media.mime_type, 'audio' ); - }, - transform: ( attributes ) => { - return createBlock( 'core/audio', { - src: attributes.href, - caption: attributes.fileName, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/video' ], - isMatch: ( { id } ) => { - if ( ! id ) { - return false; - } - const { getMedia } = select( 'core' ); - const media = getMedia( id ); - return !! media && includes( media.mime_type, 'video' ); - }, - transform: ( attributes ) => { - return createBlock( 'core/video', { - src: attributes.href, - caption: attributes.fileName, - id: attributes.id, - } ); - }, - }, - { - type: 'block', - blocks: [ 'core/image' ], - isMatch: ( { id } ) => { - if ( ! id ) { - return false; - } - const { getMedia } = select( 'core' ); - const media = getMedia( id ); - return !! media && includes( media.mime_type, 'image' ); - }, - transform: ( attributes ) => { - return createBlock( 'core/image', { - url: attributes.href, - caption: attributes.fileName, - id: attributes.id, - } ); - }, - }, - ], - }, - + transforms, edit, - - save( { attributes } ) { - const { - href, - fileName, - textLinkHref, - textLinkTarget, - showDownloadButton, - downloadButtonText, - } = attributes; - - return ( href && - <div> - { ! RichText.isEmpty( fileName ) && - <a - href={ textLinkHref } - target={ textLinkTarget } - rel={ textLinkTarget ? 'noreferrer noopener' : false } - > - <RichText.Content - value={ fileName } - /> - </a> - } - { showDownloadButton && - <a - href={ href } - className="wp-block-file__button" - download={ true } - > - <RichText.Content - value={ downloadButtonText } - /> - </a> - } - </div> - ); - }, - + save, }; diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js new file mode 100644 index 00000000000000..461018e4bb8a7c --- /dev/null +++ b/packages/block-library/src/file/save.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +export default function save( { attributes } ) { + const { + href, + fileName, + textLinkHref, + textLinkTarget, + showDownloadButton, + downloadButtonText, + } = attributes; + + return ( href && + <div> + { ! RichText.isEmpty( fileName ) && + <a + href={ textLinkHref } + target={ textLinkTarget } + rel={ textLinkTarget ? 'noreferrer noopener' : false } + > + <RichText.Content + value={ fileName } + /> + </a> + } + { showDownloadButton && + <a + href={ href } + className="wp-block-file__button" + download={ true } + > + <RichText.Content + value={ downloadButtonText } + /> + </a> + } + </div> + ); +} diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js new file mode 100644 index 00000000000000..a37aedfb45fcec --- /dev/null +++ b/packages/block-library/src/file/transforms.js @@ -0,0 +1,138 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createBlobURL } from '@wordpress/blob'; +import { createBlock } from '@wordpress/blocks'; +import { select } from '@wordpress/data'; + +const transforms = { + from: [ + { + type: 'files', + isMatch( files ) { + return files.length > 0; + }, + // We define a lower priorty (higher number) than the default of 10. This + // ensures that the File block is only created as a fallback. + priority: 15, + transform: ( files ) => { + const blocks = []; + + files.forEach( ( file ) => { + const blobURL = createBlobURL( file ); + + // File will be uploaded in componentDidMount() + blocks.push( createBlock( 'core/file', { + href: blobURL, + fileName: file.name, + textLinkHref: blobURL, + } ) ); + } ); + + return blocks; + }, + }, + { + type: 'block', + blocks: [ 'core/audio' ], + transform: ( attributes ) => { + return createBlock( 'core/file', { + href: attributes.src, + fileName: attributes.caption, + textLinkHref: attributes.src, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/video' ], + transform: ( attributes ) => { + return createBlock( 'core/file', { + href: attributes.src, + fileName: attributes.caption, + textLinkHref: attributes.src, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/image' ], + transform: ( attributes ) => { + return createBlock( 'core/file', { + href: attributes.url, + fileName: attributes.caption, + textLinkHref: attributes.url, + id: attributes.id, + } ); + }, + }, + ], + to: [ + { + type: 'block', + blocks: [ 'core/audio' ], + isMatch: ( { id } ) => { + if ( ! id ) { + return false; + } + const { getMedia } = select( 'core' ); + const media = getMedia( id ); + return !! media && includes( media.mime_type, 'audio' ); + }, + transform: ( attributes ) => { + return createBlock( 'core/audio', { + src: attributes.href, + caption: attributes.fileName, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/video' ], + isMatch: ( { id } ) => { + if ( ! id ) { + return false; + } + const { getMedia } = select( 'core' ); + const media = getMedia( id ); + return !! media && includes( media.mime_type, 'video' ); + }, + transform: ( attributes ) => { + return createBlock( 'core/video', { + src: attributes.href, + caption: attributes.fileName, + id: attributes.id, + } ); + }, + }, + { + type: 'block', + blocks: [ 'core/image' ], + isMatch: ( { id } ) => { + if ( ! id ) { + return false; + } + const { getMedia } = select( 'core' ); + const media = getMedia( id ); + return !! media && includes( media.mime_type, 'image' ); + }, + transform: ( attributes ) => { + return createBlock( 'core/image', { + url: attributes.href, + caption: attributes.fileName, + id: attributes.id, + } ); + }, + }, + ], +}; + +export default transforms; From a90752ee9f896c712b2617adb14c4450712f7597 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 6 May 2019 15:03:03 +0100 Subject: [PATCH 035/664] Fix: Server side rendered blocks are not parsed on the widgets screen. (#15414) Server-side rendered blocks are not being correctly parsed on widgets screen making legacy widget blocks not load correctly on #15074. The problem was that the server side block definitions were not being bootstrapped on the widgets page. --- lib/widgets-page.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/widgets-page.php b/lib/widgets-page.php index a1c6f5a84c1e11..cc63e767cea90c 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -33,6 +33,11 @@ function gutenberg_widgets_init( $hook ) { 'wp-edit-widgets', 'wp.editWidgets.initialize( "widgets-editor" );' ); + // Preload server-registered block schemas. + wp_add_inline_script( + 'wp-blocks', + 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');' + ); wp_enqueue_script( 'wp-edit-widgets' ); wp_enqueue_style( 'wp-edit-widgets' ); } From 06c9f4a1ce5c2e4de4b7e3cc6a934b6c4dfc337e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Mon, 6 May 2019 17:11:45 +0200 Subject: [PATCH 036/664] Remove redundant (duplicated) reducers (#15142) --- packages/editor/src/store/reducer.js | 235 --------------------------- 1 file changed, 235 deletions(-) diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index fffa2647578827..a6f2bd4a4565ac 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -9,7 +9,6 @@ import { mapValues, keys, isEqual, - last, } from 'lodash'; /** @@ -279,200 +278,6 @@ export function currentPost( state = {}, action ) { return state; } -/** - * Reducer returning typing state. - * - * @param {boolean} state Current state. - * @param {Object} action Dispatched action. - * - * @return {boolean} Updated state. - */ -export function isTyping( state = false, action ) { - switch ( action.type ) { - case 'START_TYPING': - return true; - - case 'STOP_TYPING': - return false; - } - - return state; -} - -/** - * Reducer returning whether the caret is within formatted text. - * - * @param {boolean} state Current state. - * @param {Object} action Dispatched action. - * - * @return {boolean} Updated state. - */ -export function isCaretWithinFormattedText( state = false, action ) { - switch ( action.type ) { - case 'ENTER_FORMATTED_TEXT': - return true; - - case 'EXIT_FORMATTED_TEXT': - return false; - } - - return state; -} - -/** - * Reducer returning the block selection's state. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export function blockSelection( state = { - start: null, - end: null, - isMultiSelecting: false, - isEnabled: true, - initialPosition: null, -}, action ) { - switch ( action.type ) { - case 'CLEAR_SELECTED_BLOCK': - if ( state.start === null && state.end === null && ! state.isMultiSelecting ) { - return state; - } - - return { - ...state, - start: null, - end: null, - isMultiSelecting: false, - initialPosition: null, - }; - case 'START_MULTI_SELECT': - if ( state.isMultiSelecting ) { - return state; - } - - return { - ...state, - isMultiSelecting: true, - initialPosition: null, - }; - case 'STOP_MULTI_SELECT': - if ( ! state.isMultiSelecting ) { - return state; - } - - return { - ...state, - isMultiSelecting: false, - initialPosition: null, - }; - case 'MULTI_SELECT': - return { - ...state, - start: action.start, - end: action.end, - initialPosition: null, - }; - case 'SELECT_BLOCK': - if ( action.clientId === state.start && action.clientId === state.end ) { - return state; - } - return { - ...state, - start: action.clientId, - end: action.clientId, - initialPosition: action.initialPosition, - }; - case 'INSERT_BLOCKS': { - if ( action.updateSelection ) { - return { - ...state, - start: action.blocks[ 0 ].clientId, - end: action.blocks[ 0 ].clientId, - initialPosition: null, - isMultiSelecting: false, - }; - } - return state; - } - case 'REMOVE_BLOCKS': - if ( ! action.clientIds || ! action.clientIds.length || action.clientIds.indexOf( state.start ) === -1 ) { - return state; - } - return { - ...state, - start: null, - end: null, - initialPosition: null, - isMultiSelecting: false, - }; - case 'REPLACE_BLOCKS': - if ( action.clientIds.indexOf( state.start ) === -1 ) { - return state; - } - - // If there are replacement blocks, assign last block as the next - // selected block, otherwise set to null. - const lastBlock = last( action.blocks ); - const nextSelectedBlockClientId = lastBlock ? lastBlock.clientId : null; - - if ( nextSelectedBlockClientId === state.start && nextSelectedBlockClientId === state.end ) { - return state; - } - - return { - ...state, - start: nextSelectedBlockClientId, - end: nextSelectedBlockClientId, - initialPosition: null, - isMultiSelecting: false, - }; - case 'TOGGLE_SELECTION': - return { - ...state, - isEnabled: action.isSelectionEnabled, - }; - } - - return state; -} - -export function blocksMode( state = {}, action ) { - if ( action.type === 'TOGGLE_BLOCK_MODE' ) { - const { clientId } = action; - return { - ...state, - [ clientId ]: state[ clientId ] && state[ clientId ] === 'html' ? 'visual' : 'html', - }; - } - - return state; -} - -/** - * Reducer returning the block insertion point visibility, either null if there - * is not an explicit insertion point assigned, or an object of its `index` and - * `rootClientId`. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export function insertionPoint( state = null, action ) { - switch ( action.type ) { - case 'SHOW_INSERTION_POINT': - const { rootClientId, index } = action; - return { rootClientId, index }; - - case 'HIDE_INSERTION_POINT': - return null; - } - - return state; -} - /** * Reducer returning whether the post blocks match the defined template or not. * @@ -709,46 +514,6 @@ export const reusableBlocks = combineReducers( { }, } ); -/** - * Reducer returning an object where each key is a block client ID, its value - * representing the settings for its nested blocks. - * - * @param {Object} state Current state. - * @param {Object} action Dispatched action. - * - * @return {Object} Updated state. - */ -export const blockListSettings = ( state = {}, action ) => { - switch ( action.type ) { - // Even if the replaced blocks have the same client ID, our logic - // should correct the state. - case 'REPLACE_BLOCKS' : - case 'REMOVE_BLOCKS': { - return omit( state, action.clientIds ); - } - case 'UPDATE_BLOCK_LIST_SETTINGS': { - const { clientId } = action; - if ( ! action.settings ) { - if ( state.hasOwnProperty( clientId ) ) { - return omit( state, clientId ); - } - - return state; - } - - if ( isEqual( state[ clientId ], action.settings ) ) { - return state; - } - - return { - ...state, - [ clientId ]: action.settings, - }; - } - } - return state; -}; - /** * Reducer returning the post preview link. * From 61a64dbb0d41aa6eb4e9168c5d3fecfc00a04f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Mon, 6 May 2019 17:22:56 +0200 Subject: [PATCH 037/664] Fix: webpack does not watch for block.json changes (#15455) --- bin/packages/watch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/packages/watch.js b/bin/packages/watch.js index ccfcc0b7dd85c7..1add26a5677a7f 100644 --- a/bin/packages/watch.js +++ b/bin/packages/watch.js @@ -27,7 +27,7 @@ const exists = ( filename ) => { // and files with a suffix of .test or .spec (e.g. blocks.test.js), // and deceitful source-like files, such as editor swap files. const isSourceFile = ( filename ) => { - return ! [ /\/(__tests__|test)\/.+.js$/, /.\.(spec|test)\.js$/ ].some( ( regex ) => regex.test( filename ) ) && /.\.(js|scss)$/.test( filename ); + return ! [ /\/(__tests__|test)\/.+.js$/, /.\.(spec|test)\.js$/ ].some( ( regex ) => regex.test( filename ) ) && /.\.(js|json|scss)$/.test( filename ); }; const rebuild = ( filename ) => filesToBuild.set( filename, true ); From a2e3a4016cbfbbcfdfef912cde735626c57f2ced Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 6 May 2019 17:36:13 +0100 Subject: [PATCH 038/664] Fix: ServerSideRender remains in loading state when nothing is rendered. (#15412) The server-side render component stayed loading forever if the block rendered an empty string. This PR addresses this problem. --- .../components/src/server-side-render/index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/components/src/server-side-render/index.js b/packages/components/src/server-side-render/index.js index 660521d5ce5196..d58564d2cd82eb 100644 --- a/packages/components/src/server-side-render/index.js +++ b/packages/components/src/server-side-render/index.js @@ -68,7 +68,7 @@ export class ServerSideRender extends Component { // check if it is the current request, to avoid race conditions on slow networks. const fetchRequest = this.currentFetchRequest = apiFetch( { path } ) .then( ( response ) => { - if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response && response.rendered ) { + if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response ) { this.setState( { response: response.rendered } ); } } ) @@ -86,30 +86,30 @@ export class ServerSideRender extends Component { render() { const response = this.state.response; const { className } = this.props; - if ( ! response ) { + if ( response === '' ) { return ( <Placeholder className={ className } > - <Spinner /> + { __( 'Block rendered as empty.' ) } </Placeholder> ); - } else if ( response.error ) { - // translators: %s: error message describing the problem - const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); + } else if ( ! response ) { return ( <Placeholder className={ className } > - { errorMessage } + <Spinner /> </Placeholder> ); - } else if ( ! response.length ) { + } else if ( response.error ) { + // translators: %s: error message describing the problem + const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); return ( <Placeholder className={ className } > - { __( 'No results found.' ) } + { errorMessage } </Placeholder> ); } From ef95cb95c2db5b44725c3c2a8778e7098925e170 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 6 May 2019 17:42:02 +0100 Subject: [PATCH 039/664] Fix: Block movers don't appear on the playground and on the widgets page. (#15076) The keyframe animations contained a comment saying they need to be duplicated, but they were not being duplicated in widgets and playground styles this made the block movers invisible. --- packages/edit-widgets/src/style.scss | 28 ++++++++++++++++++++++++++++ playground/src/style.scss | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 39319c706bf674..0a61df82f3c262 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -40,3 +40,31 @@ body.gutenberg_page_gutenberg-widgets { height: 100%; } } + +/** + * Animations + */ + +// These keyframes should not be part of the _animations.scss mixins file. +// Because keyframe animations can't be defined as mixins properly, they are duplicated. +// Since hey are intended only for the editor, we add them here instead. +@keyframes edit-post__loading-fade-animation { + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } +} + +@keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/playground/src/style.scss b/playground/src/style.scss index 1d4f7adb3296f8..1ec12365626ce9 100644 --- a/playground/src/style.scss +++ b/playground/src/style.scss @@ -35,3 +35,31 @@ height: 100%; } } + +/** + * Animations + */ + +// These keyframes should not be part of the _animations.scss mixins file. +// Because keyframe animations can't be defined as mixins properly, they are duplicated. +// Since hey are intended only for the editor, we add them here instead. +@keyframes edit-post__loading-fade-animation { + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } +} + +@keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} From eab473913d7c98d0ed4a26c7572786a9fbcb8b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Mon, 6 May 2019 19:43:56 +0200 Subject: [PATCH 040/664] Fix next/previous links in handbook (#15456) --- docs/manifest.json | 1532 ++++++++++++++++++++--------------------- docs/tool/config.js | 14 - docs/tool/index.js | 17 +- docs/tool/manifest.js | 18 +- 4 files changed, 785 insertions(+), 796 deletions(-) delete mode 100644 docs/tool/config.js diff --git a/docs/manifest.json b/docs/manifest.json index b9f03974dcee07..594d3504a50dcd 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -180,1149 +180,1149 @@ "parent": "developers" }, { - "title": "Components", - "slug": "components", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/README.md", - "parent": "developers" - }, - { - "title": "Theming for the Block Editor", - "slug": "themes", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/themes/README.md", - "parent": "developers" - }, - { - "title": "Theme Support", - "slug": "theme-support", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/themes/theme-support.md", - "parent": "themes" + "title": "@wordpress/a11y", + "slug": "packages-a11y", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/a11y/README.md", + "parent": "packages" }, { - "title": "Backward Compatibility", - "slug": "backward-compatibility", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/README.md", - "parent": "developers" + "title": "@wordpress/annotations", + "slug": "packages-annotations", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/annotations/README.md", + "parent": "packages" }, { - "title": "Deprecations", - "slug": "deprecations", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/deprecations.md", - "parent": "backward-compatibility" + "title": "@wordpress/api-fetch", + "slug": "packages-api-fetch", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/api-fetch/README.md", + "parent": "packages" }, { - "title": "Meta Boxes", - "slug": "meta-box", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/meta-box.md", - "parent": "backward-compatibility" + "title": "@wordpress/autop", + "slug": "packages-autop", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/autop/README.md", + "parent": "packages" }, { - "title": "Tutorials", - "slug": "tutorials", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/readme.md", - "parent": "developers" + "title": "@wordpress/babel-plugin-import-jsx-pragma", + "slug": "packages-babel-plugin-import-jsx-pragma", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/babel-plugin-import-jsx-pragma/README.md", + "parent": "packages" }, { - "title": "Getting Started with JavaScript", - "slug": "javascript", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/readme.md", - "parent": "tutorials" + "title": "@wordpress/babel-plugin-makepot", + "slug": "packages-babel-plugin-makepot", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/babel-plugin-makepot/README.md", + "parent": "packages" }, { - "title": "Plugins Background", - "slug": "plugins-background", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/plugins-background.md", - "parent": "javascript" + "title": "@wordpress/babel-preset-default", + "slug": "packages-babel-preset-default", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/babel-preset-default/README.md", + "parent": "packages" }, { - "title": "Loading JavaScript", - "slug": "loading-javascript", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", - "parent": "javascript" + "title": "@wordpress/blob", + "slug": "packages-blob", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/blob/README.md", + "parent": "packages" }, { - "title": "Extending the Block Editor", - "slug": "extending-the-block-editor", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", - "parent": "javascript" + "title": "@wordpress/block-editor", + "slug": "packages-block-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-editor/README.md", + "parent": "packages" }, { - "title": "Troubleshooting", - "slug": "troubleshooting", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", - "parent": "javascript" + "title": "@wordpress/block-library", + "slug": "packages-block-library", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-library/README.md", + "parent": "packages" }, { - "title": "JavaScript Versions and Build Step", - "slug": "versions-and-building", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", - "parent": "javascript" + "title": "@wordpress/block-serialization-default-parser", + "slug": "packages-block-serialization-default-parser", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-serialization-default-parser/README.md", + "parent": "packages" }, { - "title": "Scope Your Code", - "slug": "scope-your-code", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", - "parent": "javascript" + "title": "@wordpress/block-serialization-spec-parser", + "slug": "packages-block-serialization-spec-parser", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-serialization-spec-parser/README.md", + "parent": "packages" }, { - "title": "JavaScript Build Setup", - "slug": "js-build-setup", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", - "parent": "javascript" + "title": "@wordpress/blocks", + "slug": "packages-blocks", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/blocks/README.md", + "parent": "packages" }, { - "title": "Blocks", - "slug": "block-tutorial", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/readme.md", - "parent": "tutorials" + "title": "@wordpress/browserslist-config", + "slug": "packages-browserslist-config", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/browserslist-config/README.md", + "parent": "packages" }, { - "title": "Writing Your First Block Type", - "slug": "writing-your-first-block-type", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", - "parent": "block-tutorial" + "title": "@wordpress/components", + "slug": "packages-components", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/README.md", + "parent": "packages" }, { - "title": "Applying Styles From a Stylesheet", - "slug": "applying-styles-with-stylesheets", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", - "parent": "block-tutorial" + "title": "@wordpress/compose", + "slug": "packages-compose", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/compose/README.md", + "parent": "packages" }, { - "title": "Introducing Attributes and Editable Fields", - "slug": "introducing-attributes-and-editable-fields", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", - "parent": "block-tutorial" + "title": "@wordpress/core-data", + "slug": "packages-core-data", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/core-data/README.md", + "parent": "packages" }, { - "title": "Block Controls: Toolbars and Inspector", - "slug": "block-controls-toolbars-and-inspector", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", - "parent": "block-tutorial" + "title": "@wordpress/custom-templated-path-webpack-plugin", + "slug": "packages-custom-templated-path-webpack-plugin", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/custom-templated-path-webpack-plugin/README.md", + "parent": "packages" }, { - "title": "Creating dynamic blocks", - "slug": "creating-dynamic-blocks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", - "parent": "block-tutorial" + "title": "@wordpress/data", + "slug": "packages-data", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/data/README.md", + "parent": "packages" }, { - "title": "Generate Blocks with WP-CLI", - "slug": "generate-blocks-with-wp-cli", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", - "parent": "block-tutorial" + "title": "@wordpress/date", + "slug": "packages-date", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/date/README.md", + "parent": "packages" }, { - "title": "Meta Boxes", - "slug": "metabox", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/readme.md", - "parent": "tutorials" + "title": "@wordpress/dependency-extraction-webpack-plugin", + "slug": "packages-dependency-extraction-webpack-plugin", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dependency-extraction-webpack-plugin/README.md", + "parent": "packages" }, { - "title": "Store Post Meta with a Block", - "slug": "meta-block-1-intro", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", - "parent": "metabox" + "title": "@wordpress/deprecated", + "slug": "packages-deprecated", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/deprecated/README.md", + "parent": "packages" }, { - "title": "Register Meta Field", - "slug": "meta-block-2-register-meta", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", - "parent": "metabox" + "title": "@wordpress/docgen", + "slug": "packages-docgen", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/docgen/README.md", + "parent": "packages" }, { - "title": "Create Meta Block", - "slug": "meta-block-3-add", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", - "parent": "metabox" + "title": "@wordpress/dom-ready", + "slug": "packages-dom-ready", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dom-ready/README.md", + "parent": "packages" }, { - "title": "Use Post Meta Data", - "slug": "meta-block-4-use-data", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", - "parent": "metabox" + "title": "@wordpress/dom", + "slug": "packages-dom", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dom/README.md", + "parent": "packages" }, { - "title": "Finishing Touches", - "slug": "meta-block-5-finishing", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", - "parent": "metabox" + "title": "@wordpress/e2e-test-utils", + "slug": "packages-e2e-test-utils", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/e2e-test-utils/README.md", + "parent": "packages" }, { - "title": "Displaying Notices from Your Plugin or Theme", - "slug": "notices", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/README.md", - "parent": "tutorials" + "title": "@wordpress/e2e-tests", + "slug": "packages-e2e-tests", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/e2e-tests/README.md", + "parent": "packages" }, { - "title": "Creating a Sidebar for Your Plugin", - "slug": "plugin-sidebar-0", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", - "parent": "tutorials" + "title": "@wordpress/edit-post", + "slug": "packages-edit-post", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/edit-post/README.md", + "parent": "packages" }, { - "title": "Get a Sidebar up and Running", - "slug": "plugin-sidebar-1-up-and-running", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/edit-widgets", + "slug": "packages-edit-widgets", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/edit-widgets/README.md", + "parent": "packages" }, { - "title": "Tweak the sidebar style and add controls", - "slug": "plugin-sidebar-2-styles-and-controls", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/editor", + "slug": "packages-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/editor/README.md", + "parent": "packages" }, { - "title": "Register the Meta Field", - "slug": "plugin-sidebar-3-register-meta", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/element", + "slug": "packages-element", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/element/README.md", + "parent": "packages" }, { - "title": "Initialize the Input Control", - "slug": "plugin-sidebar-4-initialize-input", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/escape-html", + "slug": "packages-escape-html", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/escape-html/README.md", + "parent": "packages" }, { - "title": "Update the Meta Field When the Input's Content Changes", - "slug": "plugin-sidebar-5-update-meta", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/eslint-plugin", + "slug": "packages-eslint-plugin", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/eslint-plugin/README.md", + "parent": "packages" }, { - "title": "Finishing Touches", - "slug": "plugin-sidebar-6-finishing-touches", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", - "parent": "plugin-sidebar-0" + "title": "@wordpress/format-library", + "slug": "packages-format-library", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/format-library/README.md", + "parent": "packages" }, { - "title": "Introduction to the Format API", - "slug": "format-api", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/README.md", - "parent": "tutorials" + "title": "@wordpress/hooks", + "slug": "packages-hooks", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/hooks/README.md", + "parent": "packages" }, { - "title": "Register a New Format", - "slug": "1-register-format", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/1-register-format.md", - "parent": "format-api" + "title": "@wordpress/html-entities", + "slug": "packages-html-entities", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/html-entities/README.md", + "parent": "packages" }, { - "title": "Add a Button to the Toolbar", - "slug": "2-toolbar-button", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", - "parent": "format-api" + "title": "@wordpress/i18n", + "slug": "packages-i18n", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/i18n/README.md", + "parent": "packages" }, { - "title": "Apply the Format When the Button Is Clicked", - "slug": "3-apply-format", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", - "parent": "format-api" + "title": "@wordpress/is-shallow-equal", + "slug": "packages-is-shallow-equal", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/is-shallow-equal/README.md", + "parent": "packages" }, { - "title": "Designer Documentation", - "slug": "designers", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/README.md", - "parent": "designers-developers" + "title": "@wordpress/jest-console", + "slug": "packages-jest-console", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-console/README.md", + "parent": "packages" }, { - "title": "Block Design", - "slug": "block-design", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/block-design.md", - "parent": "designers" + "title": "@wordpress/jest-preset-default", + "slug": "packages-jest-preset-default", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-preset-default/README.md", + "parent": "packages" }, { - "title": "Patterns", - "slug": "design-patterns", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/design-patterns.md", - "parent": "designers" + "title": "@wordpress/jest-puppeteer-axe", + "slug": "packages-jest-puppeteer-axe", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-puppeteer-axe/README.md", + "parent": "packages" }, { - "title": "Resources", - "slug": "design-resources", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/design-resources.md", - "parent": "designers" + "title": "@wordpress/keycodes", + "slug": "packages-keycodes", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/keycodes/README.md", + "parent": "packages" }, { - "title": "Animation", - "slug": "animation", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/animation.md", - "parent": "designers" + "title": "@wordpress/library-export-default-webpack-plugin", + "slug": "packages-library-export-default-webpack-plugin", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/library-export-default-webpack-plugin/README.md", + "parent": "packages" }, { - "title": "Contributors Guide", - "slug": "contributors", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/readme.md", - "parent": null + "title": "@wordpress/list-reusable-blocks", + "slug": "packages-list-reusable-blocks", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/list-reusable-blocks/README.md", + "parent": "packages" }, { - "title": "Principles", - "slug": "principles", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles.md", - "parent": "contributors" + "title": "@wordpress/notices", + "slug": "packages-notices", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/notices/README.md", + "parent": "packages" }, { - "title": "Design Principles & Vision", - "slug": "design", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/design.md", - "parent": "contributors" + "title": "@wordpress/npm-package-json-lint-config", + "slug": "packages-npm-package-json-lint-config", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/npm-package-json-lint-config/README.md", + "parent": "packages" }, { - "title": "Blocks are the Interface", - "slug": "the-block", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles/the-block.md", - "parent": "design" + "title": "@wordpress/nux", + "slug": "packages-nux", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/nux/README.md", + "parent": "packages" }, { - "title": "Reference", - "slug": "reference", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/reference.md", - "parent": "design" + "title": "@wordpress/plugins", + "slug": "packages-plugins", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/plugins/README.md", + "parent": "packages" }, { - "title": "Developer Contributions", - "slug": "develop", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/develop.md", - "parent": "contributors" + "title": "@wordpress/postcss-themes", + "slug": "packages-postcss-themes", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/postcss-themes/README.md", + "parent": "packages" }, { - "title": "Getting Started", - "slug": "getting-started", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/getting-started.md", - "parent": "develop" + "title": "@wordpress/priority-queue", + "slug": "packages-priority-queue", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/priority-queue/README.md", + "parent": "packages" }, { - "title": "Git Workflow", - "slug": "git-workflow", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/git-workflow.md", - "parent": "develop" + "title": "@wordpress/redux-routine", + "slug": "packages-redux-routine", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/redux-routine/README.md", + "parent": "packages" }, { - "title": "Coding Guidelines", - "slug": "coding-guidelines", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", - "parent": "develop" + "title": "@wordpress/rich-text", + "slug": "packages-rich-text", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/rich-text/README.md", + "parent": "packages" }, { - "title": "Testing Overview", - "slug": "testing-overview", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/testing-overview.md", - "parent": "develop" + "title": "@wordpress/scripts", + "slug": "packages-scripts", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/scripts/README.md", + "parent": "packages" }, { - "title": "Block Grammar", - "slug": "grammar", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/grammar.md", - "parent": "develop" + "title": "@wordpress/shortcode", + "slug": "packages-shortcode", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/shortcode/README.md", + "parent": "packages" }, { - "title": "Scripts", - "slug": "scripts", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/scripts.md", - "parent": "develop" + "title": "@wordpress/token-list", + "slug": "packages-token-list", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/token-list/README.md", + "parent": "packages" }, { - "title": "Managing Packages", - "slug": "managing-packages", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/managing-packages.md", - "parent": "develop" + "title": "@wordpress/url", + "slug": "packages-url", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/url/README.md", + "parent": "packages" }, { - "title": "Gutenberg Release Process", - "slug": "release", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/release.md", - "parent": "develop" + "title": "@wordpress/viewport", + "slug": "packages-viewport", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/viewport/README.md", + "parent": "packages" }, { - "title": "Localizing Gutenberg Plugin", - "slug": "localizing", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/localizing.md", - "parent": "develop" + "title": "@wordpress/wordcount", + "slug": "packages-wordcount", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/wordcount/README.md", + "parent": "packages" }, { - "title": "Documentation Contributions", - "slug": "document", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/document.md", - "parent": "contributors" + "title": "Components", + "slug": "components", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/README.md", + "parent": "developers" }, { - "title": "Copy Guidelines", - "slug": "copy-guide", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/copy-guide.md", - "parent": "document" + "title": "Animate", + "slug": "animate", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/animate/README.md", + "parent": "components" }, { - "title": "History", - "slug": "history", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/history.md", - "parent": "contributors" + "title": "Autocomplete", + "slug": "autocomplete", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/autocomplete/README.md", + "parent": "components" }, { - "title": "Glossary", - "slug": "glossary", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/glossary.md", - "parent": "contributors" + "title": "BaseControl", + "slug": "base-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/base-control/README.md", + "parent": "components" }, { - "title": "Frequently Asked Questions", - "slug": "faq", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/faq.md", - "parent": "contributors" + "title": "ButtonGroup", + "slug": "button-group", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/button-group/README.md", + "parent": "components" }, { - "title": "Repository Management", - "slug": "repository-management", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/repository-management.md", - "parent": "contributors" + "title": "Button", + "slug": "button", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/button/README.md", + "parent": "components" }, { - "title": "Outreach", - "slug": "outreach", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach.md", - "parent": "contributors" + "title": "CheckboxControl", + "slug": "checkbox-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/checkbox-control/README.md", + "parent": "components" }, { - "title": "@wordpress/a11y", - "slug": "packages-a11y", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/a11y/README.md", - "parent": "packages" + "title": "ClipboardButton", + "slug": "clipboard-button", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/clipboard-button/README.md", + "parent": "components" }, { - "title": "@wordpress/annotations", - "slug": "packages-annotations", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/annotations/README.md", - "parent": "packages" + "title": "ColorIndicator", + "slug": "color-indicator", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/color-indicator/README.md", + "parent": "components" }, { - "title": "@wordpress/api-fetch", - "slug": "packages-api-fetch", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/api-fetch/README.md", - "parent": "packages" + "title": "ColorPalette", + "slug": "color-palette", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/color-palette/README.md", + "parent": "components" }, { - "title": "@wordpress/autop", - "slug": "packages-autop", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/autop/README.md", - "parent": "packages" + "title": "ColorPicker", + "slug": "color-picker", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/color-picker/README.md", + "parent": "components" }, { - "title": "@wordpress/babel-plugin-import-jsx-pragma", - "slug": "packages-babel-plugin-import-jsx-pragma", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/babel-plugin-import-jsx-pragma/README.md", - "parent": "packages" + "title": "Dashicon", + "slug": "dashicon", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/dashicon/README.md", + "parent": "components" }, { - "title": "@wordpress/babel-plugin-makepot", - "slug": "packages-babel-plugin-makepot", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/babel-plugin-makepot/README.md", - "parent": "packages" + "title": "DateTime", + "slug": "date-time", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/date-time/README.md", + "parent": "components" }, { - "title": "@wordpress/babel-preset-default", - "slug": "packages-babel-preset-default", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/babel-preset-default/README.md", - "parent": "packages" + "title": "Disabled", + "slug": "disabled", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/disabled/README.md", + "parent": "components" }, { - "title": "@wordpress/blob", - "slug": "packages-blob", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/blob/README.md", - "parent": "packages" + "title": "Draggable", + "slug": "draggable", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/draggable/README.md", + "parent": "components" }, { - "title": "@wordpress/block-editor", - "slug": "packages-block-editor", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-editor/README.md", - "parent": "packages" + "title": "DropZone", + "slug": "drop-zone", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/drop-zone/README.md", + "parent": "components" }, { - "title": "@wordpress/block-library", - "slug": "packages-block-library", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-library/README.md", - "parent": "packages" + "title": "DropdownMenu", + "slug": "dropdown-menu", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/dropdown-menu/README.md", + "parent": "components" }, { - "title": "@wordpress/block-serialization-default-parser", - "slug": "packages-block-serialization-default-parser", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-serialization-default-parser/README.md", - "parent": "packages" + "title": "Dropdown", + "slug": "dropdown", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/dropdown/README.md", + "parent": "components" }, { - "title": "@wordpress/block-serialization-spec-parser", - "slug": "packages-block-serialization-spec-parser", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/block-serialization-spec-parser/README.md", - "parent": "packages" + "title": "ExternalLink", + "slug": "external-link", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/external-link/README.md", + "parent": "components" }, { - "title": "@wordpress/blocks", - "slug": "packages-blocks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/blocks/README.md", - "parent": "packages" + "title": "FocalPointPicker", + "slug": "focal-point-picker", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/focal-point-picker/README.md", + "parent": "components" }, { - "title": "@wordpress/browserslist-config", - "slug": "packages-browserslist-config", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/browserslist-config/README.md", - "parent": "packages" + "title": "FocusableIframe", + "slug": "focusable-iframe", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/focusable-iframe/README.md", + "parent": "components" }, { - "title": "@wordpress/components", - "slug": "packages-components", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/README.md", - "parent": "packages" + "title": "FontSizePicker", + "slug": "font-size-picker", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/font-size-picker/README.md", + "parent": "components" }, { - "title": "@wordpress/compose", - "slug": "packages-compose", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/compose/README.md", - "parent": "packages" + "title": "FormFileUpload", + "slug": "form-file-upload", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/form-file-upload/README.md", + "parent": "components" }, { - "title": "@wordpress/core-data", - "slug": "packages-core-data", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/core-data/README.md", - "parent": "packages" + "title": "FormToggle", + "slug": "form-toggle", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/form-toggle/README.md", + "parent": "components" }, { - "title": "@wordpress/custom-templated-path-webpack-plugin", - "slug": "packages-custom-templated-path-webpack-plugin", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/custom-templated-path-webpack-plugin/README.md", - "parent": "packages" + "title": "FormTokenField", + "slug": "form-token-field", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/form-token-field/README.md", + "parent": "components" }, { - "title": "@wordpress/data", - "slug": "packages-data", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/data/README.md", - "parent": "packages" + "title": "NavigateRegions", + "slug": "navigate-regions", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/navigate-regions/README.md", + "parent": "components" }, { - "title": "@wordpress/date", - "slug": "packages-date", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/date/README.md", - "parent": "packages" + "title": "HigherOrder", + "slug": "higher-order", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/README.md", + "parent": "components" }, { - "title": "@wordpress/dependency-extraction-webpack-plugin", - "slug": "packages-dependency-extraction-webpack-plugin", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dependency-extraction-webpack-plugin/README.md", - "parent": "packages" + "title": "WithConstrainedTabbing", + "slug": "with-constrained-tabbing", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-constrained-tabbing/README.md", + "parent": "components" }, { - "title": "@wordpress/deprecated", - "slug": "packages-deprecated", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/deprecated/README.md", - "parent": "packages" + "title": "WithFallbackStyles", + "slug": "with-fallback-styles", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-fallback-styles/README.md", + "parent": "components" }, { - "title": "@wordpress/docgen", - "slug": "packages-docgen", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/docgen/README.md", - "parent": "packages" + "title": "WithFilters", + "slug": "with-filters", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-filters/README.md", + "parent": "components" }, { - "title": "@wordpress/dom-ready", - "slug": "packages-dom-ready", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dom-ready/README.md", - "parent": "packages" + "title": "WithFocusOutside", + "slug": "with-focus-outside", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-outside/README.md", + "parent": "components" }, { - "title": "@wordpress/dom", - "slug": "packages-dom", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/dom/README.md", - "parent": "packages" + "title": "WithFocusReturn", + "slug": "with-focus-return", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-return/README.md", + "parent": "components" }, { - "title": "@wordpress/e2e-test-utils", - "slug": "packages-e2e-test-utils", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/e2e-test-utils/README.md", - "parent": "packages" + "title": "WithNotices", + "slug": "with-notices", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-notices/README.md", + "parent": "components" }, { - "title": "@wordpress/e2e-tests", - "slug": "packages-e2e-tests", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/e2e-tests/README.md", - "parent": "packages" + "title": "WithSpokenMessages", + "slug": "with-spoken-messages", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-spoken-messages/README.md", + "parent": "components" }, { - "title": "@wordpress/edit-post", - "slug": "packages-edit-post", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/edit-post/README.md", - "parent": "packages" + "title": "IconButton", + "slug": "icon-button", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/icon-button/README.md", + "parent": "components" }, { - "title": "@wordpress/edit-widgets", - "slug": "packages-edit-widgets", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/edit-widgets/README.md", - "parent": "packages" + "title": "Icon", + "slug": "icon", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/icon/README.md", + "parent": "components" }, { - "title": "@wordpress/editor", - "slug": "packages-editor", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/editor/README.md", - "parent": "packages" + "title": "IsolatedEventContainer", + "slug": "isolated-event-container", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/isolated-event-container/README.md", + "parent": "components" }, { - "title": "@wordpress/element", - "slug": "packages-element", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/element/README.md", - "parent": "packages" + "title": "KeyboardShortcuts", + "slug": "keyboard-shortcuts", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/keyboard-shortcuts/README.md", + "parent": "components" }, { - "title": "@wordpress/escape-html", - "slug": "packages-escape-html", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/escape-html/README.md", - "parent": "packages" + "title": "MenuGroup", + "slug": "menu-group", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/menu-group/README.md", + "parent": "components" }, { - "title": "@wordpress/eslint-plugin", - "slug": "packages-eslint-plugin", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/eslint-plugin/README.md", - "parent": "packages" + "title": "MenuItem", + "slug": "menu-item", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/menu-item/README.md", + "parent": "components" }, { - "title": "@wordpress/format-library", - "slug": "packages-format-library", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/format-library/README.md", - "parent": "packages" + "title": "MenuItemsChoice", + "slug": "menu-items-choice", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/menu-items-choice/README.md", + "parent": "components" }, { - "title": "@wordpress/hooks", - "slug": "packages-hooks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/hooks/README.md", - "parent": "packages" + "title": "Modal", + "slug": "modal", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/modal/README.md", + "parent": "components" }, { - "title": "@wordpress/html-entities", - "slug": "packages-html-entities", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/html-entities/README.md", - "parent": "packages" + "title": "NavigableContainer", + "slug": "navigable-container", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/navigable-container/README.md", + "parent": "components" }, { - "title": "@wordpress/i18n", - "slug": "packages-i18n", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/i18n/README.md", - "parent": "packages" + "title": "Notice", + "slug": "notice", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/notice/README.md", + "parent": "components" }, { - "title": "@wordpress/is-shallow-equal", - "slug": "packages-is-shallow-equal", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/is-shallow-equal/README.md", - "parent": "packages" + "title": "Panel", + "slug": "panel", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/panel/README.md", + "parent": "components" }, { - "title": "@wordpress/jest-console", - "slug": "packages-jest-console", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-console/README.md", - "parent": "packages" + "title": "Placeholder", + "slug": "placeholder", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/placeholder/README.md", + "parent": "components" }, { - "title": "@wordpress/jest-preset-default", - "slug": "packages-jest-preset-default", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-preset-default/README.md", - "parent": "packages" + "title": "Popover", + "slug": "popover", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/popover/README.md", + "parent": "components" }, { - "title": "@wordpress/jest-puppeteer-axe", - "slug": "packages-jest-puppeteer-axe", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/jest-puppeteer-axe/README.md", - "parent": "packages" + "title": "HorizontalRule", + "slug": "horizontal-rule", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/primitives/horizontal-rule/README.md", + "parent": "components" }, { - "title": "@wordpress/keycodes", - "slug": "packages-keycodes", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/keycodes/README.md", - "parent": "packages" + "title": "Svg", + "slug": "svg", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/primitives/svg/README.md", + "parent": "components" }, { - "title": "@wordpress/library-export-default-webpack-plugin", - "slug": "packages-library-export-default-webpack-plugin", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/library-export-default-webpack-plugin/README.md", - "parent": "packages" + "title": "QueryControls", + "slug": "query-controls", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/query-controls/README.md", + "parent": "components" }, { - "title": "@wordpress/list-reusable-blocks", - "slug": "packages-list-reusable-blocks", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/list-reusable-blocks/README.md", - "parent": "packages" + "title": "RadioControl", + "slug": "radio-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/radio-control/README.md", + "parent": "components" }, { - "title": "@wordpress/notices", - "slug": "packages-notices", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/notices/README.md", - "parent": "packages" + "title": "RangeControl", + "slug": "range-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/range-control/README.md", + "parent": "components" }, { - "title": "@wordpress/npm-package-json-lint-config", - "slug": "packages-npm-package-json-lint-config", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/npm-package-json-lint-config/README.md", - "parent": "packages" + "title": "ResizableBox", + "slug": "resizable-box", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/resizable-box/README.md", + "parent": "components" }, { - "title": "@wordpress/nux", - "slug": "packages-nux", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/nux/README.md", - "parent": "packages" + "title": "ResponsiveWrapper", + "slug": "responsive-wrapper", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/responsive-wrapper/README.md", + "parent": "components" }, { - "title": "@wordpress/plugins", - "slug": "packages-plugins", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/plugins/README.md", - "parent": "packages" + "title": "Sandbox", + "slug": "sandbox", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/sandbox/README.md", + "parent": "components" }, { - "title": "@wordpress/postcss-themes", - "slug": "packages-postcss-themes", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/postcss-themes/README.md", - "parent": "packages" + "title": "ScrollLock", + "slug": "scroll-lock", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/scroll-lock/README.md", + "parent": "components" }, { - "title": "@wordpress/priority-queue", - "slug": "packages-priority-queue", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/priority-queue/README.md", - "parent": "packages" + "title": "SelectControl", + "slug": "select-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/select-control/README.md", + "parent": "components" }, { - "title": "@wordpress/redux-routine", - "slug": "packages-redux-routine", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/redux-routine/README.md", - "parent": "packages" + "title": "ServerSideRender", + "slug": "server-side-render", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/server-side-render/README.md", + "parent": "components" }, { - "title": "@wordpress/rich-text", - "slug": "packages-rich-text", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/rich-text/README.md", - "parent": "packages" + "title": "SlotFill", + "slug": "slot-fill", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/slot-fill/README.md", + "parent": "components" }, { - "title": "@wordpress/scripts", - "slug": "packages-scripts", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/scripts/README.md", - "parent": "packages" + "title": "Spinner", + "slug": "spinner", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/spinner/README.md", + "parent": "components" }, { - "title": "@wordpress/shortcode", - "slug": "packages-shortcode", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/shortcode/README.md", - "parent": "packages" + "title": "TabPanel", + "slug": "tab-panel", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tab-panel/README.md", + "parent": "components" }, { - "title": "@wordpress/token-list", - "slug": "packages-token-list", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/token-list/README.md", - "parent": "packages" + "title": "TextControl", + "slug": "text-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/text-control/README.md", + "parent": "components" }, { - "title": "@wordpress/url", - "slug": "packages-url", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/url/README.md", - "parent": "packages" + "title": "TextareaControl", + "slug": "textarea-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/textarea-control/README.md", + "parent": "components" }, { - "title": "@wordpress/viewport", - "slug": "packages-viewport", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/viewport/README.md", - "parent": "packages" + "title": "ToggleControl", + "slug": "toggle-control", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/toggle-control/README.md", + "parent": "components" }, { - "title": "@wordpress/wordcount", - "slug": "packages-wordcount", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/wordcount/README.md", - "parent": "packages" + "title": "Toolbar", + "slug": "toolbar", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/toolbar/README.md", + "parent": "components" }, { - "title": "Animate", - "slug": "animate", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/animate/README.md", + "title": "Tooltip", + "slug": "tooltip", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tooltip/README.md", "parent": "components" }, { - "title": "Autocomplete", - "slug": "autocomplete", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/autocomplete/README.md", + "title": "TreeSelect", + "slug": "tree-select", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tree-select/README.md", "parent": "components" }, { - "title": "BaseControl", - "slug": "base-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/base-control/README.md", - "parent": "components" + "title": "Theming for the Block Editor", + "slug": "themes", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/themes/README.md", + "parent": "developers" + }, + { + "title": "Theme Support", + "slug": "theme-support", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/themes/theme-support.md", + "parent": "themes" + }, + { + "title": "Backward Compatibility", + "slug": "backward-compatibility", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/README.md", + "parent": "developers" }, { - "title": "ButtonGroup", - "slug": "button-group", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/button-group/README.md", - "parent": "components" + "title": "Deprecations", + "slug": "deprecations", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/deprecations.md", + "parent": "backward-compatibility" }, { - "title": "Button", - "slug": "button", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/button/README.md", - "parent": "components" + "title": "Meta Boxes", + "slug": "meta-box", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/backward-compatibility/meta-box.md", + "parent": "backward-compatibility" }, { - "title": "CheckboxControl", - "slug": "checkbox-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/checkbox-control/README.md", - "parent": "components" + "title": "Tutorials", + "slug": "tutorials", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/readme.md", + "parent": "developers" }, { - "title": "ClipboardButton", - "slug": "clipboard-button", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/clipboard-button/README.md", - "parent": "components" + "title": "Getting Started with JavaScript", + "slug": "javascript", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/readme.md", + "parent": "tutorials" }, { - "title": "ColorIndicator", - "slug": "color-indicator", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/color-indicator/README.md", - "parent": "components" + "title": "Plugins Background", + "slug": "plugins-background", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/plugins-background.md", + "parent": "javascript" }, { - "title": "ColorPalette", - "slug": "color-palette", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/color-palette/README.md", - "parent": "components" + "title": "Loading JavaScript", + "slug": "loading-javascript", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", + "parent": "javascript" }, { - "title": "ColorPicker", - "slug": "color-picker", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/color-picker/README.md", - "parent": "components" + "title": "Extending the Block Editor", + "slug": "extending-the-block-editor", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", + "parent": "javascript" }, { - "title": "Dashicon", - "slug": "dashicon", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/dashicon/README.md", - "parent": "components" + "title": "Troubleshooting", + "slug": "troubleshooting", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", + "parent": "javascript" }, { - "title": "DateTime", - "slug": "date-time", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/date-time/README.md", - "parent": "components" + "title": "JavaScript Versions and Build Step", + "slug": "versions-and-building", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", + "parent": "javascript" }, { - "title": "Disabled", - "slug": "disabled", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/disabled/README.md", - "parent": "components" + "title": "Scope Your Code", + "slug": "scope-your-code", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", + "parent": "javascript" }, { - "title": "Draggable", - "slug": "draggable", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/draggable/README.md", - "parent": "components" + "title": "JavaScript Build Setup", + "slug": "js-build-setup", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", + "parent": "javascript" }, { - "title": "DropZone", - "slug": "drop-zone", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/drop-zone/README.md", - "parent": "components" + "title": "Blocks", + "slug": "block-tutorial", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/readme.md", + "parent": "tutorials" }, { - "title": "DropdownMenu", - "slug": "dropdown-menu", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/dropdown-menu/README.md", - "parent": "components" + "title": "Writing Your First Block Type", + "slug": "writing-your-first-block-type", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", + "parent": "block-tutorial" }, { - "title": "Dropdown", - "slug": "dropdown", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/dropdown/README.md", - "parent": "components" + "title": "Applying Styles From a Stylesheet", + "slug": "applying-styles-with-stylesheets", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", + "parent": "block-tutorial" }, { - "title": "ExternalLink", - "slug": "external-link", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/external-link/README.md", - "parent": "components" + "title": "Introducing Attributes and Editable Fields", + "slug": "introducing-attributes-and-editable-fields", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", + "parent": "block-tutorial" }, { - "title": "FocalPointPicker", - "slug": "focal-point-picker", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/focal-point-picker/README.md", - "parent": "components" + "title": "Block Controls: Toolbars and Inspector", + "slug": "block-controls-toolbars-and-inspector", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", + "parent": "block-tutorial" }, { - "title": "FocusableIframe", - "slug": "focusable-iframe", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/focusable-iframe/README.md", - "parent": "components" + "title": "Creating dynamic blocks", + "slug": "creating-dynamic-blocks", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", + "parent": "block-tutorial" }, { - "title": "FontSizePicker", - "slug": "font-size-picker", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/font-size-picker/README.md", - "parent": "components" + "title": "Generate Blocks with WP-CLI", + "slug": "generate-blocks-with-wp-cli", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", + "parent": "block-tutorial" }, { - "title": "FormFileUpload", - "slug": "form-file-upload", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/form-file-upload/README.md", - "parent": "components" + "title": "Meta Boxes", + "slug": "metabox", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/readme.md", + "parent": "tutorials" }, { - "title": "FormToggle", - "slug": "form-toggle", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/form-toggle/README.md", - "parent": "components" + "title": "Store Post Meta with a Block", + "slug": "meta-block-1-intro", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", + "parent": "metabox" }, { - "title": "FormTokenField", - "slug": "form-token-field", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/form-token-field/README.md", - "parent": "components" + "title": "Register Meta Field", + "slug": "meta-block-2-register-meta", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", + "parent": "metabox" }, { - "title": "NavigateRegions", - "slug": "navigate-regions", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/navigate-regions/README.md", - "parent": "components" + "title": "Create Meta Block", + "slug": "meta-block-3-add", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", + "parent": "metabox" }, { - "title": "HigherOrder", - "slug": "higher-order", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/README.md", - "parent": "components" + "title": "Use Post Meta Data", + "slug": "meta-block-4-use-data", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", + "parent": "metabox" }, { - "title": "WithConstrainedTabbing", - "slug": "with-constrained-tabbing", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-constrained-tabbing/README.md", - "parent": "components" + "title": "Finishing Touches", + "slug": "meta-block-5-finishing", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", + "parent": "metabox" }, { - "title": "WithFallbackStyles", - "slug": "with-fallback-styles", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-fallback-styles/README.md", - "parent": "components" + "title": "Displaying Notices from Your Plugin or Theme", + "slug": "notices", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/notices/README.md", + "parent": "tutorials" }, { - "title": "WithFilters", - "slug": "with-filters", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-filters/README.md", - "parent": "components" + "title": "Creating a Sidebar for Your Plugin", + "slug": "plugin-sidebar-0", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", + "parent": "tutorials" }, { - "title": "WithFocusOutside", - "slug": "with-focus-outside", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-outside/README.md", - "parent": "components" + "title": "Get a Sidebar up and Running", + "slug": "plugin-sidebar-1-up-and-running", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", + "parent": "plugin-sidebar-0" }, { - "title": "WithFocusReturn", - "slug": "with-focus-return", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-focus-return/README.md", - "parent": "components" + "title": "Tweak the sidebar style and add controls", + "slug": "plugin-sidebar-2-styles-and-controls", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", + "parent": "plugin-sidebar-0" }, { - "title": "WithNotices", - "slug": "with-notices", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-notices/README.md", - "parent": "components" + "title": "Register the Meta Field", + "slug": "plugin-sidebar-3-register-meta", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", + "parent": "plugin-sidebar-0" }, { - "title": "WithSpokenMessages", - "slug": "with-spoken-messages", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/higher-order/with-spoken-messages/README.md", - "parent": "components" + "title": "Initialize the Input Control", + "slug": "plugin-sidebar-4-initialize-input", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", + "parent": "plugin-sidebar-0" }, { - "title": "IconButton", - "slug": "icon-button", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/icon-button/README.md", - "parent": "components" + "title": "Update the Meta Field When the Input's Content Changes", + "slug": "plugin-sidebar-5-update-meta", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", + "parent": "plugin-sidebar-0" }, { - "title": "Icon", - "slug": "icon", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/icon/README.md", - "parent": "components" + "title": "Finishing Touches", + "slug": "plugin-sidebar-6-finishing-touches", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", + "parent": "plugin-sidebar-0" }, { - "title": "IsolatedEventContainer", - "slug": "isolated-event-container", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/isolated-event-container/README.md", - "parent": "components" + "title": "Introduction to the Format API", + "slug": "format-api", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/README.md", + "parent": "tutorials" }, { - "title": "KeyboardShortcuts", - "slug": "keyboard-shortcuts", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/keyboard-shortcuts/README.md", - "parent": "components" + "title": "Register a New Format", + "slug": "1-register-format", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/1-register-format.md", + "parent": "format-api" }, { - "title": "MenuGroup", - "slug": "menu-group", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/menu-group/README.md", - "parent": "components" + "title": "Add a Button to the Toolbar", + "slug": "2-toolbar-button", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", + "parent": "format-api" }, { - "title": "MenuItem", - "slug": "menu-item", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/menu-item/README.md", - "parent": "components" + "title": "Apply the Format When the Button Is Clicked", + "slug": "3-apply-format", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", + "parent": "format-api" }, { - "title": "MenuItemsChoice", - "slug": "menu-items-choice", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/menu-items-choice/README.md", - "parent": "components" + "title": "Designer Documentation", + "slug": "designers", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/README.md", + "parent": "designers-developers" }, { - "title": "Modal", - "slug": "modal", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/modal/README.md", - "parent": "components" + "title": "Block Design", + "slug": "block-design", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/block-design.md", + "parent": "designers" }, { - "title": "NavigableContainer", - "slug": "navigable-container", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/navigable-container/README.md", - "parent": "components" + "title": "Patterns", + "slug": "design-patterns", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/design-patterns.md", + "parent": "designers" }, { - "title": "Notice", - "slug": "notice", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/notice/README.md", - "parent": "components" + "title": "Resources", + "slug": "design-resources", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/design-resources.md", + "parent": "designers" }, { - "title": "Panel", - "slug": "panel", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/panel/README.md", - "parent": "components" + "title": "Animation", + "slug": "animation", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/animation.md", + "parent": "designers" }, { - "title": "Placeholder", - "slug": "placeholder", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/placeholder/README.md", - "parent": "components" + "title": "Contributors Guide", + "slug": "contributors", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/readme.md", + "parent": null }, { - "title": "Popover", - "slug": "popover", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/popover/README.md", - "parent": "components" + "title": "Principles", + "slug": "principles", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles.md", + "parent": "contributors" }, { - "title": "HorizontalRule", - "slug": "horizontal-rule", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/primitives/horizontal-rule/README.md", - "parent": "components" + "title": "Design Principles & Vision", + "slug": "design", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/design.md", + "parent": "contributors" }, { - "title": "Svg", - "slug": "svg", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/primitives/svg/README.md", - "parent": "components" + "title": "Blocks are the Interface", + "slug": "the-block", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/principles/the-block.md", + "parent": "design" }, { - "title": "QueryControls", - "slug": "query-controls", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/query-controls/README.md", - "parent": "components" + "title": "Reference", + "slug": "reference", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/reference.md", + "parent": "design" }, { - "title": "RadioControl", - "slug": "radio-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/radio-control/README.md", - "parent": "components" + "title": "Developer Contributions", + "slug": "develop", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/develop.md", + "parent": "contributors" }, { - "title": "RangeControl", - "slug": "range-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/range-control/README.md", - "parent": "components" + "title": "Getting Started", + "slug": "getting-started", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/getting-started.md", + "parent": "develop" }, { - "title": "ResizableBox", - "slug": "resizable-box", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/resizable-box/README.md", - "parent": "components" + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/git-workflow.md", + "parent": "develop" }, { - "title": "ResponsiveWrapper", - "slug": "responsive-wrapper", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/responsive-wrapper/README.md", - "parent": "components" + "title": "Coding Guidelines", + "slug": "coding-guidelines", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/coding-guidelines.md", + "parent": "develop" }, { - "title": "Sandbox", - "slug": "sandbox", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/sandbox/README.md", - "parent": "components" + "title": "Testing Overview", + "slug": "testing-overview", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/testing-overview.md", + "parent": "develop" }, { - "title": "ScrollLock", - "slug": "scroll-lock", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/scroll-lock/README.md", - "parent": "components" + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/grammar.md", + "parent": "develop" }, { - "title": "SelectControl", - "slug": "select-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/select-control/README.md", - "parent": "components" + "title": "Scripts", + "slug": "scripts", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/scripts.md", + "parent": "develop" }, { - "title": "ServerSideRender", - "slug": "server-side-render", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/server-side-render/README.md", - "parent": "components" + "title": "Managing Packages", + "slug": "managing-packages", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/managing-packages.md", + "parent": "develop" }, { - "title": "SlotFill", - "slug": "slot-fill", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/slot-fill/README.md", - "parent": "components" + "title": "Gutenberg Release Process", + "slug": "release", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/release.md", + "parent": "develop" }, { - "title": "Spinner", - "slug": "spinner", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/spinner/README.md", - "parent": "components" + "title": "Localizing Gutenberg Plugin", + "slug": "localizing", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/localizing.md", + "parent": "develop" }, { - "title": "TabPanel", - "slug": "tab-panel", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tab-panel/README.md", - "parent": "components" + "title": "Documentation Contributions", + "slug": "document", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/document.md", + "parent": "contributors" }, { - "title": "TextControl", - "slug": "text-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/text-control/README.md", - "parent": "components" + "title": "Copy Guidelines", + "slug": "copy-guide", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/copy-guide.md", + "parent": "document" }, { - "title": "TextareaControl", - "slug": "textarea-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/textarea-control/README.md", - "parent": "components" + "title": "History", + "slug": "history", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/history.md", + "parent": "contributors" }, { - "title": "ToggleControl", - "slug": "toggle-control", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/toggle-control/README.md", - "parent": "components" + "title": "Glossary", + "slug": "glossary", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/glossary.md", + "parent": "contributors" }, { - "title": "Toolbar", - "slug": "toolbar", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/toolbar/README.md", - "parent": "components" + "title": "Frequently Asked Questions", + "slug": "faq", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/faq.md", + "parent": "contributors" }, { - "title": "Tooltip", - "slug": "tooltip", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tooltip/README.md", - "parent": "components" + "title": "Repository Management", + "slug": "repository-management", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/repository-management.md", + "parent": "contributors" }, { - "title": "TreeSelect", - "slug": "tree-select", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/tree-select/README.md", - "parent": "components" + "title": "Outreach", + "slug": "outreach", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach.md", + "parent": "contributors" } ] \ No newline at end of file diff --git a/docs/tool/config.js b/docs/tool/config.js deleted file mode 100644 index 70ddebf14bcd30..00000000000000 --- a/docs/tool/config.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * External dependencies - */ -const glob = require( 'glob' ).sync; -const path = require( 'path' ); - -module.exports = { - componentPaths: glob( 'packages/components/src/*/**/README.md' ), - packageFileNames: glob( 'packages/*/package.json' ) - .map( ( fileName ) => fileName.split( '/' )[ 1 ] ), - - tocFileName: path.resolve( __dirname, '../toc.json' ), - manifestOutput: path.resolve( __dirname, '../manifest.json' ), -}; diff --git a/docs/tool/index.js b/docs/tool/index.js index 5e4244f88437c8..c75767b3d85d96 100644 --- a/docs/tool/index.js +++ b/docs/tool/index.js @@ -4,21 +4,18 @@ const fs = require( 'fs' ); const { join } = require( 'path' ); const { execSync } = require( 'child_process' ); +const path = require( 'path' ); /** * Internal dependencies */ -const config = require( './config' ); -const { getPackageManifest, getComponentManifest, getRootManifest } = require( './manifest' ); +const { getRootManifest } = require( './manifest' ); + +const tocFileInput = path.resolve( __dirname, '../toc.json' ); +const manifestOutput = path.resolve( __dirname, '../manifest.json' ); // Update data files from code execSync( join( __dirname, 'update-data.js' ) ); -const rootManifest = getRootManifest( config.tocFileName ); -const packageManifest = getPackageManifest( config.packageFileNames ); -const componentManifest = getComponentManifest( config.componentPaths ); - -fs.writeFileSync( - config.manifestOutput, - JSON.stringify( rootManifest.concat( packageManifest, componentManifest ), undefined, '\t' ) -); +// Process TOC file and generate manifest handbook +fs.writeFileSync( manifestOutput, JSON.stringify( getRootManifest( tocFileInput ), undefined, '\t' ) ); diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index d6dcfeebc577fd..cb5ae4deaf17c5 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -2,10 +2,14 @@ * Node dependencies */ const { camelCase, nth, upperFirst } = require( 'lodash' ); - const fs = require( 'fs' ); +const glob = require( 'glob' ).sync; const baseRepoUrl = `https://raw.githubusercontent.com/WordPress/gutenberg/master`; +const componentPaths = glob( 'packages/components/src/*/**/README.md' ); +const packagePaths = glob( 'packages/*/package.json' ).map( + ( fileName ) => fileName.split( '/' )[ 1 ] +); /** * Generates the package manifest. @@ -29,12 +33,12 @@ function getPackageManifest( packageFolderNames ) { /** * Generates the components manifest. * - * @param {Array} componentPaths Paths for all components + * @param {Array} paths Paths for all components * * @return {Array} Manifest */ -function getComponentManifest( componentPaths ) { - return componentPaths.map( ( filePath ) => { +function getComponentManifest( paths ) { + return paths.map( ( filePath ) => { const slug = nth( filePath.split( '/' ), -2 ); return { title: upperFirst( camelCase( slug ) ), @@ -79,13 +83,15 @@ function generateRootManifestFromTOCItems( items, parent = null ) { } ); if ( Array.isArray( children ) && children.length ) { pageItems = pageItems.concat( generateRootManifestFromTOCItems( children, slug ) ); + } else if ( children === '{{components}}' ) { + pageItems = pageItems.concat( getComponentManifest( componentPaths ) ); + } else if ( children === '{{packages}}' ) { + pageItems = pageItems.concat( getPackageManifest( packagePaths ) ); } } ); return pageItems; } module.exports = { - getPackageManifest, - getComponentManifest, getRootManifest, }; From 1d81635e4320cb11ec4ee1d136628b63bc05ee91 Mon Sep 17 00:00:00 2001 From: Foysal Remon <foysalremon@users.noreply.github.com> Date: Mon, 6 May 2019 23:58:31 +0600 Subject: [PATCH 041/664] Fix: HTML entities in Author name are not decoded (#15090) * Fix: HTML entities in Author name are not decoded * Update packages/editor/src/components/post-author/index.js Co-Authored-By: foysalremon <foysalremon@users.noreply.github.com> --- packages/editor/src/components/post-author/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 5bb31bf294fd6f..22f43ffd43eb90 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n'; import { withInstanceId, compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -41,7 +42,7 @@ export class PostAuthor extends Component { className="editor-post-author__select" > { authors.map( ( author ) => ( - <option key={ author.id } value={ author.id }>{ author.name }</option> + <option key={ author.id } value={ author.id }>{ decodeEntities( author.name ) }</option> ) ) } </select> </PostAuthorCheck> From d8f1875c229f49f9af501c3259bbd16461aa6aae Mon Sep 17 00:00:00 2001 From: andrei draganescu <andrei.draganescu@automattic.com> Date: Mon, 6 May 2019 23:12:25 +0300 Subject: [PATCH 042/664] reverts the rename of postsToShow as it may cause breaking changes (#15453) * reverts the rename of postsToShow as it may cause breaking changes * array field allign * rafactor spacing in PHP --- packages/block-library/src/latest-posts/edit.js | 14 +++++++------- packages/block-library/src/latest-posts/index.php | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 433810e467b0fb..e8811a310787af 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -70,7 +70,7 @@ class LatestPostsEdit extends Component { render() { const { attributes, setAttributes, latestPosts } = this.props; const { categoriesList } = this.state; - const { displayPostContentRadio, displayPostContent, displayPostDate, postLayout, columns, order, orderBy, categories, postCount, excerptLength } = attributes; + const { displayPostContentRadio, displayPostContent, displayPostDate, postLayout, columns, order, orderBy, categories, postsToShow, excerptLength } = attributes; const inspectorControls = ( <InspectorControls> @@ -113,13 +113,13 @@ class LatestPostsEdit extends Component { <PanelBody title={ __( 'Sorting and Filtering' ) }> <QueryControls { ...{ order, orderBy } } - numberOfItems={ postCount } + numberOfItems={ postsToShow } categoriesList={ categoriesList } selectedCategoryId={ categories } onOrderChange={ ( value ) => setAttributes( { order: value } ) } onOrderByChange={ ( value ) => setAttributes( { orderBy: value } ) } onCategoryChange={ ( value ) => setAttributes( { categories: '' !== value ? value : undefined } ) } - onNumberOfItemsChange={ ( value ) => setAttributes( { postCount: value } ) } + onNumberOfItemsChange={ ( value ) => setAttributes( { postsToShow: value } ) } /> { postLayout === 'grid' && <RangeControl @@ -154,8 +154,8 @@ class LatestPostsEdit extends Component { } // Removing posts from display should be instant. - const displayPosts = latestPosts.length > postCount ? - latestPosts.slice( 0, postCount ) : + const displayPosts = latestPosts.length > postsToShow ? + latestPosts.slice( 0, postsToShow ) : latestPosts; const layoutControls = [ @@ -244,13 +244,13 @@ class LatestPostsEdit extends Component { } export default withSelect( ( select, props ) => { - const { postCount, order, orderBy, categories } = props.attributes; + const { postsToShow, order, orderBy, categories } = props.attributes; const { getEntityRecords } = select( 'core' ); const latestPostsQuery = pickBy( { categories, order, orderby: orderBy, - per_page: postCount, + per_page: postsToShow, }, ( value ) => ! isUndefined( value ) ); return { latestPosts: getEntityRecords( 'postType', 'post', latestPostsQuery ), diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index d3a7a30c90c2f5..e8cd3c6fb66122 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -14,7 +14,7 @@ */ function render_block_core_latest_posts( $attributes ) { $args = array( - 'posts_per_page' => $attributes['postCount'], + 'posts_per_page' => $attributes['postsToShow'], 'post_status' => 'publish', 'order' => $attributes['order'], 'orderby' => $attributes['orderBy'], @@ -135,7 +135,7 @@ function register_block_core_latest_posts() { 'categories' => array( 'type' => 'string', ), - 'postCount' => array( + 'postsToShow' => array( 'type' => 'number', 'default' => 5, ), From 39f568faf2e62c57736bb80d7088a996eb067944 Mon Sep 17 00:00:00 2001 From: Jon Surrell <jon.surrell@automattic.com> Date: Tue, 7 May 2019 08:05:30 +0200 Subject: [PATCH 043/664] JSX: Use <> Fragments (#15261) * Replace <Fragment /> with </> and remove imports * Update documentation with </> Fragments in JSX --- .../block-editor-keyboard-shortcuts/index.js | 6 ++-- .../src/components/block-inspector/index.js | 5 ++-- .../src/components/block-list/block.js | 6 ++-- .../src/components/block-list/breadcrumb.js | 6 ++-- .../components/block-navigation/dropdown.js | 5 ++-- .../components/block-settings-menu/index.js | 5 ++-- .../src/components/block-switcher/index.js | 10 +++---- .../src/components/block-toolbar/index.js | 5 ++-- .../README.md | 7 ++--- .../components/inspector-controls/README.md | 5 ++-- .../src/components/media-placeholder/index.js | 14 ++++----- .../src/components/rich-text/format-edit.js | 5 ++-- .../src/components/rich-text/index.js | 6 ++-- .../src/components/rich-text/list-edit.js | 5 ++-- .../src/components/url-popover/README.md | 5 ++-- packages/block-editor/src/hooks/anchor.js | 5 ++-- .../src/hooks/custom-class-name.js | 5 ++-- packages/block-library/src/archives/edit.js | 5 ++-- packages/block-library/src/audio/edit.js | 6 ++-- .../src/block/edit-panel/index.js | 6 ++-- packages/block-library/src/block/edit.js | 6 ++-- packages/block-library/src/button/edit.js | 9 ++---- packages/block-library/src/categories/edit.js | 14 ++++----- packages/block-library/src/columns/edit.js | 5 ++-- packages/block-library/src/cover/edit.js | 18 +++++------ packages/block-library/src/embed/edit.js | 6 ++-- .../block-library/src/embed/embed-controls.js | 5 ++-- packages/block-library/src/file/edit.js | 6 ++-- packages/block-library/src/file/inspector.js | 5 ++-- packages/block-library/src/gallery/edit.js | 10 +++---- .../src/gallery/gallery-image.js | 6 ++-- packages/block-library/src/group/edit.js | 5 ++-- packages/block-library/src/heading/edit.js | 5 ++-- packages/block-library/src/image/edit.js | 30 +++++++++---------- packages/block-library/src/image/save.js | 5 ++-- .../block-library/src/latest-comments/edit.js | 6 ++-- .../block-library/src/latest-posts/edit.js | 10 +++---- .../block-library/src/legacy-widget/edit.js | 14 ++++----- packages/block-library/src/media-text/edit.js | 10 +++---- .../src/media-text/media-container.js | 10 +++---- packages/block-library/src/missing/edit.js | 6 ++-- packages/block-library/src/more/edit.js | 6 ++-- packages/block-library/src/paragraph/edit.js | 9 ++---- packages/block-library/src/pullquote/edit.js | 9 ++---- packages/block-library/src/quote/edit.js | 5 ++-- packages/block-library/src/rss/edit.js | 6 ++-- packages/block-library/src/spacer/edit.js | 6 ++-- packages/block-library/src/subhead/edit.js | 5 ++-- packages/block-library/src/table/edit.js | 6 ++-- packages/block-library/src/tag-cloud/edit.js | 6 ++-- .../block-library/src/text-columns/edit.js | 5 ++-- packages/block-library/src/verse/edit.js | 5 ++-- packages/block-library/src/video/edit.js | 6 ++-- packages/components/src/date-time/index.js | 10 +++---- .../src/higher-order/with-filters/README.md | 10 +++---- packages/components/src/slot-fill/slot.js | 5 ++-- .../plugin-block-settings-menu-group.js | 5 ++-- .../src/components/header/more-menu/index.js | 5 ++-- .../edit-post/src/components/layout/index.js | 5 ++-- .../manage-blocks-modal/checklist.js | 5 ++-- .../src/components/meta-boxes/index.js | 5 ++-- .../sidebar/plugin-sidebar/index.js | 5 ++-- .../src/components/sidebar/post-link/index.js | 5 ++-- .../components/sidebar/post-schedule/index.js | 5 ++-- .../components/sidebar/post-status/index.js | 5 ++-- .../sidebar/settings-sidebar/index.js | 5 ++-- .../sidebar/sidebar-header/index.js | 5 ++-- packages/edit-post/src/plugins/index.js | 9 +++--- .../src/components/layout/index.js | 5 ++-- .../visual-editor-shortcuts.js | 6 ++-- .../post-publish-panel/postpublish.js | 4 +-- .../post-publish-panel/prepublish.js | 5 ++-- .../src/components/post-text-editor/index.js | 6 ++-- .../reusable-blocks-buttons/index.js | 5 ++-- .../reusable-block-convert-button.js | 5 ++-- .../src/components/table-of-contents/panel.js | 9 +++--- packages/format-library/src/bold/index.js | 5 ++-- packages/format-library/src/code/index.js | 5 ++-- packages/format-library/src/italic/index.js | 5 ++-- packages/format-library/src/link/index.js | 6 ++-- .../format-library/src/link/index.native.js | 6 ++-- .../format-library/src/strikethrough/index.js | 5 ++-- .../format-library/src/underline/index.js | 5 ++-- packages/plugins/README.md | 5 ++-- packages/plugins/src/api/index.js | 5 ++-- 85 files changed, 255 insertions(+), 313 deletions(-) diff --git a/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js b/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js index 9b30976a27ee02..7e9a433182eba6 100644 --- a/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/block-editor-keyboard-shortcuts/index.js @@ -6,7 +6,7 @@ import { first, last, some, flow } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; @@ -80,7 +80,7 @@ class BlockEditorKeyboardShortcuts extends Component { render() { const { selectedBlockClientIds } = this.props; return ( - <Fragment> + <> <KeyboardShortcuts shortcuts={ { [ rawShortcut.primary( 'a' ) ]: this.selectAll, @@ -114,7 +114,7 @@ class BlockEditorKeyboardShortcuts extends Component { ) } </BlockActions> ) } - </Fragment> + </> ); } } diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index fc54f9d174e61f..6682741f9101e8 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -10,7 +10,6 @@ import { __ } from '@wordpress/i18n'; import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -38,7 +37,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, } return ( - <Fragment> + <> <div className="editor-block-inspector__card block-editor-block-inspector__card"> <BlockIcon icon={ blockType.icon } showColors /> <div className="editor-block-inspector__card-content block-editor-block-inspector__card-content"> @@ -73,7 +72,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, </InspectorAdvancedControls.Slot> </div> <SkipToSelectedBlock key="back" /> - </Fragment> + </> ); }; diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index e76a0f8e207217..5e12d4ef18d457 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -7,7 +7,7 @@ import { get, reduce, size, first, last } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { focus, isTextField, @@ -582,7 +582,7 @@ export class BlockListBlock extends Component { </IgnoreNestedEvents> </div> { showEmptyBlockSideInserter && ( - <Fragment> + <> <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> <InserterWithShortcuts clientId={ clientId } @@ -598,7 +598,7 @@ export class BlockListBlock extends Component { clientId={ clientId } /> </div> - </Fragment> + </> ) } </IgnoreNestedEvents> ); diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index 0f22df00312b35..a0eb3a385407c6 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Toolbar } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -54,10 +54,10 @@ export class BlockBreadcrumb extends Component { <div className={ 'editor-block-list__breadcrumb block-editor-block-list__breadcrumb' }> <Toolbar> { rootClientId && ( - <Fragment> + <> <BlockTitle clientId={ rootClientId } /> <span className="editor-block-list__descendant-arrow block-editor-block-list__descendant-arrow" /> - </Fragment> + </> ) } <BlockTitle clientId={ clientId } /> </Toolbar> diff --git a/packages/block-editor/src/components/block-navigation/dropdown.js b/packages/block-editor/src/components/block-navigation/dropdown.js index ab8790cefa0d97..b4314a4042035b 100644 --- a/packages/block-editor/src/components/block-navigation/dropdown.js +++ b/packages/block-editor/src/components/block-navigation/dropdown.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { IconButton, Dropdown, SVG, Path, KeyboardShortcuts } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; @@ -24,7 +23,7 @@ function BlockNavigationDropdown( { hasBlocks, isDisabled } ) { return ( <Dropdown renderToggle={ ( { isOpen, onToggle } ) => ( - <Fragment> + <> { isEnabled && <KeyboardShortcuts bindGlobal shortcuts={ { @@ -41,7 +40,7 @@ function BlockNavigationDropdown( { hasBlocks, isDisabled } ) { shortcut={ displayShortcut.access( 'o' ) } aria-disabled={ ! isEnabled } /> - </Fragment> + </> ) } renderContent={ ( { onClose } ) => ( <BlockNavigation onSelect={ onClose } /> diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 05c023ac43b906..ca9df214fae228 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -8,7 +8,6 @@ import { castArray } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { Toolbar, Dropdown, NavigableMenu, MenuItem } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; @@ -79,7 +78,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { </MenuItem> ) } { ! isLocked && ( - <Fragment> + <> <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onInsertBefore } @@ -96,7 +95,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { > { __( 'Insert After' ) } </MenuItem> - </Fragment> + </> ) } { count === 1 && ( <BlockModeToggle diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 371975e53d088e..4d6964cc0e1109 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -9,7 +9,7 @@ import { castArray, filter, first, mapKeys, orderBy, uniq, map } from 'lodash'; import { __, _n, sprintf } from '@wordpress/i18n'; import { Dropdown, IconButton, Toolbar, PanelBody, Path, SVG } from '@wordpress/components'; import { getBlockType, getPossibleBlockTransformations, switchToBlockType, hasChildBlocksWithInserterSupport } from '@wordpress/blocks'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { DOWN } from '@wordpress/keycodes'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -117,17 +117,17 @@ export class BlockSwitcher extends Component { tooltip={ label } onKeyDown={ openOnArrowDown } icon={ ( - <Fragment> + <> <BlockIcon icon={ icon } showColors /> <SVG className="editor-block-switcher__transform block-editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> - </Fragment> + </> ) } /> </Toolbar> ); } } renderContent={ ( { onClose } ) => ( - <Fragment> + <> { hasBlockStyles && <PanelBody title={ __( 'Block Styles' ) } @@ -166,7 +166,7 @@ export class BlockSwitcher extends Component { attributes={ { ...blocks[ 0 ].attributes, className: hoveredClassName } } /> } - </Fragment> + </> ) } /> ); diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index c5bc11d920f95b..4eb26c2d50f86b 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -30,11 +29,11 @@ function BlockToolbar( { blockClientIds, isValid, mode } ) { return ( <div className="editor-block-toolbar block-editor-block-toolbar"> { mode === 'visual' && isValid && ( - <Fragment> + <> <BlockSwitcher clientIds={ blockClientIds } /> <BlockControls.Slot /> <BlockFormatControls.Slot /> - </Fragment> + </> ) } <BlockSettingsMenu clientIds={ blockClientIds } /> </div> diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md b/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md index 8c2c58a03720d4..8eca9ffe0861a6 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md @@ -12,7 +12,6 @@ In a block's `edit` implementation, render a `<BlockControls />` component. Then ```jsx import { registerBlockType } from '@wordpress/blocks'; -import { Fragment } from '@wordpress/element'; import { BlockControls, BlockVerticalAlignmentToolbar, @@ -38,7 +37,7 @@ registerBlockType( 'my-plugin/my-block', { const onChange = ( alignment ) => setAttributes( { verticalAlignment: alignment } ); return ( - <Fragment> + <> <BlockControls> <BlockVerticalAlignmentToolbar onChange={ onChange } @@ -48,7 +47,7 @@ registerBlockType( 'my-plugin/my-block', { <div> // your Block here </div> - </Fragment> + </> ); } } ); @@ -81,4 +80,4 @@ const onChange = ( alignment ) => setAttributes( { verticalAlignment: alignment ## Examples -The [Core Columns](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src/columns) Block utilises the `BlockVerticalAlignmentToolbar`. \ No newline at end of file +The [Core Columns](https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src/columns) Block utilises the `BlockVerticalAlignmentToolbar`. diff --git a/packages/block-editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md index 9bab201e722a79..f8b19593fbf976 100644 --- a/packages/block-editor/src/components/inspector-controls/README.md +++ b/packages/block-editor/src/components/inspector-controls/README.md @@ -236,7 +236,6 @@ registerBlockType( 'my-plugin/inspector-controls-example', { {% ESNext %} ```js const { registerBlockType } = wp.blocks; -const { Fragment } = wp.element; const { CheckboxControl, RadioControl, @@ -309,7 +308,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { } return ( - <Fragment> + <> <InspectorControls> <CheckboxControl @@ -366,7 +365,7 @@ registerBlockType( 'my-plugin/inspector-controls-example', { onChange={ onChangeContent } value={ content } /> - </Fragment> + </> ); }, diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 8f042ed1d893a8..07daa4d8f813eb 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -22,7 +22,7 @@ import { withFilters, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; @@ -327,7 +327,7 @@ export class MediaPlaceholder extends Component { if ( mediaUpload && isAppender ) { return ( - <Fragment> + <> { this.renderDropZone() } <FormFileUpload onChange={ this.onUpload } @@ -335,7 +335,7 @@ export class MediaPlaceholder extends Component { multiple={ multiple } render={ ( { openFileDialog } ) => { const content = ( - <Fragment> + <> <IconButton isLarge className={ classnames( @@ -350,17 +350,17 @@ export class MediaPlaceholder extends Component { { mediaLibraryButton } { this.renderUrlSelectionUI() } { this.renderCancelLink() } - </Fragment> + </> ); return this.renderPlaceholder( content, openFileDialog ); } } /> - </Fragment> + </> ); } if ( mediaUpload ) { const content = ( - <Fragment> + <> { this.renderDropZone() } <FormFileUpload isLarge @@ -378,7 +378,7 @@ export class MediaPlaceholder extends Component { { mediaLibraryButton } { this.renderUrlSelectionUI() } { this.renderCancelLink() } - </Fragment> + </> ); return this.renderPlaceholder( content ); } diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js index 29911206aba839..f37de7f2091b5f 100644 --- a/packages/block-editor/src/components/rich-text/format-edit.js +++ b/packages/block-editor/src/components/rich-text/format-edit.js @@ -2,12 +2,11 @@ * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; import { getActiveFormat, getActiveObject } from '@wordpress/rich-text'; const FormatEdit = ( { formatTypes, onChange, value } ) => { return ( - <Fragment> + <> { formatTypes.map( ( { name, edit: Edit } ) => { if ( ! Edit ) { return null; @@ -34,7 +33,7 @@ const FormatEdit = ( { formatTypes, onChange, value } ) => { /> ); } ) } - </Fragment> + </> ); }; diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index d47e7d71e12ed2..90b10613f86229 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -13,7 +13,7 @@ import memize from 'memize'; /** * WordPress dependencies */ -import { Component, Fragment, RawHTML } from '@wordpress/element'; +import { Component, RawHTML } from '@wordpress/element'; import { isHorizontalEdge } from '@wordpress/dom'; import { createBlobURL } from '@wordpress/blob'; import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; @@ -1080,7 +1080,7 @@ export class RichText extends Component { onChange={ this.onChange } > { ( { listBoxId, activeId } ) => ( - <Fragment> + <> <Editable tagName={ Tagname } style={ style } @@ -1113,7 +1113,7 @@ export class RichText extends Component { </Tagname> } { isSelected && <FormatEdit value={ record } onChange={ this.onChange } /> } - </Fragment> + </> ) } </Autocomplete> { isSelected && <RemoveBrowserShortcuts /> } diff --git a/packages/block-editor/src/components/rich-text/list-edit.js b/packages/block-editor/src/components/rich-text/list-edit.js index 808bb7fbdd6a5d..762f24b8a10025 100644 --- a/packages/block-editor/src/components/rich-text/list-edit.js +++ b/packages/block-editor/src/components/rich-text/list-edit.js @@ -4,7 +4,6 @@ import { Toolbar } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { __unstableIndentListItems as indentListItems, __unstableOutdentListItems as outdentListItems, @@ -26,7 +25,7 @@ export const ListEdit = ( { value, onChange, } ) => ( - <Fragment> + <> <RichTextShortcut type="primary" character="[" @@ -101,5 +100,5 @@ export const ListEdit = ( { ].filter( Boolean ) } /> </BlockFormatControls> - </Fragment> + </> ); diff --git a/packages/block-editor/src/components/url-popover/README.md b/packages/block-editor/src/components/url-popover/README.md index 8245f9e9c49331..240daaf43c45d3 100644 --- a/packages/block-editor/src/components/url-popover/README.md +++ b/packages/block-editor/src/components/url-popover/README.md @@ -8,7 +8,6 @@ URLPopover is a presentational React component used to render a popover used for The component will be rendered adjacent to its parent. ```jsx -import { Fragment } from '@wordpress/element'; import { ToggleControl, IconButton, Button } from '@wordpress/components'; import { URLPopover } from '@wordpress/block-editor'; @@ -58,7 +57,7 @@ class MyURLPopover extends Component { const { url, isVisible, isEditing } = this.state; return ( - <Fragment> + <> <Button onClick={ this.openURLPopover }>Edit URL</Button> { isVisible && ( <URLPopover @@ -77,7 +76,7 @@ class MyURLPopover extends Component { </form> </URLPopover> ) } - </Fragment> + </> ); } } diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 3af32e39ef2ac4..2b9e7adfd9dd1c 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -6,7 +6,6 @@ import { assign } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -63,7 +62,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => if ( hasAnchor && props.isSelected ) { return ( - <Fragment> + <> <BlockEdit { ...props } /> <InspectorAdvancedControls> <TextControl @@ -77,7 +76,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => } ); } } /> </InspectorAdvancedControls> - </Fragment> + </> ); } diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index 3d4af472276701..b8c242f05a9267 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; import { TextControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -57,7 +56,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => const hasCustomClassName = hasBlockSupport( props.name, 'customClassName', true ); if ( hasCustomClassName && props.isSelected ) { return ( - <Fragment> + <> <BlockEdit { ...props } /> <InspectorAdvancedControls> <TextControl @@ -70,7 +69,7 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => } } /> </InspectorAdvancedControls> - </Fragment> + </> ); } diff --git a/packages/block-library/src/archives/edit.js b/packages/block-library/src/archives/edit.js index 485233ce5c8eb4..c67cdc912e5260 100644 --- a/packages/block-library/src/archives/edit.js +++ b/packages/block-library/src/archives/edit.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { PanelBody, ToggleControl, @@ -15,7 +14,7 @@ export default function ArchivesEdit( { attributes, setAttributes } ) { const { showPostCounts, displayAsDropdown } = attributes; return ( - <Fragment> + <> <InspectorControls> <PanelBody title={ __( 'Archives Settings' ) }> <ToggleControl @@ -33,6 +32,6 @@ export default function ArchivesEdit( { attributes, setAttributes } ) { <Disabled> <ServerSideRender block="core/archives" attributes={ attributes } /> </Disabled> - </Fragment> + </> ); } diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index d222b42ae6a641..7a704382e36c0b 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -19,7 +19,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -136,7 +136,7 @@ class AudioEdit extends Component { /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ return ( - <Fragment> + <> <BlockControls> <Toolbar> <IconButton @@ -190,7 +190,7 @@ class AudioEdit extends Component { /> ) } </figure> - </Fragment> + </> ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } diff --git a/packages/block-library/src/block/edit-panel/index.js b/packages/block-library/src/block/edit-panel/index.js index eb620a877b63a1..0bc064d25203c7 100644 --- a/packages/block-library/src/block/edit-panel/index.js +++ b/packages/block-library/src/block/edit-panel/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { ESCAPE } from '@wordpress/keycodes'; import { withInstanceId } from '@wordpress/compose'; @@ -56,7 +56,7 @@ class ReusableBlockEditPanel extends Component { const { isEditing, title, isSaving, isEditDisabled, onEdit, instanceId } = this.props; return ( - <Fragment> + <> { ( ! isEditing && ! isSaving ) && ( <div className="reusable-block-edit-panel"> <b className="reusable-block-edit-panel__info"> @@ -102,7 +102,7 @@ class ReusableBlockEditPanel extends Component { </Button> </form> ) } - </Fragment> + </> ); } } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 722aad97c9bfcc..785f5a762c01fd 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -6,7 +6,7 @@ import { noop, partial } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Placeholder, Spinner, Disabled } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -124,7 +124,7 @@ class ReusableBlockEdit extends Component { } return ( - <Fragment> + <> { ( isSelected || isEditing ) && ( <ReusableBlockEditPanel isEditing={ isEditing } @@ -139,7 +139,7 @@ class ReusableBlockEdit extends Component { ) } { ! isSelected && ! isEditing && <ReusableBlockIndicator title={ reusableBlock.title } /> } { element } - </Fragment> + </> ); } } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 1cf0c1e43d049c..6d3f279542e009 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -7,10 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - Component, - Fragment, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { Dashicon, @@ -75,7 +72,7 @@ class ButtonEdit extends Component { } = attributes; return ( - <Fragment> + <> <div className={ className } title={ title } ref={ this.bindRef }> <RichText placeholder={ __( 'Add text…' ) } @@ -138,7 +135,7 @@ class ButtonEdit extends Component { <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> </form> ) } - </Fragment> + </> ); } } diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index faee47015394ae..9020c33d484788 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -10,7 +10,7 @@ import { PanelBody, Placeholder, Spinner, ToggleControl } from '@wordpress/compo import { compose, withInstanceId } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { InspectorControls } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; class CategoriesEdit extends Component { @@ -112,14 +112,14 @@ class CategoriesEdit extends Component { const categories = this.getCategories( parentId ); const selectId = `blocks-category-select-${ instanceId }`; return ( - <Fragment> + <> <label htmlFor={ selectId } className="screen-reader-text"> { __( 'Categories' ) } </label> <select id={ selectId } className="wp-block-categories__dropdown"> { categories.map( ( category ) => this.renderCategoryDropdownItem( category, 0 ) ) } </select> - </Fragment> + </> ); } @@ -172,7 +172,7 @@ class CategoriesEdit extends Component { if ( isRequesting ) { return ( - <Fragment> + <> { inspectorControls } <Placeholder icon="admin-post" @@ -180,12 +180,12 @@ class CategoriesEdit extends Component { > <Spinner /> </Placeholder> - </Fragment> + </> ); } return ( - <Fragment> + <> { inspectorControls } <div className={ this.props.className }> { @@ -194,7 +194,7 @@ class CategoriesEdit extends Component { this.renderCategoryList() } </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 206b313427cf99..15adfff384dfff 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -12,7 +12,6 @@ import { PanelBody, RangeControl, } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { InspectorControls, InnerBlocks, @@ -50,7 +49,7 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd }; return ( - <Fragment> + <> <InspectorControls> <PanelBody> <RangeControl @@ -78,7 +77,7 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd templateLock="all" allowedBlocks={ ALLOWED_BLOCKS } /> </div> - </Fragment> + </> ); }; diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 9fadb5541082b5..bf9ba990cb9219 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -29,7 +29,7 @@ import { PanelColorSettings, withColors, } from '@wordpress/editor'; -import { Component, createRef, Fragment } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -158,10 +158,10 @@ class CoverEdit extends Component { } const controls = ( - <Fragment> + <> <BlockControls> { !! url && ( - <Fragment> + <> <MediaUploadCheck> <Toolbar> <MediaUpload @@ -179,7 +179,7 @@ class CoverEdit extends Component { /> </Toolbar> </MediaUploadCheck> - </Fragment> + </> ) } </BlockControls> { !! url && ( @@ -222,7 +222,7 @@ class CoverEdit extends Component { </PanelBody> </InspectorControls> ) } - </Fragment> + </> ); if ( ! url ) { @@ -230,7 +230,7 @@ class CoverEdit extends Component { const label = __( 'Cover' ); return ( - <Fragment> + <> { controls } <MediaPlaceholder icon={ placeholderIcon } @@ -245,7 +245,7 @@ class CoverEdit extends Component { notices={ noticeUI } onError={ noticeOperations.createErrorNotice } /> - </Fragment> + </> ); } @@ -260,7 +260,7 @@ class CoverEdit extends Component { ); return ( - <Fragment> + <> { controls } <div data-url={ url } @@ -296,7 +296,7 @@ class CoverEdit extends Component { /> </div> </div> - </Fragment> + </> ); } diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index fcc305d9e8a7c3..e62a6173001833 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -16,7 +16,7 @@ import { kebabCase, toLower } from 'lodash'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; export function getEmbedEditComponent( title, icon, responsive = true ) { return class extends Component { @@ -180,7 +180,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } return ( - <Fragment> + <> <EmbedControls showEditButton={ preview && ! cannotEmbed } themeSupportsResponsive={ themeSupportsResponsive } @@ -201,7 +201,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { icon={ icon } label={ label } /> - </Fragment> + </> ); } }; diff --git a/packages/block-library/src/embed/embed-controls.js b/packages/block-library/src/embed/embed-controls.js index f768f3fa529038..fe115b403be224 100644 --- a/packages/block-library/src/embed/embed-controls.js +++ b/packages/block-library/src/embed/embed-controls.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { IconButton, Toolbar, PanelBody, ToggleControl } from '@wordpress/components'; import { BlockControls, InspectorControls } from '@wordpress/block-editor'; @@ -17,7 +16,7 @@ const EmbedControls = ( props ) => { switchBackToURLInput, } = props; return ( - <Fragment> + <> <BlockControls> <Toolbar> { showEditButton && ( @@ -42,7 +41,7 @@ const EmbedControls = ( props ) => { </PanelBody> </InspectorControls> ) } - </Fragment> + </> ); }; diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 9aa76977cc2cec..efa93424a5a9e6 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -28,7 +28,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; /** @@ -166,7 +166,7 @@ class FileEdit extends Component { } ); return ( - <Fragment> + <> <FileBlockInspector hrefs={ { href, textLinkHref, attachmentPage } } { ...{ @@ -234,7 +234,7 @@ class FileEdit extends Component { </ClipboardButton> } </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index 6ed6747f4455c6..611c06ae9572a8 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -7,7 +7,6 @@ import { SelectControl, ToggleControl, } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; export default function FileBlockInspector( { @@ -29,7 +28,7 @@ export default function FileBlockInspector( { } return ( - <Fragment> + <> <InspectorControls> <PanelBody title={ __( 'Text Link Settings' ) }> <SelectControl @@ -52,6 +51,6 @@ export default function FileBlockInspector( { /> </PanelBody> </InspectorControls> - </Fragment> + </> ); } diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 6013bc2d79d07e..e6b109da9ad0e3 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -23,7 +23,7 @@ import { MediaUpload, InspectorControls, } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; /** @@ -201,15 +201,15 @@ class GalleryEdit extends Component { if ( ! hasImages ) { return ( - <Fragment> + <> { controls } { mediaPlaceholder } - </Fragment> + </> ); } return ( - <Fragment> + <> { controls } <InspectorControls> <PanelBody title={ __( 'Gallery Settings' ) }> @@ -268,7 +268,7 @@ class GalleryEdit extends Component { } ) } </ul> { mediaPlaceholder } - </Fragment> + </> ); } } diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index 9deae12a3ca02f..e69b8385fe7e52 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { IconButton, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; @@ -103,7 +103,7 @@ class GalleryImage extends Component { // Disable reason: Image itself is not meant to be interactive, but should // direct image selection and unfocus caption fields. /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - <Fragment> + <> <img src={ url } alt={ alt } @@ -116,7 +116,7 @@ class GalleryImage extends Component { ref={ this.bindContainer } /> { isBlobURL( url ) && <Spinner /> } - </Fragment> + </> /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ ); diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index f391a277a46bbb..f6756a7750c223 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; @@ -32,7 +31,7 @@ function GroupEdit( { } ); return ( - <Fragment> + <> <InspectorControls> <PanelColorSettings title={ __( 'Color Settings' ) } @@ -50,7 +49,7 @@ function GroupEdit( { renderAppender={ ! hasInnerBlocks && InnerBlocks.ButtonBlockAppender } /> </div> - </Fragment> + </> ); } diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index cf723896a55a03..5c57b5e5d57bc1 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -7,7 +7,6 @@ import HeadingToolbar from './heading-toolbar'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { PanelBody } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; import { @@ -29,7 +28,7 @@ export default function HeadingEdit( { const tagName = 'h' + level; return ( - <Fragment> + <> <BlockControls> <HeadingToolbar minLevel={ 2 } maxLevel={ 5 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> </BlockControls> @@ -69,6 +68,6 @@ export default function HeadingEdit( { className={ className } placeholder={ placeholder || __( 'Write heading…' ) } /> - </Fragment> + </> ); } diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index cea813aefc4a6d..91eeb5a40d6baf 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -44,7 +44,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; import { withViewportMatch } from '@wordpress/viewport'; @@ -431,10 +431,10 @@ class ImageEdit extends Component { ); if ( isEditing || ! url ) { return ( - <Fragment> + <> { controls } { mediaPlaceholder } - </Fragment> + </> ); } @@ -456,12 +456,12 @@ class ImageEdit extends Component { value={ alt } onChange={ this.updateAlt } help={ - <Fragment> + <> <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> { __( 'Describe the purpose of the image' ) } </ExternalLink> { __( 'Leave empty if the image is purely decorative.' ) } - </Fragment> + </> } /> { ! isEmpty( imageSizeOptions ) && ( @@ -534,7 +534,7 @@ class ImageEdit extends Component { onChange={ this.onSetLinkDestination } /> { linkDestination !== LINK_DESTINATION_NONE && ( - <Fragment> + <> <TextControl label={ __( 'Link URL' ) } value={ href || '' } @@ -556,7 +556,7 @@ class ImageEdit extends Component { value={ rel || '' } onChange={ this.onSetLinkRel } /> - </Fragment> + </> ) } </PanelBody> </InspectorControls> @@ -565,7 +565,7 @@ class ImageEdit extends Component { // Disable reason: Each block can be selected by clicking on it /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ return ( - <Fragment> + <> { controls } <figure className={ classes }> <ImageSize src={ url } dirtynessTrigger={ align }> @@ -591,7 +591,7 @@ class ImageEdit extends Component { // Disable reason: Image itself is not meant to be interactive, but // should direct focus to block. /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - <Fragment> + <> <img src={ url } alt={ defaultedAlt } @@ -600,18 +600,18 @@ class ImageEdit extends Component { onError={ () => this.onImageError( url ) } /> { isBlobURL( url ) && <Spinner /> } - </Fragment> + </> /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ ); if ( ! isResizable || ! imageWidthWithinContainer ) { return ( - <Fragment> + <> { getInspectorControls( imageWidth, imageHeight ) } <div style={ { width, height } }> { img } </div> - </Fragment> + </> ); } @@ -659,7 +659,7 @@ class ImageEdit extends Component { /* eslint-enable no-lonely-if */ return ( - <Fragment> + <> { getInspectorControls( imageWidth, imageHeight ) } <ResizableBox size={ @@ -692,7 +692,7 @@ class ImageEdit extends Component { > { img } </ResizableBox> - </Fragment> + </> ); } } </ImageSize> @@ -709,7 +709,7 @@ class ImageEdit extends Component { ) } </figure> { mediaPlaceholder } - </Fragment> + </> ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index 032759c34c158d..ae023d3399b02b 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; * WordPress dependencies */ import { RichText } from '@wordpress/block-editor'; -import { Fragment } from '@wordpress/element'; export default function save( { attributes } ) { const { @@ -40,7 +39,7 @@ export default function save( { attributes } ) { ); const figure = ( - <Fragment> + <> { href ? ( <a className={ linkClass } @@ -52,7 +51,7 @@ export default function save( { attributes } ) { </a> ) : image } { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } - </Fragment> + </> ); if ( 'left' === align || 'right' === align || 'center' === align ) { diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index b87aecdabd7d12..61e7ec3e3aaabb 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -9,7 +9,7 @@ import { ToggleControl, } from '@wordpress/components'; import { ServerSideRender } from '@wordpress/editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -61,7 +61,7 @@ class LatestComments extends Component { } = this.props.attributes; return ( - <Fragment> + <> <InspectorControls> <PanelBody title={ __( 'Latest Comments Settings' ) }> <ToggleControl @@ -95,7 +95,7 @@ class LatestComments extends Component { attributes={ this.props.attributes } /> </Disabled> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index e8811a310787af..a98f9459ac7562 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, Fragment, RawHTML } from '@wordpress/element'; +import { Component, RawHTML } from '@wordpress/element'; import { PanelBody, Placeholder, @@ -138,7 +138,7 @@ class LatestPostsEdit extends Component { const hasPosts = Array.isArray( latestPosts ) && latestPosts.length; if ( ! hasPosts ) { return ( - <Fragment> + <> { inspectorControls } <Placeholder icon="admin-post" @@ -149,7 +149,7 @@ class LatestPostsEdit extends Component { __( 'No posts found.' ) } </Placeholder> - </Fragment> + </> ); } @@ -176,7 +176,7 @@ class LatestPostsEdit extends Component { const dateFormat = __experimentalGetSettings().formats.date; return ( - <Fragment> + <> { inspectorControls } <BlockControls> <Toolbar controls={ layoutControls } /> @@ -238,7 +238,7 @@ class LatestPostsEdit extends Component { ); } ) } </ul> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/legacy-widget/edit.js b/packages/block-library/src/legacy-widget/edit.js index e2f2a4c4cf2405..89621422bc6650 100644 --- a/packages/block-library/src/legacy-widget/edit.js +++ b/packages/block-library/src/legacy-widget/edit.js @@ -6,7 +6,7 @@ import { map } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Button, IconButton, @@ -99,15 +99,15 @@ class LegacyWidgetEdit extends Component { ); if ( ! hasPermissionsToManageWidgets ) { return ( - <Fragment> + <> { inspectorControls } { this.renderWidgetPreview() } - </Fragment> + </> ); } return ( - <Fragment> + <> <BlockControls> <Toolbar> <IconButton @@ -117,7 +117,7 @@ class LegacyWidgetEdit extends Component { > </IconButton> { ! isCallbackWidget && ( - <Fragment> + <> <Button className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } onClick={ this.switchToEdit } @@ -130,7 +130,7 @@ class LegacyWidgetEdit extends Component { > <span>{ __( 'Preview' ) }</span> </Button> - </Fragment> + </> ) } </Toolbar> </BlockControls> @@ -150,7 +150,7 @@ class LegacyWidgetEdit extends Component { /> ) } { ( isPreview || isCallbackWidget ) && this.renderWidgetPreview() } - </Fragment> + </> ); } diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index 48e18f2a30f026..e4f843bcc24bf4 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -16,7 +16,7 @@ import { PanelColorSettings, withColors, } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { PanelBody, TextareaControl, @@ -198,18 +198,18 @@ class MediaTextEdit extends Component { value={ mediaAlt } onChange={ onMediaAltChange } help={ - <Fragment> + <> <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> { __( 'Describe the purpose of the image' ) } </ExternalLink> { __( 'Leave empty if the image is purely decorative.' ) } - </Fragment> + </> } /> ) } </PanelBody> ); return ( - <Fragment> + <> <InspectorControls> { mediaTextGeneralSettings } <PanelColorSettings @@ -235,7 +235,7 @@ class MediaTextEdit extends Component { templateInsertUpdatesSelection={ false } /> </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index e32ebb47da6d03..9afc28c58b3a7c 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -8,7 +8,7 @@ import { MediaPlaceholder, MediaUpload, } from '@wordpress/block-editor'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -58,24 +58,24 @@ class MediaContainer extends Component { const { mediaAlt, mediaUrl, className, imageFill, focalPoint } = this.props; const backgroundStyles = imageFill ? imageFillStyles( mediaUrl, focalPoint ) : {}; return ( - <Fragment> + <> { this.renderToolbarEditButton() } <figure className={ className } style={ backgroundStyles }> <img src={ mediaUrl } alt={ mediaAlt } /> </figure> - </Fragment> + </> ); } renderVideo() { const { mediaUrl, className } = this.props; return ( - <Fragment> + <> { this.renderToolbarEditButton() } <figure className={ className }> <video controls src={ mediaUrl } /> </figure> - </Fragment> + </> ); } diff --git a/packages/block-library/src/missing/edit.js b/packages/block-library/src/missing/edit.js index fbd205e98f79c0..15d8a33946d422 100644 --- a/packages/block-library/src/missing/edit.js +++ b/packages/block-library/src/missing/edit.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { RawHTML, Fragment } from '@wordpress/element'; +import { RawHTML } from '@wordpress/element'; import { Button } from '@wordpress/components'; import { getBlockType, createBlock } from '@wordpress/blocks'; import { withDispatch } from '@wordpress/data'; @@ -33,12 +33,12 @@ function MissingBlockWarning( { attributes, convertToHTML } ) { } return ( - <Fragment> + <> <Warning actions={ actions }> { messageHTML } </Warning> <RawHTML>{ originalUndelimitedContent }</RawHTML> - </Fragment> + </> ); } diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index 3840182a80d845..670ef8027e5e59 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody, ToggleControl } from '@wordpress/components'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; import { ENTER } from '@wordpress/keycodes'; import { @@ -56,7 +56,7 @@ export default class MoreEdit extends Component { const inputLength = value.length + 1; return ( - <Fragment> + <> <InspectorControls> <PanelBody> <ToggleControl @@ -76,7 +76,7 @@ export default class MoreEdit extends Component { onKeyDown={ this.onKeyDown } /> </div> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 91dc83c69984f9..7fcf059100a004 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -7,10 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { - Component, - Fragment, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { PanelBody, ToggleControl, @@ -151,7 +148,7 @@ class ParagraphBlock extends Component { } = attributes; return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ align } @@ -249,7 +246,7 @@ class ParagraphBlock extends Component { aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) } placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) } /> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/pullquote/edit.js b/packages/block-library/src/pullquote/edit.js index 7875d6e7e5fbbc..cd5acddd1ace6f 100644 --- a/packages/block-library/src/pullquote/edit.js +++ b/packages/block-library/src/pullquote/edit.js @@ -8,10 +8,7 @@ import { includes } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - Component, - Fragment, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { RichText, ContrastChecker, @@ -76,7 +73,7 @@ class PullQuoteEdit extends Component { [ textColor.class ]: textColor.class, } ) : undefined; return ( - <Fragment> + <> <figure style={ figureStyle } className={ classnames( className, { [ mainColor.class ]: isSolidColorStyle && mainColor.class, @@ -140,7 +137,7 @@ class PullQuoteEdit extends Component { ) } </PanelColorSettings> </InspectorControls> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 82e4ed78972ba6..9c01310a3ade03 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -2,13 +2,12 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/block-editor'; export default function QuoteEdit( { attributes, setAttributes, isSelected, mergeBlocks, onReplace, className } ) { const { align, value, citation } = attributes; return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ align } @@ -56,6 +55,6 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg /> ) } </blockquote> - </Fragment> + </> ); } diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 9b98eaea537819..43bbf41fd1ed2d 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { Button, Disabled, @@ -107,7 +107,7 @@ class RSSEdit extends Component { ]; return ( - <Fragment> + <> <BlockControls> <Toolbar controls={ toolbarControls } /> </BlockControls> @@ -164,7 +164,7 @@ class RSSEdit extends Component { attributes={ this.props.attributes } /> </Disabled> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/spacer/edit.js b/packages/block-library/src/spacer/edit.js index 5a02543cfaf5ba..fdb1abcf9d2395 100644 --- a/packages/block-library/src/spacer/edit.js +++ b/packages/block-library/src/spacer/edit.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment, useState } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import { BaseControl, PanelBody, ResizableBox } from '@wordpress/components'; @@ -18,7 +18,7 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, toggleSelection, i const [ inputHeightValue, setInputHeightValue ] = useState( height ); return ( - <Fragment> + <> <ResizableBox className={ classnames( 'block-library-spacer__resize-container', @@ -78,7 +78,7 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, toggleSelection, i </BaseControl> </PanelBody> </InspectorControls> - </Fragment> + </> ); }; diff --git a/packages/block-library/src/subhead/edit.js b/packages/block-library/src/subhead/edit.js index 53535620b0f812..8ceda8116ad7cc 100644 --- a/packages/block-library/src/subhead/edit.js +++ b/packages/block-library/src/subhead/edit.js @@ -3,7 +3,6 @@ */ import deprecated from '@wordpress/deprecated'; import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { RichText, BlockControls, @@ -19,7 +18,7 @@ export default function SubheadEdit( { attributes, setAttributes, className } ) } ); return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ align } @@ -40,6 +39,6 @@ export default function SubheadEdit( { attributes, setAttributes, className } ) className={ className } placeholder={ placeholder || __( 'Write subheading…' ) } /> - </Fragment> + </> ); } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index a54b3e9d1789d7..2bed02abc2c1bf 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment, Component } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { InspectorControls, BlockControls, @@ -431,7 +431,7 @@ export class TableEdit extends Component { } ); return ( - <Fragment> + <> <BlockControls> <Toolbar> <DropdownMenu @@ -468,7 +468,7 @@ export class TableEdit extends Component { <Section type="body" rows={ body } /> <Section type="foot" rows={ foot } /> </table> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 9a58c64de87e16..ae653ffbfa9e98 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -6,7 +6,7 @@ import { map, filter } from 'lodash'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { PanelBody, ToggleControl, @@ -82,14 +82,14 @@ class TagCloudEdit extends Component { ); return ( - <Fragment> + <> { inspectorControls } <ServerSideRender key="tag-cloud" block="core/tag-cloud" attributes={ attributes } /> - </Fragment> + </> ); } } diff --git a/packages/block-library/src/text-columns/edit.js b/packages/block-library/src/text-columns/edit.js index 91ea2f1fa90eff..ec3368acdeccca 100644 --- a/packages/block-library/src/text-columns/edit.js +++ b/packages/block-library/src/text-columns/edit.js @@ -8,7 +8,6 @@ import { get, times } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { PanelBody, RangeControl } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { BlockControls, BlockAlignmentToolbar, @@ -26,7 +25,7 @@ export default function TextColumnsEdit( { attributes, setAttributes, className } ); return ( - <Fragment> + <> <BlockControls> <BlockAlignmentToolbar value={ width } @@ -68,6 +67,6 @@ export default function TextColumnsEdit( { attributes, setAttributes, className ); } ) } </div> - </Fragment> + </> ); } diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 972b3c222b90e5..edb17517d5f093 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { RichText, BlockControls, @@ -13,7 +12,7 @@ export default function VerseEdit( { attributes, setAttributes, className, merge const { textAlign, content } = attributes; return ( - <Fragment> + <> <BlockControls> <AlignmentToolbar value={ textAlign } @@ -35,6 +34,6 @@ export default function VerseEdit( { attributes, setAttributes, className, merge wrapperClassName={ className } onMerge={ mergeBlocks } /> - </Fragment> + </> ); } diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 35532afe073ccd..2ec1ad42041c83 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -23,7 +23,7 @@ import { RichText, } from '@wordpress/block-editor'; import { mediaUpload } from '@wordpress/editor'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { __, sprintf, @@ -183,7 +183,7 @@ class VideoEdit extends Component { /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ return ( - <Fragment> + <> <BlockControls> <Toolbar> <IconButton @@ -294,7 +294,7 @@ class VideoEdit extends Component { /> ) } </figure> - </Fragment> + </> ); /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } diff --git a/packages/components/src/date-time/index.js b/packages/components/src/date-time/index.js index e3782393fe2e8f..97bd891cac9a7c 100644 --- a/packages/components/src/date-time/index.js +++ b/packages/components/src/date-time/index.js @@ -8,7 +8,7 @@ import 'react-dates/initialize'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; /** @@ -39,7 +39,7 @@ export class DateTimePicker extends Component { return ( <div className="components-datetime"> { ! this.state.calendarHelpIsVisible && ( - <Fragment> + <> <TimePicker currentTime={ currentDate } onChange={ onChange } @@ -49,11 +49,11 @@ export class DateTimePicker extends Component { currentDate={ currentDate } onChange={ onChange } /> - </Fragment> + </> ) } { this.state.calendarHelpIsVisible && ( - <Fragment> + <> <div className="components-datetime__calendar-help"> <h4>{ __( 'Click to Select' ) }</h4> <ul> @@ -94,7 +94,7 @@ export class DateTimePicker extends Component { { __( 'Close' ) } </Button> </div> - </Fragment> + </> ) } { ! this.state.calendarHelpIsVisible && ( diff --git a/packages/components/src/higher-order/with-filters/README.md b/packages/components/src/higher-order/with-filters/README.md index 41a936c8541626..a5522fd8a14b4f 100644 --- a/packages/components/src/higher-order/with-filters/README.md +++ b/packages/components/src/higher-order/with-filters/README.md @@ -7,7 +7,6 @@ Wrapping a component with `withFilters` provides a filtering capability controll ## Usage ```jsx -import { Fragment } from '@wordpress/element'; import { withFilters } from '@wordpress/components'; import { addFilter } from '@wordpress/hooks'; @@ -17,10 +16,10 @@ const ComponentToAppend = () => <div>Appended component</div>; function withComponentAppended( FilteredComponent ) { return ( props ) => ( - <Fragment> + <> <FilteredComponent { ...props } /> <ComponentToAppend /> - </Fragment> + </> ); } @@ -38,15 +37,14 @@ const MyComponentWithFilters = withFilters( 'MyHookName' )( MyComponent ); It is also possible to override props by implementing a higher-order component which works as follows: ```jsx -import { Fragment } from '@wordpress/element'; import { withFilters } from '@wordpress/components'; import { addFilter } from '@wordpress/hooks'; const MyComponent = ( { hint, title } ) => ( - <Fragment> + <> <h1>{ title }</h1> <p>{ hint }</p> - </Fragment> + </> ); function withHintOverridden( FilteredComponent ) { diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 13a226eab5de5d..68f11b1395b83a 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -15,7 +15,6 @@ import { Children, Component, cloneElement, - Fragment, isEmptyElement, } from '@wordpress/element'; @@ -83,9 +82,9 @@ class SlotComponent extends Component { ); return ( - <Fragment> + <> { isFunction( children ) ? children( fills ) : fills } - </Fragment> + </> ); } } diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js index fa056618f5fc34..a762c52a9f2a3a 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js @@ -7,7 +7,6 @@ import { isEmpty, map } from 'lodash'; * WordPress dependencies */ import { createSlotFill } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; const { Fill: PluginBlockSettingsMenuGroup, Slot } = createSlotFill( 'PluginBlockSettingsMenuGroup' ); @@ -17,10 +16,10 @@ const PluginBlockSettingsMenuGroupSlot = ( { fillProps, selectedBlocks } ) => { return ( <Slot fillProps={ { ...fillProps, selectedBlocks } } > { ( fills ) => ! isEmpty( fills ) && ( - <Fragment> + <> <div className="editor-block-settings-menu__separator block-editor-block-settings-menu__separator" /> { fills } - </Fragment> + </> ) } </Slot> ); diff --git a/packages/edit-post/src/components/header/more-menu/index.js b/packages/edit-post/src/components/header/more-menu/index.js index 5255740df260fd..9458a73c66b5e3 100644 --- a/packages/edit-post/src/components/header/more-menu/index.js +++ b/packages/edit-post/src/components/header/more-menu/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { IconButton, Dropdown, MenuGroup } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -32,7 +31,7 @@ const MoreMenu = () => ( /> ) } renderContent={ ( { onClose } ) => ( - <Fragment> + <> <WritingMenu /> <ModeSwitcher /> <PluginMoreMenuGroup.Slot fillProps={ { onClose } } /> @@ -40,7 +39,7 @@ const MoreMenu = () => ( <MenuGroup> <OptionsMenuItem onSelect={ onClose } /> </MenuGroup> - </Fragment> + </> ) } /> ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 643184f4b971be..364208959d1b63 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -22,7 +22,6 @@ import { PostPublishPanel, } from '@wordpress/editor'; import { withDispatch, withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; import { PluginArea } from '@wordpress/plugins'; import { withViewportMatch } from '@wordpress/viewport'; import { compose } from '@wordpress/compose'; @@ -111,7 +110,7 @@ function Layout( { PostPublishExtension={ PluginPostPublishPanel.Slot } /> ) : ( - <Fragment> + <> <div className="edit-post-toggle-publish-panel" { ...publishLandmarkProps }> <Button isDefault @@ -128,7 +127,7 @@ function Layout( { { isMobileViewport && sidebarIsOpened && <ScrollLock /> } - </Fragment> + </> ) } <Popover.Slot /> <PluginArea /> diff --git a/packages/edit-post/src/components/manage-blocks-modal/checklist.js b/packages/edit-post/src/components/manage-blocks-modal/checklist.js index b65042929d647f..22ebb8139934a4 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/checklist.js +++ b/packages/edit-post/src/components/manage-blocks-modal/checklist.js @@ -6,7 +6,6 @@ import { partial } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { BlockIcon } from '@wordpress/block-editor'; import { CheckboxControl } from '@wordpress/components'; @@ -20,10 +19,10 @@ function BlockTypesChecklist( { blockTypes, value, onItemChange } ) { > <CheckboxControl label={ ( - <Fragment> + <> { blockType.title } <BlockIcon icon={ blockType.icon } /> - </Fragment> + </> ) } checked={ value.includes( blockType.name ) } onChange={ partial( onItemChange, blockType.name ) } diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js index 3f108b59ecef8b..20f10bd32a1ac6 100644 --- a/packages/edit-post/src/components/meta-boxes/index.js +++ b/packages/edit-post/src/components/meta-boxes/index.js @@ -7,7 +7,6 @@ import { map } from 'lodash'; * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -17,12 +16,12 @@ import MetaBoxVisibility from './meta-box-visibility'; function MetaBoxes( { location, isVisible, metaBoxes } ) { return ( - <Fragment> + <> { map( metaBoxes, ( { id } ) => ( <MetaBoxVisibility key={ id } id={ id } /> ) ) } { isVisible && <MetaBoxesArea location={ location } /> } - </Fragment> + </> ); } diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index f02deeab87bcab..5303de703b30b9 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -3,7 +3,6 @@ */ import { IconButton, Panel } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withPluginContext } from '@wordpress/plugins'; import { compose } from '@wordpress/compose'; @@ -30,7 +29,7 @@ function PluginSidebar( props ) { } = props; return ( - <Fragment> + <> { isPinnable && ( <PinnedPlugins> { isPinned && <IconButton @@ -64,7 +63,7 @@ function PluginSidebar( props ) { { children } </Panel> </Sidebar> - </Fragment> + </> ); } diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 6b85cb8739a462..71f20775034ff5 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -6,7 +6,6 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { PanelBody, TextControl, ExternalLink } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -104,9 +103,9 @@ function PostLink( { target="_blank" > { isEditable ? - ( <Fragment> + ( <> { prefixElement }{ postNameElement }{ suffixElement } - </Fragment> ) : + </> ) : postLink } </ExternalLink> diff --git a/packages/edit-post/src/components/sidebar/post-schedule/index.js b/packages/edit-post/src/components/sidebar/post-schedule/index.js index 70ae683fa86b50..27dfcc64ba0ed3 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/index.js +++ b/packages/edit-post/src/components/sidebar/post-schedule/index.js @@ -5,7 +5,6 @@ import { __ } from '@wordpress/i18n'; import { PanelRow, Dropdown, Button } from '@wordpress/components'; import { withInstanceId } from '@wordpress/compose'; import { PostSchedule as PostScheduleForm, PostScheduleLabel, PostScheduleCheck } from '@wordpress/editor'; -import { Fragment } from '@wordpress/element'; export function PostSchedule( { instanceId } ) { return ( @@ -21,7 +20,7 @@ export function PostSchedule( { instanceId } ) { position="bottom left" contentClassName="edit-post-post-schedule__dialog" renderToggle={ ( { onToggle, isOpen } ) => ( - <Fragment> + <> <label className="edit-post-post-schedule__label" htmlFor={ `edit-post-post-schedule__toggle-${ instanceId }` } @@ -39,7 +38,7 @@ export function PostSchedule( { instanceId } ) { > <PostScheduleLabel /> </Button> - </Fragment> + </> ) } renderContent={ () => <PostScheduleForm /> } /> diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index ba8705cd19641a..729ad9e09ff913 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -29,7 +28,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <PanelBody className="edit-post-post-status" title={ __( 'Status & Visibility' ) } opened={ isOpened } onToggle={ onTogglePanel }> <PluginPostStatusInfo.Slot> { ( fills ) => ( - <Fragment> + <> <PostVisibility /> <PostSchedule /> <PostFormat /> @@ -38,7 +37,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <PostAuthor /> { fills } <PostTrash /> - </Fragment> + </> ) } </PluginPostStatusInfo.Slot> </PanelBody> diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c43856376a3890..81e8a0ba87eb03 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -5,7 +5,6 @@ import { Panel, PanelBody } from '@wordpress/components'; import { compose, ifCondition } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { BlockInspector } from '@wordpress/block-editor'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -30,7 +29,7 @@ const SettingsSidebar = ( { sidebarName } ) => ( <SettingsHeader sidebarName={ sidebarName } /> <Panel> { sidebarName === 'edit-post/document' && ( - <Fragment> + <> <PostStatus /> <LastRevision /> <PostLink /> @@ -40,7 +39,7 @@ const SettingsSidebar = ( { sidebarName } ) => ( <DiscussionPanel /> <PageAttributes /> <MetaBoxes location="side" /> - </Fragment> + </> ) } { sidebarName === 'edit-post/block' && ( <PanelBody className="edit-post-settings-sidebar__panel-block"> diff --git a/packages/edit-post/src/components/sidebar/sidebar-header/index.js b/packages/edit-post/src/components/sidebar/sidebar-header/index.js index 5c3a96a900f14f..375491985ef469 100644 --- a/packages/edit-post/src/components/sidebar/sidebar-header/index.js +++ b/packages/edit-post/src/components/sidebar/sidebar-header/index.js @@ -6,7 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { IconButton } from '@wordpress/components'; @@ -19,7 +18,7 @@ import shortcuts from '../../../keyboard-shortcuts'; const SidebarHeader = ( { children, className, closeLabel, closeSidebar, title } ) => { return ( - <Fragment> + <> <div className="components-panel__header edit-post-sidebar-header__small"> <span className="edit-post-sidebar-header__title"> { title || __( '(no title)' ) } @@ -39,7 +38,7 @@ const SidebarHeader = ( { children, className, closeLabel, closeSidebar, title } shortcut={ shortcuts.toggleSidebar } /> </div> - </Fragment> + </> ); }; diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index 47f09e6bd547e6..bae6f081189ee6 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { MenuItem } from '@wordpress/components'; -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { registerPlugin } from '@wordpress/plugins'; import { addQueryArgs } from '@wordpress/url'; @@ -18,10 +17,10 @@ import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; registerPlugin( 'edit-post', { render() { return ( - <Fragment> + <> <ToolsMoreMenuGroup> { ( { onClose } ) => ( - <Fragment> + <> <ManageBlocksMenuItem onSelect={ onClose } /> <MenuItem role="menuitem" @@ -31,10 +30,10 @@ registerPlugin( 'edit-post', { </MenuItem> <KeyboardShortcutsHelpMenuItem onSelect={ onClose } /> <CopyContentMenuItem /> - </Fragment> + </> ) } </ToolsMoreMenuGroup> - </Fragment> + </> ); }, } ); diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 1e8a2787509a25..31d3577eb793da 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { navigateRegions } from '@wordpress/components'; @@ -20,7 +19,7 @@ function Layout() { ]; return ( - <Fragment> + <> <Header /> <Sidebar /> <div @@ -35,7 +34,7 @@ function Layout() { </div> ) ) } </div> - </Fragment> + </> ); } diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index 6298496b87f479..a0e484befceae6 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; import { rawShortcut } from '@wordpress/keycodes'; @@ -33,7 +33,7 @@ class VisualEditorGlobalKeyboardShortcuts extends Component { render() { return ( - <Fragment> + <> <BlockEditorKeyboardShortcuts /> <KeyboardShortcuts shortcuts={ { @@ -42,7 +42,7 @@ class VisualEditorGlobalKeyboardShortcuts extends Component { } } /> <SaveShortcut /> - </Fragment> + </> ); } } diff --git a/packages/editor/src/components/post-publish-panel/postpublish.js b/packages/editor/src/components/post-publish-panel/postpublish.js index 6527e40560489f..1764e270e244fd 100644 --- a/packages/editor/src/components/post-publish-panel/postpublish.js +++ b/packages/editor/src/components/post-publish-panel/postpublish.js @@ -8,7 +8,7 @@ import { get } from 'lodash'; */ import { PanelBody, Button, ClipboardButton, TextControl } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { Component, Fragment, createRef } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { safeDecodeURIComponent } from '@wordpress/url'; @@ -61,7 +61,7 @@ class PostPublishPanelPostpublish extends Component { const viewPostLabel = get( postType, [ 'labels', 'view_item' ] ); const postPublishNonLinkHeader = isScheduled ? - <Fragment>{ __( 'is now scheduled. It will go live on' ) } <PostScheduleLabel />.</Fragment> : + <>{ __( 'is now scheduled. It will go live on' ) } <PostScheduleLabel />.</> : __( 'is now live.' ); return ( diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index 6006ec5ce5b46c..ba7967b479e021 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -7,7 +7,6 @@ import { get } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; @@ -44,7 +43,7 @@ function PostPublishPanelPrepublish( { <div><strong>{ prePublishTitle }</strong></div> <p>{ prePublishBodyText }</p> { hasPublishAction && ( - <Fragment> + <> <PanelBody initialOpen={ false } title={ [ __( 'Visibility:' ), <span className="editor-post-publish-panel__link" key="label"><PostVisibilityLabel /></span>, @@ -60,7 +59,7 @@ function PostPublishPanelPrepublish( { <MaybePostFormatPanel /> <MaybeTagsPanel /> { children } - </Fragment> + </> ) } </div> ); diff --git a/packages/editor/src/components/post-text-editor/index.js b/packages/editor/src/components/post-text-editor/index.js index 13926f4b79e857..667aaa76fcd700 100644 --- a/packages/editor/src/components/post-text-editor/index.js +++ b/packages/editor/src/components/post-text-editor/index.js @@ -7,7 +7,7 @@ import Textarea from 'react-autosize-textarea'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { parse } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; @@ -66,7 +66,7 @@ export class PostTextEditor extends Component { const { value } = this.state; const { instanceId } = this.props; return ( - <Fragment> + <> <label htmlFor={ `post-content-${ instanceId }` } className="screen-reader-text"> { __( 'Type text or HTML' ) } </label> @@ -80,7 +80,7 @@ export class PostTextEditor extends Component { id={ `post-content-${ instanceId }` } placeholder={ __( 'Start writing with text or HTML' ) } /> - </Fragment> + </> ); } } diff --git a/packages/editor/src/components/reusable-blocks-buttons/index.js b/packages/editor/src/components/reusable-blocks-buttons/index.js index d5a47844bb33ac..5ba661980b437c 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/index.js +++ b/packages/editor/src/components/reusable-blocks-buttons/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __experimentalBlockSettingsMenuPluginsExtension } from '@wordpress/block-editor'; import { withSelect } from '@wordpress/data'; @@ -15,7 +14,7 @@ function ReusableBlocksButtons( { clientIds } ) { return ( <__experimentalBlockSettingsMenuPluginsExtension> { ( { onClose } ) => ( - <Fragment> + <> <ReusableBlockConvertButton clientIds={ clientIds } onToggle={ onClose } @@ -26,7 +25,7 @@ function ReusableBlocksButtons( { clientIds } ) { onToggle={ onClose } /> ) } - </Fragment> + </> ) } </__experimentalBlockSettingsMenuPluginsExtension> ); diff --git a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js index b699d59aa75723..e9b9bbb81f003a 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js +++ b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js @@ -6,7 +6,6 @@ import { noop, every } from 'lodash'; /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { MenuItem } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { hasBlockSupport, isReusableBlock } from '@wordpress/blocks'; @@ -24,7 +23,7 @@ export function ReusableBlockConvertButton( { } return ( - <Fragment> + <> { ! isReusable && ( <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" @@ -43,7 +42,7 @@ export function ReusableBlockConvertButton( { { __( 'Convert to Regular Block' ) } </MenuItem> ) } - </Fragment> + </> ); } diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index 901c2a31556e05..7c956651af3199 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; @@ -13,7 +12,7 @@ import DocumentOutline from '../document-outline'; function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled, onRequestClose } ) { return ( - <Fragment> + <> <div className="table-of-contents__counts" role="note" @@ -44,15 +43,15 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, h </div> </div> { headingCount > 0 && ( - <Fragment> + <> <hr /> <span className="table-of-contents__title"> { __( 'Document Outline' ) } </span> <DocumentOutline onSelect={ onRequestClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> - </Fragment> + </> ) } - </Fragment> + </> ); } diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index d90bbea5d72412..a97cb170cca2c2 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const bold = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="primary" character="b" @@ -37,7 +36,7 @@ export const bold = { inputType="formatBold" onInput={ onToggle } /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index c7d0ce3910fe69..aaccbde3946f49 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextShortcut, RichTextToolbarButton } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const code = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="access" character="x" @@ -32,7 +31,7 @@ export const code = { shortcutType="access" shortcutCharacter="x" /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index eee6cd14a649c9..25c586a58dbc5a 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const italic = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="primary" character="i" @@ -37,7 +36,7 @@ export const italic = { inputType="formatItalic" onInput={ onToggle } /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index d04d58312c15c3..913285f2e8b0f3 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { withSpokenMessages } from '@wordpress/components'; import { getTextContent, @@ -70,7 +70,7 @@ export const link = { const { isActive, activeAttributes, value, onChange } = this.props; return ( - <Fragment> + <> <RichTextShortcut type="access" character="a" @@ -117,7 +117,7 @@ export const link = { value={ value } onChange={ onChange } /> - </Fragment> + </> ); } } ), diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js index 99713b911a62f4..8457682d07d285 100644 --- a/packages/format-library/src/link/index.native.js +++ b/packages/format-library/src/link/index.native.js @@ -7,7 +7,7 @@ import { find } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { withSpokenMessages } from '@wordpress/components'; import { RichTextToolbarButton } from '@wordpress/block-editor'; import { @@ -106,7 +106,7 @@ export const link = { const linkSelection = this.getLinkSelection(); return ( - <Fragment> + <> <ModalLinkUI isVisible={ this.state.addingLink } isActive={ isActive } @@ -125,7 +125,7 @@ export const link = { shortcutType="primary" shortcutCharacter="k" /> - </Fragment> + </> ); } } ), diff --git a/packages/format-library/src/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js index cbaf8aa51a0a07..244b2787fb4d04 100644 --- a/packages/format-library/src/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; @@ -18,7 +17,7 @@ export const strikethrough = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <Fragment> + <> <RichTextShortcut type="access" character="d" @@ -32,7 +31,7 @@ export const strikethrough = { shortcutType="access" shortcutCharacter="d" /> - </Fragment> + </> ); }, }; diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js index 9be88724548c32..d83cc2e3c484c2 100644 --- a/packages/format-library/src/underline/index.js +++ b/packages/format-library/src/underline/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; import { toggleFormat } from '@wordpress/rich-text'; import { RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; @@ -28,7 +27,7 @@ export const underline = { }; return ( - <Fragment> + <> <RichTextShortcut type="primary" character="u" @@ -38,7 +37,7 @@ export const underline = { inputType="formatUnderline" onInput={ onToggle } /> - </Fragment> + </> ); }, }; diff --git a/packages/plugins/README.md b/packages/plugins/README.md index e3b7eb00cd0e9e..2875c76b0fa8da 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -116,12 +116,11 @@ registerPlugin( 'plugin-name', { ```js // Using ESNext syntax -const { Fragment } = wp.element; const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; const { registerPlugin } = wp.plugins; const Component = () => ( - <Fragment> + <> <PluginSidebarMoreMenuItem target="sidebar-name" > @@ -133,7 +132,7 @@ const Component = () => ( > Content of the sidebar </PluginSidebar> - </Fragment> + </> ); registerPlugin( 'plugin-name', { diff --git a/packages/plugins/src/api/index.js b/packages/plugins/src/api/index.js index afcd733db880e7..6e6ea102a80ffd 100644 --- a/packages/plugins/src/api/index.js +++ b/packages/plugins/src/api/index.js @@ -65,12 +65,11 @@ const plugins = {}; * @example <caption>ESNext</caption> * ```js * // Using ESNext syntax - * const { Fragment } = wp.element; * const { PluginSidebar, PluginSidebarMoreMenuItem } = wp.editPost; * const { registerPlugin } = wp.plugins; * * const Component = () => ( - * <Fragment> + * <> * <PluginSidebarMoreMenuItem * target="sidebar-name" * > @@ -82,7 +81,7 @@ const plugins = {}; * > * Content of the sidebar * </PluginSidebar> - * </Fragment> + * </> * ); * * registerPlugin( 'plugin-name', { From ea1878098fa4862d569919d9a4f2d53f76e3ceb5 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 7 May 2019 08:27:06 +0100 Subject: [PATCH 044/664] Fix: Header text scale is inconsistent between classic block and heading block. (#15393) The classic blocks styles specified styles for the headings, this made the text scale inconsistent with the styles used for header blocks that normally use theme styles. This commit removes the style changes applied to headings by the classic block. --- .../block-library/src/classic/editor.scss | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 306661c4e92ae5..4313096afe14d8 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -24,30 +24,6 @@ color: $dark-gray-800; } - h1 { - font-size: 2em; - } - - h2 { - font-size: 1.6em; - } - - h3 { - font-size: 1.4em; - } - - h4 { - font-size: 1.2em; - } - - h5 { - font-size: 1.1em; - } - - h6 { - font-size: 1em; - } - > *:first-child { margin-top: 0; } From 9ad5b935a05a41f8ea78307d8ebcbdfb259a8269 Mon Sep 17 00:00:00 2001 From: Jon Surrell <jon.surrell@automattic.com> Date: Tue, 7 May 2019 10:56:40 +0200 Subject: [PATCH 045/664] Webpack dependency plugin: Document unsupported multiple instances (#15451) * Add note about multiple instances to README * Update webpack plugin name in docs to match package * Remove duplicate line with jQuery --- .../README.md | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/packages/dependency-extraction-webpack-plugin/README.md b/packages/dependency-extraction-webpack-plugin/README.md index 34354a2cc9ceec..6b317a5d983a41 100644 --- a/packages/dependency-extraction-webpack-plugin/README.md +++ b/packages/dependency-extraction-webpack-plugin/README.md @@ -27,16 +27,38 @@ Use this plugin as you would other webpack plugins: ```js // webpack.config.js -const WordPressExternalDependenciesPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); +const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); module.exports = { // …snip plugins: [ - new WordPressExternalDependenciesPlugin(), + new DependencyExtractionWebpackPlugin(), ] } ``` +**Note:** Multiple instances of the plugin are not supported and may produced unexpected results. If +you plan to extend the webpack configuration from `@wordpress/scripts` with your own `DependencyExtractionWebpackPlugin`, be sure to +remove the default instance of the plugin: + +```js +const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); +const config = { + ...defaultConfig, + plugins: [ + ...defaultConfig.plugins.filter( + plugin => plugin.constructor.name !== 'DependencyExtractionWebpackPlugin', + ), + new DependencyExtractionWebpackPlugin( { + injectPolyfill: true, + requestToExternal(request) { + /* My externals */ + }, + } ), + ], +}; +``` + Each entrypoint in the webpack bundle will include JSON file that declares the WordPress script dependencies that should be enqueued. For example: @@ -59,7 +81,6 @@ By default, the following module requests are handled: | `@babel/runtime/regenerator` | `regeneratorRuntime` | `wp-polyfill` | | `@wordpress/*` | `wp['*']` | `wp-*` | | `jquery` | `jQuery` | `jquery` | -| `jquery` | `jQuery` | `jquery` | | `lodash-es` | `lodash` | `lodash` | | `lodash` | `lodash` | `lodash` | | `moment` | `moment` | `moment` | @@ -83,7 +104,7 @@ An object can be passed to the constructor to customize the behavior, for exampl ```js module.exports = { plugins: [ - new WordPressExternalDependenciesPlugin( { injectPolyfill: true } ), + new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), ] } ``` @@ -134,7 +155,7 @@ function requestToExternal( request ) { module.exports = { plugins: [ - new WordPressExternalDependenciesPlugin( { requestToExternal } ), + new DependencyExtractionWebpackPlugin( { requestToExternal } ), ] } ``` @@ -162,14 +183,14 @@ function requestToHandle( request ) { // Handle imports like `import myModule from 'my-module'` if ( request === 'my-module' ) { - // Expect to find `my-module` as myModule in the global scope: + // `my-module` depends on the script with the 'my-module-script-handle' handle. return 'my-module-script-handle'; } } module.exports = { plugins: [ - new WordPressExternalDependenciesPlugin( { requestToExternal } ), + new DependencyExtractionWebpackPlugin( { requestToExternal } ), ] } ``` From 31e21443b63d23d585bdd1e5a827963bdb94b71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Tue, 7 May 2019 11:28:46 +0200 Subject: [PATCH 046/664] Testing: Add integration tests for blocks with deprecations (#15268) * Testing: Add integration tests for blocks with deprecations * Testing: Add integration test covering deprecated version of Image block * Add additional test covering deprecated Pullqote block * Tests: Add missing integration tests for Gallery deprecations * Tests: Add missing integration tests for Image deprecations --- packages/block-library/src/gallery/index.js | 123 ++++++++++++------ .../blocks/core__gallery__deprecated-1.html | 10 ++ .../blocks/core__gallery__deprecated-1.json | 25 ++++ .../core__gallery__deprecated-1.parsed.json | 22 ++++ ...ore__gallery__deprecated-1.serialized.html | 3 + .../blocks/core__gallery__deprecated-2.html | 14 ++ .../blocks/core__gallery__deprecated-2.json | 29 +++++ .../core__gallery__deprecated-2.parsed.json | 22 ++++ ...ore__gallery__deprecated-2.serialized.html | 3 + .../blocks/core__image__deprecated-1.html | 5 + .../blocks/core__image__deprecated-1.json | 16 +++ .../core__image__deprecated-1.parsed.json | 22 ++++ .../core__image__deprecated-1.serialized.html | 3 + .../blocks/core__image__deprecated-2.html | 5 + .../blocks/core__image__deprecated-2.json | 18 +++ .../core__image__deprecated-2.parsed.json | 24 ++++ .../core__image__deprecated-2.serialized.html | 3 + .../blocks/core__image__deprecated-3.html | 5 + .../blocks/core__image__deprecated-3.json | 18 +++ .../core__image__deprecated-3.parsed.json | 24 ++++ .../core__image__deprecated-3.serialized.html | 3 + .../fixtures/blocks/core__latest-posts.json | 3 + .../core__latest-posts__displayPostDate.json | 3 + .../blocks/core__pullquote__deprecated-1.html | 6 + .../blocks/core__pullquote__deprecated-1.json | 14 ++ .../core__pullquote__deprecated-1.parsed.json | 20 +++ ...e__pullquote__deprecated-1.serialized.html | 3 + .../blocks/core__pullquote__deprecated-2.html | 6 + .../blocks/core__pullquote__deprecated-2.json | 13 ++ .../core__pullquote__deprecated-2.parsed.json | 20 +++ ...e__pullquote__deprecated-2.serialized.html | 3 + .../blocks/core__quote__deprecated-1.html | 6 + .../blocks/core__quote__deprecated-1.json | 14 ++ .../core__quote__deprecated-1.parsed.json | 20 +++ .../core__quote__deprecated-1.serialized.html | 3 + .../full-content/server-registered.json | 2 +- 36 files changed, 492 insertions(+), 41 deletions(-) create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index d6ea28fe051c36..ba5e06f0df7ebc 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import classnames from 'classnames'; import { map, some } from 'lodash'; /** @@ -19,7 +20,7 @@ import save from './save'; import transforms from './transforms'; import { defaultColumnsNumber } from './shared'; -const { name, attributes: blockAttributes } = metadata; +const { name } = metadata; export { metadata, name }; @@ -36,7 +37,53 @@ export const settings = { save, deprecated: [ { - attributes: blockAttributes, + attributes: { + images: { + type: 'array', + default: [], + source: 'query', + selector: 'ul.wp-block-gallery .blocks-gallery-item', + query: { + url: { + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + alt: { + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + }, + id: { + source: 'attribute', + selector: 'img', + attribute: 'data-id', + }, + link: { + source: 'attribute', + selector: 'img', + attribute: 'data-link', + }, + caption: { + type: 'array', + source: 'children', + selector: 'figcaption', + }, + }, + }, + columns: { + type: 'number', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + type: 'string', + default: 'none', + }, + }, isEligible( { images, ids } ) { return images && images.length > 0 && @@ -95,47 +142,39 @@ export const settings = { ); }, }, - { - attributes: blockAttributes, - save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; - return ( - <ul className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > - { images.map( ( image ) => { - let href; - - switch ( linkTo ) { - case 'media': - href = image.url; - break; - case 'attachment': - href = image.link; - break; - } - - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } data-link={ image.link } />; - - return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> - <figure> - { href ? <a href={ href }>{ img }</a> : img } - { image.caption && image.caption.length > 0 && ( - <RichText.Content tagName="figcaption" value={ image.caption } /> - ) } - </figure> - </li> - ); - } ) } - </ul> - ); - }, - }, { attributes: { - ...blockAttributes, images: { - ...blockAttributes.images, + type: 'array', + default: [], + source: 'query', selector: 'div.wp-block-gallery figure.blocks-gallery-image img', + query: { + url: { + source: 'attribute', + attribute: 'src', + }, + alt: { + source: 'attribute', + attribute: 'alt', + default: '', + }, + id: { + source: 'attribute', + attribute: 'data-id', + }, + }, + }, + columns: { + type: 'number', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + type: 'string', + default: 'none', }, align: { type: 'string', @@ -145,8 +184,12 @@ export const settings = { save( { attributes } ) { const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes; + const className = classnames( `columns-${ columns }`, { + alignnone: align === 'none', + 'is-cropped': imageCrop, + } ); return ( - <div className={ `align${ align } columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > + <div className={ className } > { images.map( ( image ) => { let href; diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html new file mode 100644 index 00000000000000..f73ee633132cc6 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.html @@ -0,0 +1,10 @@ +<!-- wp:core/gallery {"columns":2} --> +<div class="wp-block-gallery alignnone columns-2 is-cropped"> + <figure class="blocks-gallery-image"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="title" /> + </figure> + <figure class="blocks-gallery-image"> + <img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" alt="title" /> + </figure> +</div> +<!-- /wp:core/gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json new file mode 100644 index 00000000000000..92f3e7f2bd7a33 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.json @@ -0,0 +1,25 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/gallery", + "isValid": true, + "attributes": { + "images": [ + { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "title" + }, + { + "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + "alt": "title" + } + ], + "columns": 2, + "imageCrop": true, + "linkTo": "none", + "align": "none" + }, + "innerBlocks": [], + "originalContent": "<div class=\"wp-block-gallery alignnone columns-2 is-cropped\">\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"title\" />\n\t</figure>\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" alt=\"title\" />\n\t</figure>\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json new file mode 100644 index 00000000000000..08adc8d3871127 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/gallery", + "attrs": { + "columns": 2 + }, + "innerBlocks": [], + "innerHTML": "\n<div class=\"wp-block-gallery alignnone columns-2 is-cropped\">\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"title\" />\n\t</figure>\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" alt=\"title\" />\n\t</figure>\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-gallery alignnone columns-2 is-cropped\">\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"title\" />\n\t</figure>\n\t<figure class=\"blocks-gallery-image\">\n\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" alt=\"title\" />\n\t</figure>\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html new file mode 100644 index 00000000000000..eb7bc9482b7f29 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:gallery {"columns":2,"align":"none"} --> +<ul class="wp-block-gallery columns-2 is-cropped"><li class="blocks-gallery-item"><figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="title"/></figure></li><li class="blocks-gallery-item"><figure><img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" alt="title"/></figure></li></ul> +<!-- /wp:gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html new file mode 100644 index 00000000000000..fec3435d8876e8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.html @@ -0,0 +1,14 @@ +<!-- wp:core/gallery {"columns":2} --> +<ul class="wp-block-gallery columns-2 is-cropped"> + <li class="blocks-gallery-item"> + <figure> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" data-id="1" alt="title" class="wp-image-1" /> + </figure> + </li> + <li class="blocks-gallery-item"> + <figure> + <img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" data-id="2" alt="title" class="wp-image-2" /> + </figure> + </li> +</ul> +<!-- /wp:core/gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json new file mode 100644 index 00000000000000..3c0eacb2de59fb --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.json @@ -0,0 +1,29 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/gallery", + "isValid": true, + "attributes": { + "images": [ + { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "title", + "id": "1", + "caption": "" + }, + { + "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + "alt": "title", + "id": "2", + "caption": "" + } + ], + "ids": [], + "columns": 2, + "imageCrop": true, + "linkTo": "none" + }, + "innerBlocks": [], + "originalContent": "<ul class=\"wp-block-gallery columns-2 is-cropped\">\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" data-id=\"1\" alt=\"title\" class=\"wp-image-1\" />\n\t\t</figure>\n\t</li>\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" data-id=\"2\" alt=\"title\" class=\"wp-image-2\" />\n\t\t</figure>\n\t</li>\n</ul>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json new file mode 100644 index 00000000000000..acaac1c9499e57 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/gallery", + "attrs": { + "columns": 2 + }, + "innerBlocks": [], + "innerHTML": "\n<ul class=\"wp-block-gallery columns-2 is-cropped\">\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" data-id=\"1\" alt=\"title\" class=\"wp-image-1\" />\n\t\t</figure>\n\t</li>\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" data-id=\"2\" alt=\"title\" class=\"wp-image-2\" />\n\t\t</figure>\n\t</li>\n</ul>\n", + "innerContent": [ + "\n<ul class=\"wp-block-gallery columns-2 is-cropped\">\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" data-id=\"1\" alt=\"title\" class=\"wp-image-1\" />\n\t\t</figure>\n\t</li>\n\t<li class=\"blocks-gallery-item\">\n\t\t<figure>\n\t\t\t<img src=\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\" data-id=\"2\" alt=\"title\" class=\"wp-image-2\" />\n\t\t</figure>\n\t</li>\n</ul>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html new file mode 100644 index 00000000000000..8951d81b0c4e80 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__gallery__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:gallery {"columns":2} --> +<ul class="wp-block-gallery columns-2 is-cropped"><li class="blocks-gallery-item"><figure><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="title" data-id="1" class="wp-image-1"/></figure></li><li class="blocks-gallery-item"><figure><img src="data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=" alt="title" data-id="2" class="wp-image-2"/></figure></li></ul> +<!-- /wp:gallery --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html new file mode 100644 index 00000000000000..bc30fe27271aec --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.html @@ -0,0 +1,5 @@ +<!-- wp:core/image {"align":"left"} --> +<figure class="wp-block-image alignleft" style="max-width:50%"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" /> +</figure> +<!-- /wp:core/image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json new file mode 100644 index 00000000000000..f02d8ba36cc66c --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.json @@ -0,0 +1,16 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "linkDestination": "none" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-image alignleft\" style=\"max-width:50%\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" />\n</figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json new file mode 100644 index 00000000000000..1bb1a6ba234c46 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left" + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-image alignleft\" style=\"max-width:50%\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" />\n</figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-image alignleft\" style=\"max-width:50%\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" />\n</figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html new file mode 100644 index 00000000000000..3b55fa1c3de09a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:image {"align":"left"} --> +<div class="wp-block-image"><figure class="alignleft"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt=""/></figure></div> +<!-- /wp:image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html new file mode 100644 index 00000000000000..5b0daa5006d2b8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.html @@ -0,0 +1,5 @@ +<!-- wp:core/image {"align":"left","height":100,"width":100} --> +<figure class="wp-block-image alignleft"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100" /> +</figure> +<!-- /wp:core/image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json new file mode 100644 index 00000000000000..a9d3fe689b461a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.json @@ -0,0 +1,18 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "width": 100, + "height": 100, + "linkDestination": "none" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-image alignleft\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json new file mode 100644 index 00000000000000..02c54b013d378a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.parsed.json @@ -0,0 +1,24 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left", + "height": 100, + "width": 100 + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-image alignleft\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-image alignleft\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html new file mode 100644 index 00000000000000..723951075ee44d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:image {"align":"left","width":100,"height":100} --> +<div class="wp-block-image"><figure class="alignleft is-resized"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100"/></figure></div> +<!-- /wp:image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html new file mode 100644 index 00000000000000..429c8b3150212d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.html @@ -0,0 +1,5 @@ +<!-- wp:core/image {"align":"left","height":100,"width":100} --> +<figure class="wp-block-image alignleft is-resized"> + <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100" /> +</figure> +<!-- /wp:core/image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json new file mode 100644 index 00000000000000..68d63a7a1ee0f0 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.json @@ -0,0 +1,18 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "width": 100, + "height": 100, + "linkDestination": "none" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-image alignleft is-resized\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json new file mode 100644 index 00000000000000..00094831d08522 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.parsed.json @@ -0,0 +1,24 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left", + "height": 100, + "width": 100 + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-image alignleft is-resized\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-image alignleft is-resized\">\n\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\" alt=\"\" width=\"100\" height=\"100\" />\n</figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html new file mode 100644 index 00000000000000..723951075ee44d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__image__deprecated-3.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:image {"align":"left","width":100,"height":100} --> +<div class="wp-block-image"><figure class="alignleft is-resized"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" alt="" width="100" height="100"/></figure></div> +<!-- /wp:image --> diff --git a/packages/e2e-tests/fixtures/blocks/core__latest-posts.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json index f31c903f58c274..dcad7e999b3f43 100644 --- a/packages/e2e-tests/fixtures/blocks/core__latest-posts.json +++ b/packages/e2e-tests/fixtures/blocks/core__latest-posts.json @@ -5,6 +5,9 @@ "isValid": true, "attributes": { "postsToShow": 5, + "displayPostContent": false, + "displayPostContentRadio": "excerpt", + "excerptLength": 55, "displayPostDate": false, "postLayout": "list", "columns": 3, diff --git a/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json index 024e1a7f37b8a7..ebb5f884dfb01d 100644 --- a/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json +++ b/packages/e2e-tests/fixtures/blocks/core__latest-posts__displayPostDate.json @@ -5,6 +5,9 @@ "isValid": true, "attributes": { "postsToShow": 5, + "displayPostContent": false, + "displayPostContentRadio": "excerpt", + "excerptLength": 55, "displayPostDate": true, "postLayout": "list", "columns": 3, diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html new file mode 100644 index 00000000000000..290e9289ebf2d9 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.html @@ -0,0 +1,6 @@ +<!-- wp:core/pullquote --> +<blockquote class="wp-block-pullquote alignnone"> + <p>Testing deprecated pullquote block...</p> + <footer>...with a caption</footer> +</blockquote> +<!-- /wp:core/pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json new file mode 100644 index 00000000000000..0843d9a619195b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/pullquote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated pullquote block...</p>", + "citation": "...with a caption", + "align": "none" + }, + "innerBlocks": [], + "originalContent": "<blockquote class=\"wp-block-pullquote alignnone\">\n <p>Testing deprecated pullquote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json new file mode 100644 index 00000000000000..de13c8201f5046 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/pullquote", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<blockquote class=\"wp-block-pullquote alignnone\">\n <p>Testing deprecated pullquote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote class=\"wp-block-pullquote alignnone\">\n <p>Testing deprecated pullquote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html new file mode 100644 index 00000000000000..b3010f99233ae0 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote {"align":"none"} --> +<figure class="wp-block-pullquote"><blockquote><p>Testing deprecated pullquote block...</p><cite>...with a caption</cite></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html new file mode 100644 index 00000000000000..180c5f054120be --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.html @@ -0,0 +1,6 @@ +<!-- wp:core/pullquote --> +<blockquote class="wp-block-pullquote"> + <p>Testing deprecated pullquote block...</p> + <cite>...with a caption</cite> +</blockquote> +<!-- /wp:core/pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json new file mode 100644 index 00000000000000..bd60dcfad3ed36 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.json @@ -0,0 +1,13 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/pullquote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated pullquote block...</p>", + "citation": "...with a caption" + }, + "innerBlocks": [], + "originalContent": "<blockquote class=\"wp-block-pullquote\">\n <p>Testing deprecated pullquote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json new file mode 100644 index 00000000000000..643d7acc5155d0 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/pullquote", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<blockquote class=\"wp-block-pullquote\">\n <p>Testing deprecated pullquote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote class=\"wp-block-pullquote\">\n <p>Testing deprecated pullquote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html new file mode 100644 index 00000000000000..d329fc6153b4de --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote --> +<figure class="wp-block-pullquote"><blockquote><p>Testing deprecated pullquote block...</p><cite>...with a caption</cite></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html new file mode 100644 index 00000000000000..ade24a270ceaf5 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.html @@ -0,0 +1,6 @@ +<!-- wp:core/quote --> +<blockquote class="wp-block-quote blocks-quote-style-1"> + <p>Testing deprecated quote block...</p> + <footer>...with a caption</footer> +</blockquote> +<!-- /wp:core/quote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json new file mode 100644 index 00000000000000..aa920966ae06ba --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/quote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated quote block...</p>", + "citation": "...with a caption", + "style": 1 + }, + "innerBlocks": [], + "originalContent": "<blockquote class=\"wp-block-quote blocks-quote-style-1\">\n\t<p>Testing deprecated quote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json new file mode 100644 index 00000000000000..107d6a15672137 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/quote", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<blockquote class=\"wp-block-quote blocks-quote-style-1\">\n\t<p>Testing deprecated quote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote class=\"wp-block-quote blocks-quote-style-1\">\n\t<p>Testing deprecated quote block...</p>\n\t<footer>...with a caption</footer>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html new file mode 100644 index 00000000000000..d865a5f09d9011 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p>Testing deprecated quote block...</p><cite>...with a caption</cite></blockquote> +<!-- /wp:quote --> diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index 9ca842b541fda0..49912d76c49605 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"className":{"type":"string"},"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file +{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostContent":{"type":"boolean","default":false},"displayPostContentRadio":{"type":"string","default":"excerpt"},"excerptLength":{"type":"number","default":55},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"className":{"type":"string"},"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file From 7f9d858540b39c316d4d7f2c853dd6fbb1603e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Tue, 7 May 2019 11:29:35 +0200 Subject: [PATCH 047/664] Scripts: Allow non-production env in wp-scripts build (#15480) --- packages/scripts/scripts/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 567a2405f2f1da..2c3b9318afd4d5 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -9,7 +9,7 @@ const { sync: resolveBin } = require( 'resolve-bin' ); */ const { getWebpackArgs } = require( '../utils' ); -process.env.NODE_ENV = 'production'; +process.env.NODE_ENV = process.env.NODE_ENV || 'production'; const { status } = spawn( resolveBin( 'webpack' ), getWebpackArgs(), From b66bbccf26ba46ea67e55d44b96349899e935a87 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 7 May 2019 12:21:34 +0100 Subject: [PATCH 048/664] Update the block component to use react hooks instead (#14985) --- .../src/components/block-list/block.js | 907 +++++++++--------- packages/eslint-plugin/configs/react.js | 1 - 2 files changed, 439 insertions(+), 469 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 5e12d4ef18d457..87b3bccdc2bed9 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -7,7 +7,7 @@ import { get, reduce, size, first, last } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useRef, useEffect, useState } from '@wordpress/element'; import { focus, isTextField, @@ -48,100 +48,169 @@ import Inserter from '../inserter'; import HoverArea from './hover-area'; import { isInsideRootBlock } from '../../utils/dom'; -export class BlockListBlock extends Component { - constructor() { - super( ...arguments ); - - this.setBlockListRef = this.setBlockListRef.bind( this ); - this.bindBlockNode = this.bindBlockNode.bind( this ); - this.setAttributes = this.setAttributes.bind( this ); - this.maybeHover = this.maybeHover.bind( this ); - this.forceFocusedContextualToolbar = this.forceFocusedContextualToolbar.bind( this ); - this.hideHoverEffects = this.hideHoverEffects.bind( this ); - this.onFocus = this.onFocus.bind( this ); - this.preventDrag = this.preventDrag.bind( this ); - this.onPointerDown = this.onPointerDown.bind( this ); - this.deleteOrInsertAfterWrapper = this.deleteOrInsertAfterWrapper.bind( this ); - this.onBlockError = this.onBlockError.bind( this ); - this.onTouchStart = this.onTouchStart.bind( this ); - this.onClick = this.onClick.bind( this ); - this.onDragStart = this.onDragStart.bind( this ); - this.onDragEnd = this.onDragEnd.bind( this ); - this.selectOnOpen = this.selectOnOpen.bind( this ); - this.hadTouchStart = false; - - this.state = { - error: null, - dragging: false, - isHovered: false, - }; - this.isForcingContextualToolbar = false; - } +/** + * Prevents default dragging behavior within a block to allow for multi- + * selection to take effect unhampered. + * + * @param {DragEvent} event Drag event. + */ +const preventDrag = ( event ) => { + event.preventDefault(); +}; + +function BlockListBlock( { + blockRef, + mode, + isFocusMode, + hasFixedToolbar, + isLocked, + clientId, + rootClientId, + isSelected, + isPartOfMultiSelection, + isFirstMultiSelected, + isTypingWithinBlock, + isCaretWithinFormattedText, + isEmptyDefaultBlock, + isMovable, + isParentOfSelectedBlock, + isDraggable, + isSelectionEnabled, + className, + name, + isValid, + attributes, + initialPosition, + wrapperProps, + setAttributes, + onReplace, + onInsertBlocksAfter, + onMerge, + onSelect, + onRemove, + onInsertDefaultBlockAfter, + toggleSelection, + onShiftSelection, + onSelectionStart, +} ) { + // Random state used to rerender the component if needed, ideally we don't need this + const [ , updateRerenderState ] = useState( {} ); + const rerender = () => updateRerenderState( {} ); + + // Reference of the wrapper + const wrapper = useRef( null ); + useEffect( () => { + blockRef( wrapper.current, clientId ); + // We need to rerender to trigger a rerendering of HoverArea. + rerender(); + }, [] ); + + // Reference to the block edit node + const blockNodeRef = useRef(); + + // Keep track of touchstart to disable hover on iOS + const hadTouchStart = useRef( false ); + const onTouchStart = () => { + hadTouchStart.current = true; + }; + const onTouchStop = () => { + // Clear touchstart detection + // Browser will try to emulate mouse events also see https://www.html5rocks.com/en/mobile/touchandmouse/ + hadTouchStart.current = false; + }; - componentDidMount() { - if ( this.props.isSelected ) { - this.focusTabbable(); - } - } + // Handling isHovered + const [ isBlockHovered, setBlockHoveredState ] = useState( false ); - componentDidUpdate( prevProps ) { - if ( this.isForcingContextualToolbar ) { - // The forcing of contextual toolbar should only be true during one update, - // after the first update normal conditions should apply. - this.isForcingContextualToolbar = false; + /** + * Sets the block state as unhovered if currently hovering. There are cases + * where mouseleave may occur but the block is not hovered (multi-select), + * so to avoid unnecesary renders, the state is only set if hovered. + */ + const hideHoverEffects = () => { + if ( isBlockHovered ) { + setBlockHoveredState( false ); } - if ( this.props.isTypingWithinBlock || this.props.isSelected ) { - this.hideHoverEffects(); + }; + /** + * A mouseover event handler to apply hover effect when a pointer device is + * placed within the bounds of the block. The mouseover event is preferred + * over mouseenter because it may be the case that a previous mouseenter + * event was blocked from being handled by a IgnoreNestedEvents component, + * therefore transitioning out of a nested block to the bounds of the block + * would otherwise not trigger a hover effect. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter + */ + const maybeHover = () => { + if ( + isBlockHovered || + isPartOfMultiSelection || + isSelected || + hadTouchStart.current + ) { + return; } + setBlockHoveredState( true ); + }; - if ( this.props.isSelected && ! prevProps.isSelected ) { - this.focusTabbable( true ); + // Set hover to false once we start typing or select the block. + useEffect( () => { + if ( isTypingWithinBlock || isSelected ) { + hideHoverEffects(); } + } ); - // When triggering a multi-selection, move the focus to the wrapper of the first selected block. - // This ensures that it is not possible to continue editing the initially selected block - // when a multi-selection is triggered. - if ( this.props.isFirstMultiSelected && ! prevProps.isFirstMultiSelected ) { - this.wrapperNode.focus(); - } - } + // Handling the dragging state + const [ isDragging, setBlockDraggingState ] = useState( false ); + const onDragStart = () => { + setBlockDraggingState( true ); + }; + const onDragEnd = () => { + setBlockDraggingState( false ); + }; - setBlockListRef( node ) { - this.wrapperNode = node; - this.props.blockRef( node, this.props.clientId ); + // Handling the error state + const [ hasError, setErrorState ] = useState( false ); + const onBlockError = () => setErrorState( false ); - // We need to rerender to trigger a rerendering of HoverArea - // it depents on this.wrapperNode but we can't keep this.wrapperNode in state - // Because we need it to be immediately availeble for `focusableTabbable` to work. - this.forceUpdate(); - } + // Handling of forceContextualToolbarFocus + const isForcingContextualToolbar = useRef( false ); + useEffect( () => { + if ( isForcingContextualToolbar.current ) { + // The forcing of contextual toolbar should only be true during one update, + // after the first update normal conditions should apply. + isForcingContextualToolbar.current = false; + } + } ); + const forceFocusedContextualToolbar = () => { + isForcingContextualToolbar.current = true; + // trigger a re-render + rerender(); + }; - bindBlockNode( node ) { - this.node = node; - } + // Handing the focus of the block on creation and update /** * When a block becomes selected, transition focus to an inner tabbable. * * @param {boolean} ignoreInnerBlocks Should not focus inner blocks. */ - focusTabbable( ignoreInnerBlocks ) { - const { initialPosition } = this.props; - + const focusTabbable = ( ignoreInnerBlocks ) => { // Focus is captured by the wrapper node, so while focus transition // should only consider tabbables within editable display, since it // may be the wrapper itself or a side control which triggered the // focus event, don't unnecessary transition to an inner tabbable. - if ( this.wrapperNode.contains( document.activeElement ) ) { + if ( wrapper.current.contains( document.activeElement ) ) { return; } // Find all tabbables within node. const textInputs = focus.tabbable - .find( this.node ) + .find( blockNodeRef.current ) .filter( isTextField ) // Exclude inner blocks - .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( this.node, node ) ); + .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( blockNodeRef.current, node ) ); // If reversed (e.g. merge via backspace), use the last in the set of // tabbables. @@ -149,140 +218,41 @@ export class BlockListBlock extends Component { const target = ( isReverse ? last : first )( textInputs ); if ( ! target ) { - this.wrapperNode.focus(); + wrapper.current.focus(); return; } placeCaretAtHorizontalEdge( target, isReverse ); - } - - setAttributes( attributes ) { - const { clientId, name, onChange } = this.props; - const type = getBlockType( name ); - onChange( clientId, attributes ); - - const metaAttributes = reduce( - attributes, - ( result, value, key ) => { - if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { - result[ type.attributes[ key ].meta ] = value; - } - - return result; - }, - {} - ); + }; - if ( size( metaAttributes ) ) { - this.props.onMetaChange( metaAttributes ); + // Focus the selected block's wrapper or inner input on mount and update + const isMounting = useRef( true ); + useEffect( () => { + if ( isSelected ) { + focusTabbable( ! isMounting.current ); } - } - - onTouchStart() { - // Detect touchstart to disable hover on iOS - this.hadTouchStart = true; - } - - onClick() { - // Clear touchstart detection - // Browser will try to emulate mouse events also see https://www.html5rocks.com/en/mobile/touchandmouse/ - this.hadTouchStart = false; - } - - /** - * A mouseover event handler to apply hover effect when a pointer device is - * placed within the bounds of the block. The mouseover event is preferred - * over mouseenter because it may be the case that a previous mouseenter - * event was blocked from being handled by a IgnoreNestedEvents component, - * therefore transitioning out of a nested block to the bounds of the block - * would otherwise not trigger a hover effect. - * - * @see https://developer.mozilla.org/en-US/docs/Web/Events/mouseenter - */ - maybeHover() { - const { isPartOfMultiSelection, isSelected } = this.props; - const { isHovered } = this.state; + isMounting.current = false; + }, [ isSelected ] ); - if ( - isHovered || - isPartOfMultiSelection || - isSelected || - this.hadTouchStart - ) { - return; + // Focus the first multi selected block + useEffect( () => { + if ( isFirstMultiSelected ) { + wrapper.current.focus(); } + }, [ isFirstMultiSelected ] ); - this.setState( { isHovered: true } ); - } - - /** - * Sets the block state as unhovered if currently hovering. There are cases - * where mouseleave may occur but the block is not hovered (multi-select), - * so to avoid unnecesary renders, the state is only set if hovered. - */ - hideHoverEffects() { - if ( this.state.isHovered ) { - this.setState( { isHovered: false } ); - } - } + // Other event handlers /** * Marks the block as selected when focused and not already selected. This * specifically handles the case where block does not set focus on its own * (via `setFocus`), typically if there is no focusable input in the block. - * - * @return {void} - */ - onFocus() { - if ( ! this.props.isSelected && ! this.props.isPartOfMultiSelection ) { - this.props.onSelect(); - } - } - - /** - * Prevents default dragging behavior within a block to allow for multi- - * selection to take effect unhampered. - * - * @param {DragEvent} event Drag event. - * - * @return {void} */ - preventDrag( event ) { - event.preventDefault(); - } - - /** - * Begins tracking cursor multi-selection when clicking down within block. - * - * @param {MouseEvent} event A mousedown event. - * - * @return {void} - */ - onPointerDown( event ) { - // Not the main button. - // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button - if ( event.button !== 0 ) { - return; + const onFocus = () => { + if ( ! isSelected && ! isPartOfMultiSelection ) { + onSelect(); } - - if ( event.shiftKey ) { - if ( ! this.props.isSelected ) { - this.props.onShiftSelection(); - event.preventDefault(); - } - } else { - this.props.onSelectionStart( this.props.clientId ); - - // Allow user to escape out of a multi-selection to a singular - // selection of a block via click. This is handled here since - // onFocus excludes blocks involved in a multiselection, as - // focus can be incurred by starting a multiselection (focus - // moved to first block's multi-controls). - if ( this.props.isPartOfMultiSelection ) { - this.props.onSelect(); - } - } - } + }; /** * Interprets keydown event intent to remove or insert after block if key @@ -292,15 +262,15 @@ export class BlockListBlock extends Component { * * @param {KeyboardEvent} event Keydown event. */ - deleteOrInsertAfterWrapper( event ) { + const deleteOrInsertAfterWrapper = ( event ) => { const { keyCode, target } = event; // These block shortcuts should only trigger if the wrapper of the block is selected // And when it's not a multi-selection to avoid conflicting with RichText/Inputs and multiselection. if ( - ! this.props.isSelected || - target !== this.wrapperNode || - this.props.isLocked + ! isSelected || + target !== wrapper.current || + isLocked ) { return; } @@ -309,304 +279,290 @@ export class BlockListBlock extends Component { case ENTER: // Insert default block after current block if enter and event // not already handled by descendant. - this.props.onInsertDefaultBlockAfter(); + onInsertDefaultBlockAfter(); event.preventDefault(); break; case BACKSPACE: case DELETE: // Remove block on backspace. - const { clientId, onRemove } = this.props; onRemove( clientId ); event.preventDefault(); break; } - } + }; - onBlockError( error ) { - this.setState( { error } ); - } + /** + * Begins tracking cursor multi-selection when clicking down within block. + * + * @param {MouseEvent} event A mousedown event. + */ + const onPointerDown = ( event ) => { + // Not the main button. + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button + if ( event.button !== 0 ) { + return; + } - onDragStart() { - this.setState( { dragging: true } ); - } + if ( event.shiftKey ) { + if ( ! isSelected ) { + onShiftSelection(); + event.preventDefault(); + } + } else { + onSelectionStart( clientId ); - onDragEnd() { - this.setState( { dragging: false } ); - } + // Allow user to escape out of a multi-selection to a singular + // selection of a block via click. This is handled here since + // onFocus excludes blocks involved in a multiselection, as + // focus can be incurred by starting a multiselection (focus + // moved to first block's multi-controls). + if ( isPartOfMultiSelection ) { + onSelect(); + } + } + }; - selectOnOpen( open ) { - if ( open && ! this.props.isSelected ) { - this.props.onSelect(); + const selectOnOpen = ( open ) => { + if ( open && ! isSelected ) { + onSelect(); } - } + }; - forceFocusedContextualToolbar() { - this.isForcingContextualToolbar = true; - // trigger a re-render - this.setState( () => ( {} ) ); - } + return ( + <HoverArea container={ wrapper.current }> + { ( { hoverArea } ) => { + const isHovered = isBlockHovered && ! isPartOfMultiSelection; + const blockType = getBlockType( name ); + // translators: %s: Type of block (i.e. Text, Image etc) + const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); + // The block as rendered in the editor is composed of general block UI + // (mover, toolbar, wrapper) and the display of the block content. + + const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); + + // If the block is selected and we're typing the block should not appear. + // Empty paragraph blocks should always show up as unselected. + const showEmptyBlockSideInserter = + ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const shouldAppearSelected = + ! isFocusMode && + ! showEmptyBlockSideInserter && + isSelected && + ! isTypingWithinBlock; + const shouldAppearHovered = + ! isFocusMode && + ! hasFixedToolbar && + isHovered && + ! isEmptyDefaultBlock; + // We render block movers and block settings to keep them tabbale even if hidden + const shouldRenderMovers = + ( isSelected || hoverArea === 'left' ) && + ! showEmptyBlockSideInserter && + ! isPartOfMultiSelection && + ! isTypingWithinBlock; + const shouldShowBreadcrumb = + ! isFocusMode && isHovered && ! isEmptyDefaultBlock; + const shouldShowContextualToolbar = + ! hasFixedToolbar && + ! showEmptyBlockSideInserter && + ( ( isSelected && + ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || + isFirstMultiSelected ); + const shouldShowMobileToolbar = shouldAppearSelected; + + // Insertion point can only be made visible if the block is at the + // the extent of a multi-selection, or not in a multi-selection. + const shouldShowInsertionPoint = + ( isPartOfMultiSelection && isFirstMultiSelected ) || + ! isPartOfMultiSelection; + + // The wp-block className is important for editor styles. + // Generate the wrapper class names handling the different states of the block. + const wrapperClassName = classnames( + 'wp-block editor-block-list__block block-editor-block-list__block', + { + 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, + 'is-selected': shouldAppearSelected, + 'is-multi-selected': isPartOfMultiSelection, + 'is-hovered': shouldAppearHovered, + 'is-reusable': isReusableBlock( blockType ), + 'is-dragging': isDragging, + 'is-typing': isTypingWithinBlock, + 'is-focused': + isFocusMode && ( isSelected || isParentOfSelectedBlock ), + 'is-focus-mode': isFocusMode, + }, + className + ); + + // Determine whether the block has props to apply to the wrapper. + let blockWrapperProps = wrapperProps; + if ( blockType.getEditWrapperProps ) { + blockWrapperProps = { + ...blockWrapperProps, + ...blockType.getEditWrapperProps( attributes ), + }; + } + const blockElementId = `block-${ clientId }`; + + // We wrap the BlockEdit component in a div that hides it when editing in + // HTML mode. This allows us to render all of the ancillary pieces + // (InspectorControls, etc.) which are inside `BlockEdit` but not + // `BlockHTML`, even in HTML mode. + let blockEdit = ( + <BlockEdit + name={ name } + isSelected={ isSelected } + attributes={ attributes } + setAttributes={ setAttributes } + insertBlocksAfter={ isLocked ? undefined : onInsertBlocksAfter } + onReplace={ isLocked ? undefined : onReplace } + mergeBlocks={ isLocked ? undefined : onMerge } + clientId={ clientId } + isSelectionEnabled={ isSelectionEnabled } + toggleSelection={ toggleSelection } + /> + ); + if ( mode !== 'visual' ) { + blockEdit = <div style={ { display: 'none' } }>{ blockEdit }</div>; + } - render() { - return ( - <HoverArea container={ this.wrapperNode }> - { ( { hoverArea } ) => { - const { - mode, - isFocusMode, - hasFixedToolbar, - isLocked, - clientId, - rootClientId, - isSelected, - isPartOfMultiSelection, - isFirstMultiSelected, - isTypingWithinBlock, - isCaretWithinFormattedText, - isEmptyDefaultBlock, - isMovable, - isParentOfSelectedBlock, - isDraggable, - className, - name, - isValid, - attributes, - } = this.props; - const isHovered = this.state.isHovered && ! isPartOfMultiSelection; - const blockType = getBlockType( name ); - // translators: %s: Type of block (i.e. Text, Image etc) - const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); - // The block as rendered in the editor is composed of general block UI - // (mover, toolbar, wrapper) and the display of the block content. - - const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); - - // If the block is selected and we're typing the block should not appear. - // Empty paragraph blocks should always show up as unselected. - const showEmptyBlockSideInserter = - ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; - const shouldAppearSelected = - ! isFocusMode && - ! showEmptyBlockSideInserter && - isSelected && - ! isTypingWithinBlock; - const shouldAppearHovered = - ! isFocusMode && - ! hasFixedToolbar && - isHovered && - ! isEmptyDefaultBlock; - // We render block movers and block settings to keep them tabbale even if hidden - const shouldRenderMovers = - ( isSelected || hoverArea === 'left' ) && - ! showEmptyBlockSideInserter && - ! isPartOfMultiSelection && - ! isTypingWithinBlock; - const shouldShowBreadcrumb = - ! isFocusMode && isHovered && ! isEmptyDefaultBlock; - const shouldShowContextualToolbar = - ! hasFixedToolbar && - ! showEmptyBlockSideInserter && - ( ( isSelected && - ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || - isFirstMultiSelected ); - const shouldShowMobileToolbar = shouldAppearSelected; - const { error, dragging } = this.state; - - // Insertion point can only be made visible if the block is at the - // the extent of a multi-selection, or not in a multi-selection. - const shouldShowInsertionPoint = - ( isPartOfMultiSelection && isFirstMultiSelected ) || - ! isPartOfMultiSelection; - - // The wp-block className is important for editor styles. - // Generate the wrapper class names handling the different states of the block. - const wrapperClassName = classnames( - 'wp-block editor-block-list__block block-editor-block-list__block', - { - 'has-warning': ! isValid || !! error || isUnregisteredBlock, - 'is-selected': shouldAppearSelected, - 'is-multi-selected': isPartOfMultiSelection, - 'is-hovered': shouldAppearHovered, - 'is-reusable': isReusableBlock( blockType ), - 'is-dragging': dragging, - 'is-typing': isTypingWithinBlock, - 'is-focused': - isFocusMode && ( isSelected || isParentOfSelectedBlock ), - 'is-focus-mode': isFocusMode, - }, - className - ); - - const { onReplace } = this.props; - - // Determine whether the block has props to apply to the wrapper. - let wrapperProps = this.props.wrapperProps; - if ( blockType.getEditWrapperProps ) { - wrapperProps = { - ...wrapperProps, - ...blockType.getEditWrapperProps( attributes ), - }; - } - const blockElementId = `block-${ clientId }`; - - // We wrap the BlockEdit component in a div that hides it when editing in - // HTML mode. This allows us to render all of the ancillary pieces - // (InspectorControls, etc.) which are inside `BlockEdit` but not - // `BlockHTML`, even in HTML mode. - let blockEdit = ( - <BlockEdit - name={ name } - isSelected={ isSelected } - attributes={ attributes } - setAttributes={ this.setAttributes } - insertBlocksAfter={ isLocked ? undefined : this.props.onInsertBlocksAfter } - onReplace={ isLocked ? undefined : onReplace } - mergeBlocks={ isLocked ? undefined : this.props.onMerge } + // Disable reasons: + // + // jsx-a11y/mouse-events-have-key-events: + // - onMouseOver is explicitly handling hover effects + // + // jsx-a11y/no-static-element-interactions: + // - Each block can be selected by clicking on it + + /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + + return ( + <IgnoreNestedEvents + id={ blockElementId } + ref={ wrapper } + onMouseOver={ maybeHover } + onMouseOverHandled={ hideHoverEffects } + onMouseLeave={ hideHoverEffects } + className={ wrapperClassName } + data-type={ name } + onTouchStart={ onTouchStart } + onFocus={ onFocus } + onClick={ onTouchStop } + onKeyDown={ deleteOrInsertAfterWrapper } + tabIndex="0" + aria-label={ blockLabel } + childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } + { ...blockWrapperProps } + > + { shouldShowInsertionPoint && ( + <BlockInsertionPoint + clientId={ clientId } + rootClientId={ rootClientId } + /> + ) } + <BlockDropZone clientId={ clientId } - isSelectionEnabled={ this.props.isSelectionEnabled } - toggleSelection={ this.props.toggleSelection } + rootClientId={ rootClientId } /> - ); - if ( mode !== 'visual' ) { - blockEdit = <div style={ { display: 'none' } }>{ blockEdit }</div>; - } - - // Disable reasons: - // - // jsx-a11y/mouse-events-have-key-events: - // - onMouseOver is explicitly handling hover effects - // - // jsx-a11y/no-static-element-interactions: - // - Each block can be selected by clicking on it - - /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - - return ( - <IgnoreNestedEvents - id={ blockElementId } - ref={ this.setBlockListRef } - onMouseOver={ this.maybeHover } - onMouseOverHandled={ this.hideHoverEffects } - onMouseLeave={ this.hideHoverEffects } - className={ wrapperClassName } - data-type={ name } - onTouchStart={ this.onTouchStart } - onFocus={ this.onFocus } - onClick={ this.onClick } - onKeyDown={ this.deleteOrInsertAfterWrapper } - tabIndex="0" - aria-label={ blockLabel } - childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } - { ...wrapperProps } - > - { shouldShowInsertionPoint && ( - <BlockInsertionPoint + { isFirstMultiSelected && ( + <BlockMultiControls rootClientId={ rootClientId } /> + ) } + <div className="editor-block-list__block-edit block-editor-block-list__block-edit"> + { shouldRenderMovers && ( + <BlockMover + clientIds={ clientId } + blockElementId={ blockElementId } + isHidden={ ! ( isHovered || isSelected ) || hoverArea !== 'left' } + isDraggable={ + isDraggable !== false && + ( ! isPartOfMultiSelection && isMovable ) + } + onDragStart={ onDragStart } + onDragEnd={ onDragEnd } + /> + ) } + { shouldShowBreadcrumb && ( + <BlockBreadcrumb clientId={ clientId } - rootClientId={ rootClientId } + isHidden={ + ! ( isHovered || isSelected ) || hoverArea !== 'left' + } /> ) } - <BlockDropZone - clientId={ clientId } - rootClientId={ rootClientId } - /> - { isFirstMultiSelected && ( - <BlockMultiControls rootClientId={ rootClientId } /> + { ( shouldShowContextualToolbar || + isForcingContextualToolbar.current ) && ( + <BlockContextualToolbar + // If the toolbar is being shown because of being forced + // it should focus the toolbar right after the mount. + focusOnMount={ isForcingContextualToolbar.current } + /> ) } - <div className="editor-block-list__block-edit block-editor-block-list__block-edit"> - { shouldRenderMovers && ( - <BlockMover - clientIds={ clientId } - blockElementId={ blockElementId } - isHidden={ ! ( isHovered || isSelected ) || hoverArea !== 'left' } - isDraggable={ - isDraggable !== false && - ( ! isPartOfMultiSelection && isMovable ) - } - onDragStart={ this.onDragStart } - onDragEnd={ this.onDragEnd } - /> + { ! shouldShowContextualToolbar && + isSelected && + ! hasFixedToolbar && + ! isEmptyDefaultBlock && ( + <KeyboardShortcuts + bindGlobal + eventName="keydown" + shortcuts={ { + 'alt+f10': forceFocusedContextualToolbar, + } } + /> + ) } + <IgnoreNestedEvents + ref={ blockNodeRef } + onDragStart={ preventDrag } + onMouseDown={ onPointerDown } + data-block={ clientId } + > + <BlockCrashBoundary onError={ onBlockError }> + { isValid && blockEdit } + { isValid && mode === 'html' && ( + <BlockHtml clientId={ clientId } /> + ) } + { ! isValid && [ + <BlockInvalidWarning + key="invalid-warning" + clientId={ clientId } + />, + <div key="invalid-preview"> + { getSaveElement( blockType, attributes ) } + </div>, + ] } + </BlockCrashBoundary> + { shouldShowMobileToolbar && ( + <BlockMobileToolbar clientId={ clientId } /> ) } - { shouldShowBreadcrumb && ( - <BlockBreadcrumb + { !! hasError && <BlockCrashWarning /> } + </IgnoreNestedEvents> + </div> + { showEmptyBlockSideInserter && ( + <> + <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> + <InserterWithShortcuts clientId={ clientId } - isHidden={ - ! ( isHovered || isSelected ) || hoverArea !== 'left' - } - /> - ) } - { ( shouldShowContextualToolbar || - this.isForcingContextualToolbar ) && ( - <BlockContextualToolbar - // If the toolbar is being shown because of being forced - // it should focus the toolbar right after the mount. - focusOnMount={ this.isForcingContextualToolbar } + rootClientId={ rootClientId } + onToggle={ selectOnOpen } /> - ) } - { ! shouldShowContextualToolbar && - isSelected && - ! hasFixedToolbar && - ! isEmptyDefaultBlock && ( - <KeyboardShortcuts - bindGlobal - eventName="keydown" - shortcuts={ { - 'alt+f10': this.forceFocusedContextualToolbar, - } } + </div> + <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> + <Inserter + position="top right" + onToggle={ selectOnOpen } + rootClientId={ rootClientId } + clientId={ clientId } /> - ) } - <IgnoreNestedEvents - ref={ this.bindBlockNode } - onDragStart={ this.preventDrag } - onMouseDown={ this.onPointerDown } - data-block={ clientId } - > - <BlockCrashBoundary onError={ this.onBlockError }> - { isValid && blockEdit } - { isValid && mode === 'html' && ( - <BlockHtml clientId={ clientId } /> - ) } - { ! isValid && [ - <BlockInvalidWarning - key="invalid-warning" - clientId={ clientId } - />, - <div key="invalid-preview"> - { getSaveElement( blockType, attributes ) } - </div>, - ] } - </BlockCrashBoundary> - { shouldShowMobileToolbar && ( - <BlockMobileToolbar clientId={ clientId } /> - ) } - { !! error && <BlockCrashWarning /> } - </IgnoreNestedEvents> - </div> - { showEmptyBlockSideInserter && ( - <> - <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> - <InserterWithShortcuts - clientId={ clientId } - rootClientId={ rootClientId } - onToggle={ this.selectOnOpen } - /> - </div> - <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> - <Inserter - position="top right" - onToggle={ this.selectOnOpen } - rootClientId={ rootClientId } - clientId={ clientId } - /> - </div> - </> - ) } - </IgnoreNestedEvents> - ); - /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - } } - </HoverArea> - ); - } + </div> + </> + ) } + </IgnoreNestedEvents> + ); + /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + } } + </HoverArea> + ); } const applyWithSelect = withSelect( @@ -681,11 +637,31 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks, replaceBlocks, toggleSelection, + } = dispatch( 'core/block-editor' ); return { - onChange( clientId, attributes ) { - updateBlockAttributes( clientId, attributes ); + setAttributes( newAttributes ) { + const { name, clientId } = ownProps; + const type = getBlockType( name ); + updateBlockAttributes( clientId, newAttributes ); + const metaAttributes = reduce( + newAttributes, + ( result, value, key ) => { + if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { + result[ type.attributes[ key ].meta ] = value; + } + + return result; + }, + {} + ); + + if ( size( metaAttributes ) ) { + const { getSettings } = select( 'core/block-editor' ); + const onChangeMeta = getSettings().__experimentalMetaSource.onChange; + onChangeMeta( metaAttributes ); + } }, onSelect( clientId = ownProps.clientId, initialPosition ) { selectBlock( clientId, initialPosition ); @@ -735,11 +711,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { onReplace( blocks ) { replaceBlocks( [ ownProps.clientId ], blocks ); }, - onMetaChange( updatedMeta ) { - const { getSettings } = select( 'core/block-editor' ); - const onChangeMeta = getSettings().__experimentalMetaSource.onChange; - onChangeMeta( updatedMeta ); - }, onShiftSelection() { if ( ! ownProps.isSelectionEnabled ) { return; diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js index 4947d15fb57468..8f88b208df2e36 100644 --- a/packages/eslint-plugin/configs/react.js +++ b/packages/eslint-plugin/configs/react.js @@ -26,6 +26,5 @@ module.exports = { 'react/prop-types': 'off', 'react/react-in-jsx-scope': 'off', 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', }, }; From 9c6e06202d20ad5687d80a7ec8001ead6dd63433 Mon Sep 17 00:00:00 2001 From: Nicolas Juen <njuen87@gmail.com> Date: Tue, 7 May 2019 13:48:44 +0200 Subject: [PATCH 049/664] Add an e2e test to check custom taxonomies (#15151) --- .../e2e-tests/plugins/custom-taxonomies.php | 51 +++++++++++++++++++ .../specs/plugins/custom-taxonomies.test.js | 50 ++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 packages/e2e-tests/plugins/custom-taxonomies.php create mode 100644 packages/e2e-tests/specs/plugins/custom-taxonomies.test.js diff --git a/packages/e2e-tests/plugins/custom-taxonomies.php b/packages/e2e-tests/plugins/custom-taxonomies.php new file mode 100644 index 00000000000000..094f3761a427b6 --- /dev/null +++ b/packages/e2e-tests/plugins/custom-taxonomies.php @@ -0,0 +1,51 @@ +<?php +/** + * Plugin Name: Gutenberg Test Custom Taxonomies + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-taxonomies + */ + +/** + * Registers a taxonomy with custom labels + */ +function taxonomy_custom_label() { + register_taxonomy( + 'model', + 'post', + array( + 'labels' => array( + 'name' => 'Models', + 'singular_name' => 'Model', + 'menu_name' => 'Model', + 'all_items' => 'All Models', + 'parent_item' => 'Parent Model', + 'parent_item_colon' => 'Parent Model:', + 'new_item_name' => 'New Model name', + 'add_new_item' => 'Add New Model', + 'edit_item' => 'Edit Model', + 'update_item' => 'Update Model', + 'view_item' => 'View Model', + 'separate_items_with_commas' => 'Separate models with commas', + 'add_or_remove_items' => 'Add or remove models', + 'choose_from_most_used' => 'Choose from the most used', + 'popular_items' => 'Popular Models', + 'search_items' => 'Search Models', + 'not_found' => 'Not Found', + 'no_terms' => 'No models', + 'items_list' => 'Models list', + 'items_list_navigation' => 'Models list navigation', + ), + 'hierarchical' => false, + 'public' => true, + 'show_ui' => true, + 'show_admin_column' => true, + 'show_in_nav_menus' => true, + 'show_tagcloud' => true, + 'show_in_rest' => true, + ) + ); +} + +add_action( 'init', 'taxonomy_custom_label' ); diff --git a/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js b/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js new file mode 100644 index 00000000000000..f66f5aeb6dba83 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + deactivatePlugin, findSidebarPanelWithTitle, openDocumentSettingsSidebar, +} from '@wordpress/e2e-test-utils'; + +describe( 'Custom Taxonomies labels are used', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-custom-taxonomies' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-custom-taxonomies' ); + } ); + + it( 'Ensures the custom taxonomy labels are respected', async () => { + // Open the Setting sidebar. + await openDocumentSettingsSidebar(); + + const openButton = await findSidebarPanelWithTitle( 'Model' ); + expect( openButton ).not.toBeFalsy(); + + // Get the classes from the panel + const buttonClassName = await( await openButton.getProperty( 'className' ) ).jsonValue(); + + // Open the panel if needed. + if ( -1 === buttonClassName.indexOf( 'is-opened' ) ) { + await openButton.click(); + } + + // Check the add new button + const labelNew = await page.$x( "//label[@class='components-form-token-field__label' and contains(text(), 'Add New Model')]" ); + expect( labelNew ).not.toBeFalsy(); + + // Fill with one entry + await page.type( 'input.components-form-token-field__input', 'Model 1' ); + await page.keyboard.press( 'Enter' ); + + // Check the "Remove Model" + const value = await page.$x( "//div[@class='components-form-token-field__input-container']//span//button[@aria-label='Remove Model']" ); + expect( value ).not.toBeFalsy(); + } ); +} ); From 5c87bcebb562c645c3ce57080b5d0319d7467be2 Mon Sep 17 00:00:00 2001 From: Alex Sanford <alex.sanford1@gmail.com> Date: Tue, 7 May 2019 08:53:26 -0300 Subject: [PATCH 050/664] Add check for author in post data before submission (#15375) Previously, the submission code on the Post Edit page assumed that the Post data from the REST API contained an author field. But this is not the case for CPT's that do not include `author` in their `supports` array. This commit only includes the author in the submission if it exists. --- packages/edit-post/src/store/effects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index 6c64a173130975..54bfe07fbd67b5 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -76,7 +76,7 @@ const effects = { post.comment_status ? [ 'comment_status', post.comment_status ] : false, post.ping_status ? [ 'ping_status', post.ping_status ] : false, post.sticky ? [ 'sticky', post.sticky ] : false, - [ 'post_author', post.author ], + post.author ? [ 'post_author', post.author ] : false, ].filter( Boolean ); // We gather all the metaboxes locations data and the base form data From b2ad325096b96a086bef8223fd5b61aa02e4d25c Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 7 May 2019 15:31:48 +0100 Subject: [PATCH 051/664] Fix linting issue in the custom taxonomies test --- packages/e2e-tests/specs/plugins/custom-taxonomies.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js b/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js index f66f5aeb6dba83..fccba18eafae1d 100644 --- a/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js +++ b/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js @@ -28,7 +28,7 @@ describe( 'Custom Taxonomies labels are used', () => { expect( openButton ).not.toBeFalsy(); // Get the classes from the panel - const buttonClassName = await( await openButton.getProperty( 'className' ) ).jsonValue(); + const buttonClassName = await ( await openButton.getProperty( 'className' ) ).jsonValue(); // Open the panel if needed. if ( -1 === buttonClassName.indexOf( 'is-opened' ) ) { From 1385adf98197a4f12b1490c87a3487ffad2c88a3 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 7 May 2019 18:18:26 +0100 Subject: [PATCH 052/664] Fix: Impossible to drag & drop blocks if locking is insert (#14521) If the CPT locking is set to insert it should be possible to move the blocks. Currently, it is possible to move the blocks using the block move buttons but not possible using drag & drop. --- .../block-editor/src/components/block-drop-zone/index.js | 6 +++--- packages/block-editor/src/store/actions.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js index fc71ee0a0c7d21..e0a0d1060ce4fe 100644 --- a/packages/block-editor/src/components/block-drop-zone/index.js +++ b/packages/block-editor/src/components/block-drop-zone/index.js @@ -111,8 +111,8 @@ class BlockDropZone extends Component { } render() { - const { isLocked, index } = this.props; - if ( isLocked ) { + const { isLockedAll, index } = this.props; + if ( isLockedAll ) { return null; } const isAppender = index === undefined; @@ -158,7 +158,7 @@ export default compose( withSelect( ( select, { rootClientId } ) => { const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/block-editor' ); return { - isLocked: !! getTemplateLock( rootClientId ), + isLockedAll: getTemplateLock( rootClientId ) === 'all', getClientIdsOfDescendants, getBlockIndex, }; diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 6915280885ad42..42d866c04b21d5 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -298,7 +298,7 @@ export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); * * @yields {Object} Action object. */ -export function* moveBlockToPosition( clientId, fromRootClientId, toRootClientId, index ) { +export function* moveBlockToPosition( clientId, fromRootClientId = '', toRootClientId = '', index ) { const templateLock = yield select( 'core/block-editor', 'getTemplateLock', From 33208ba4e6e54038b40dade94fb70055d171a16f Mon Sep 17 00:00:00 2001 From: Joen Asmussen <asmussen@gmail.com> Date: Wed, 8 May 2019 08:30:02 +0200 Subject: [PATCH 053/664] Add back focus style to Document Outline panel (#15479) This PR intends to, and maybe fixes #15324. That is, it restores the focus outline style to the document outline popover which regressed. --- .../editor/src/components/table-of-contents/style.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/table-of-contents/style.scss b/packages/editor/src/components/table-of-contents/style.scss index 322e098b62a42c..bd748416c6b8ab 100644 --- a/packages/editor/src/components/table-of-contents/style.scss +++ b/packages/editor/src/components/table-of-contents/style.scss @@ -4,7 +4,7 @@ .table-of-contents__popover { .components-popover__content { - padding: 16px; + padding: $grid-size-large; @include break-small { max-height: calc(100vh - 120px); @@ -20,6 +20,11 @@ .table-of-contents__counts { display: flex; flex-wrap: wrap; + + &:focus { + @include square-style__focus(); + outline-offset: $grid-size; + } } .table-of-contents__count { From 885f32ef319b89a91183ad1495b1aa3b20d782c4 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 8 May 2019 08:49:55 +0100 Subject: [PATCH 054/664] Fix: Intermittent problem on block transforms tests. (#15485) --- packages/e2e-test-utils/src/transform-block-to.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 39f191331e3794..079adbea2f379f 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -9,4 +9,10 @@ export async function transformBlockTo( name ) { await page.click( '.block-editor-block-switcher__toggle' ); await page.waitForSelector( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); await page.click( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); + const BLOCK_SELECTOR = '.block-editor-block-list__block'; + const BLOCK_NAME_SELECTOR = `[aria-label="Block: ${ name }"]`; + // Wait for the transformed block to appear. + await page.waitForSelector( + `${ BLOCK_SELECTOR }${ BLOCK_NAME_SELECTOR }` + ); } From cdfb97c8fa61a43431e4c38fa5be9939ed052822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 8 May 2019 12:09:08 +0200 Subject: [PATCH 055/664] Remove componentWillReceiveProps from ColorPicker (#11772) --- .../test/__snapshots__/index.js.snap | 4 +- packages/components/src/color-picker/alpha.js | 3 +- packages/components/src/color-picker/hue.js | 7 +- packages/components/src/color-picker/index.js | 154 ++- .../components/src/color-picker/inputs.js | 143 +-- .../components/src/color-picker/saturation.js | 7 +- .../test/__snapshots__/index.js.snap | 1042 ++++++++++++++++- .../components/src/color-picker/test/index.js | 83 +- .../components/src/color-picker/test/input.js | 162 +++ 9 files changed, 1465 insertions(+), 140 deletions(-) create mode 100644 packages/components/src/color-picker/test/input.js diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index e0ad2ef51ff3d9..bef88aa74e182a 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -7,7 +7,7 @@ exports[`ColorPalette Dropdown .renderContent should render dropdown content 1`] <div className="components-color-picker__saturation" > - <WithInstanceId(Saturation) + <Pure(WithInstanceId(Saturation)) hsl={ Object { "a": 1, @@ -48,7 +48,7 @@ exports[`ColorPalette Dropdown .renderContent should render dropdown content 1`] <div className="components-color-picker__toggles" > - <WithInstanceId(Hue) + <Pure(WithInstanceId(Hue)) hsl={ Object { "a": 1, diff --git a/packages/components/src/color-picker/alpha.js b/packages/components/src/color-picker/alpha.js index 7e08d687f71bd6..eb6e889e7ca470 100644 --- a/packages/components/src/color-picker/alpha.js +++ b/packages/components/src/color-picker/alpha.js @@ -36,6 +36,7 @@ import { noop } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; +import { pure } from '@wordpress/compose'; /** * Internal dependencies @@ -174,4 +175,4 @@ export class Alpha extends Component { } } -export default Alpha; +export default pure( Alpha ); diff --git a/packages/components/src/color-picker/hue.js b/packages/components/src/color-picker/hue.js index 8f78c439365793..214df7e725bdc1 100644 --- a/packages/components/src/color-picker/hue.js +++ b/packages/components/src/color-picker/hue.js @@ -33,7 +33,7 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { withInstanceId } from '@wordpress/compose'; +import { compose, pure, withInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; @@ -171,4 +171,7 @@ export class Hue extends Component { } } -export default withInstanceId( Hue ); +export default compose( + pure, + withInstanceId +)( Hue ); diff --git a/packages/components/src/color-picker/index.js b/packages/components/src/color-picker/index.js index ba786b33fcb326..3db7096c2e3d96 100644 --- a/packages/components/src/color-picker/index.js +++ b/packages/components/src/color-picker/index.js @@ -43,30 +43,150 @@ import Alpha from './alpha'; import Hue from './hue'; import Inputs from './inputs'; import Saturation from './saturation'; -import { colorToState, simpleCheckForValidColor } from './utils'; +import { colorToState, simpleCheckForValidColor, isValidHex } from './utils'; + +const toLowerCase = ( value ) => String( value ).toLowerCase(); +const isValueEmpty = ( data ) => { + if ( data.source === 'hex' && ! data.hex ) { + return true; + } else if ( data.source === 'hsl' && ( ! data.h || ! data.s || ! data.l ) ) { + return true; + } else if ( data.source === 'rgb' && ( + ( ! data.r || ! data.g || ! data.b ) && + ( ! data.h || ! data.s || ! data.v || ! data.a ) && + ( ! data.h || ! data.s || ! data.l || ! data.a ) + ) ) { + return true; + } + return false; +}; +const isValidColor = ( colors ) => colors.hex ? + isValidHex( colors.hex ) : + simpleCheckForValidColor( colors ); + +/** + * Function that creates the new color object + * from old data and the new value. + * + * @param {Object} oldColors The old color object. + * @param {string} oldColors.hex + * @param {Object} oldColors.rgb + * @param {number} oldColors.rgb.r + * @param {number} oldColors.rgb.g + * @param {number} oldColors.rgb.b + * @param {number} oldColors.rgb.a + * @param {Object} oldColors.hsl + * @param {number} oldColors.hsl.h + * @param {number} oldColors.hsl.s + * @param {number} oldColors.hsl.l + * @param {number} oldColors.hsl.a + * @param {string} oldColors.draftHex Same format as oldColors.hex + * @param {Object} oldColors.draftRgb Same format as oldColors.rgb + * @param {Object} oldColors.draftHsl Same format as oldColors.hsl + * @param {Object} data Data containing the new value to update. + * @param {Object} data.source One of `hex`, `rgb`, `hsl`. + * @param {string\number} data.value Value to update. + * @param {string} data.valueKey Depends on `data.source` values: + * - when source = `rgb`, valuKey can be `r`, `g`, `b`, or `a`. + * - when source = `hsl`, valuKey can be `h`, `s`, `l`, or `a`. + * @return {Object} A new color object for a specific source. For example: + * { source: 'rgb', r: 1, g: 2, b:3, a:0 } + */ +const dataToColors = ( oldColors, { source, valueKey, value } ) => { + if ( source === 'hex' ) { + return { + source, + [ source ]: value, + }; + } + return { + source, + ...{ ...oldColors[ source ], ...{ [ valueKey ]: value } }, + }; +}; export default class ColorPicker extends Component { constructor( { color = '0071a1' } ) { super( ...arguments ); - this.state = colorToState( color ); - this.handleChange = this.handleChange.bind( this ); + const colors = colorToState( color ); + this.state = { + ...colors, + draftHex: toLowerCase( colors.hex ), + draftRgb: colors.rgb, + draftHsl: colors.hsl, + }; + this.commitValues = this.commitValues.bind( this ); + this.setDraftValues = this.setDraftValues.bind( this ); + this.resetDraftValues = this.resetDraftValues.bind( this ); + this.handleInputChange = this.handleInputChange.bind( this ); } - handleChange( data ) { + commitValues( data ) { const { oldHue, onChangeComplete = noop } = this.props; - const isValidColor = simpleCheckForValidColor( data ); - if ( isValidColor ) { + + if ( isValidColor( data ) ) { const colors = colorToState( data, data.h || oldHue ); - this.setState( - colors, - debounce( partial( onChangeComplete, colors ), 100 ) + this.setState( { + ...colors, + draftHex: toLowerCase( colors.hex ), + draftHsl: colors.hsl, + draftRgb: colors.rgb, + }, + debounce( partial( onChangeComplete, colors ), 100 ) ); } } + resetDraftValues() { + this.setState( { + draftHex: this.state.hex, + draftHsl: this.state.hsl, + draftRgb: this.state.rgb, + } ); + } + + setDraftValues( data ) { + switch ( data.source ) { + case 'hex': + this.setState( { draftHex: toLowerCase( data.hex ) } ); + break; + case 'rgb': + this.setState( { draftRgb: data } ); + break; + case 'hsl': + this.setState( { draftHsl: data } ); + break; + } + } + + handleInputChange( data ) { + switch ( data.state ) { + case 'reset': + this.resetDraftValues(); + break; + case 'commit': + const colors = dataToColors( this.state, data ); + if ( ! isValueEmpty( colors ) ) { + this.commitValues( colors ); + } + break; + case 'draft': + this.setDraftValues( dataToColors( this.state, data ) ); + break; + } + } + render() { const { className, disableAlpha } = this.props; - const { color, hex, hsl, hsv, rgb } = this.state; + const { + color, + hsl, + hsv, + rgb, + draftHex, + draftHsl, + draftRgb, + } = this.state; const classes = classnames( className, { 'components-color-picker': true, 'is-alpha-disabled': disableAlpha, @@ -79,7 +199,7 @@ export default class ColorPicker extends Component { <Saturation hsl={ hsl } hsv={ hsv } - onChange={ this.handleChange } + onChange={ this.commitValues } /> </div> @@ -95,22 +215,22 @@ export default class ColorPicker extends Component { </div> <div className="components-color-picker__toggles"> - <Hue hsl={ hsl } onChange={ this.handleChange } /> + <Hue hsl={ hsl } onChange={ this.commitValues } /> { disableAlpha ? null : ( <Alpha rgb={ rgb } hsl={ hsl } - onChange={ this.handleChange } + onChange={ this.commitValues } /> ) } </div> </div> <Inputs - rgb={ rgb } - hsl={ hsl } - hex={ hex } - onChange={ this.handleChange } + rgb={ draftRgb } + hsl={ draftHsl } + hex={ draftHex } + onChange={ this.handleInputChange } disableAlpha={ disableAlpha } /> </div> diff --git a/packages/components/src/color-picker/inputs.js b/packages/components/src/color-picker/inputs.js index c42aa0022f6c6a..39817d13edb74f 100644 --- a/packages/components/src/color-picker/inputs.js +++ b/packages/components/src/color-picker/inputs.js @@ -10,59 +10,68 @@ import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { DOWN, ENTER, UP } from '@wordpress/keycodes'; +import { pure } from '@wordpress/compose'; /** * Internal dependencies */ import IconButton from '../icon-button'; -import { isValidHex } from './utils'; import TextControl from '../text-control'; +import { isValidHex } from './utils'; /* Wrapper for TextControl, only used to handle intermediate state while typing. */ -class Input extends Component { - constructor( { value } ) { +export class Input extends Component { + constructor() { super( ...arguments ); - this.state = { value: String( value ).toLowerCase() }; this.handleBlur = this.handleBlur.bind( this ); this.handleChange = this.handleChange.bind( this ); this.handleKeyDown = this.handleKeyDown.bind( this ); } - componentWillReceiveProps( nextProps ) { - if ( nextProps.value !== this.props.value ) { - this.setState( { - value: String( nextProps.value ).toLowerCase(), - } ); - } - } - handleBlur() { - const { valueKey, onChange } = this.props; - const { value } = this.state; - onChange( { [ valueKey ]: value } ); + const { value, valueKey, onChange, source } = this.props; + onChange( { + source, + state: 'commit', + value, + valueKey, + } ); } handleChange( value ) { - const { valueKey, onChange } = this.props; - // Protect against expanding a value while we're typing. - if ( value.length > 4 ) { - onChange( { [ valueKey ]: value } ); + const { valueKey, onChange, source } = this.props; + if ( value.length > 4 && isValidHex( value ) ) { + onChange( { + source, + state: 'commit', + value, + valueKey, + } ); + } else { + onChange( { + source, + state: 'draft', + value, + valueKey, + } ); } - this.setState( { value } ); } handleKeyDown( { keyCode } ) { if ( keyCode !== ENTER && keyCode !== UP && keyCode !== DOWN ) { return; } - const { value } = this.state; - const { valueKey, onChange } = this.props; - onChange( { [ valueKey ]: value } ); + const { value, valueKey, onChange, source } = this.props; + onChange( { + source, + state: 'commit', + value, + valueKey, + } ); } render() { - const { label, ...props } = this.props; - const { value } = this.state; + const { label, value, ...props } = this.props; return ( <TextControl className="components-color-picker__inputs-field" @@ -71,12 +80,14 @@ class Input extends Component { onChange={ ( newValue ) => this.handleChange( newValue ) } onBlur={ this.handleBlur } onKeyDown={ this.handleKeyDown } - { ...omit( props, [ 'onChange', 'value', 'valueKey' ] ) } + { ...omit( props, [ 'onChange', 'valueKey', 'source' ] ) } /> ); } } +const PureIconButton = pure( IconButton ); + export class Inputs extends Component { constructor( { hsl } ) { super( ...arguments ); @@ -85,7 +96,9 @@ export class Inputs extends Component { this.state = { view }; this.toggleViews = this.toggleViews.bind( this ); + this.resetDraftValues = this.resetDraftValues.bind( this ); this.handleChange = this.handleChange.bind( this ); + this.normalizeValue = this.normalizeValue.bind( this ); } static getDerivedStateFromProps( props, state ) { @@ -97,63 +110,52 @@ export class Inputs extends Component { toggleViews() { if ( this.state.view === 'hex' ) { - this.setState( { view: 'rgb' } ); + this.setState( { view: 'rgb' }, this.resetDraftValues ); speak( __( 'RGB mode active' ) ); } else if ( this.state.view === 'rgb' ) { - this.setState( { view: 'hsl' } ); + this.setState( { view: 'hsl' }, this.resetDraftValues ); speak( __( 'Hue/saturation/lightness mode active' ) ); } else if ( this.state.view === 'hsl' ) { if ( this.props.hsl.a === 1 ) { - this.setState( { view: 'hex' } ); + this.setState( { view: 'hex' }, this.resetDraftValues ); speak( __( 'Hex color mode active' ) ); } else { - this.setState( { view: 'rgb' } ); + this.setState( { view: 'rgb' }, this.resetDraftValues ); speak( __( 'RGB mode active' ) ); } } } - handleChange( data ) { - if ( data.hex ) { - if ( isValidHex( data.hex ) ) { - this.props.onChange( { - hex: data.hex, - source: 'hex', - } ); - } - } else if ( data.r || data.g || data.b ) { - this.props.onChange( { - r: data.r || this.props.rgb.r, - g: data.g || this.props.rgb.g, - b: data.b || this.props.rgb.b, - source: 'rgb', - } ); - } else if ( data.a ) { - if ( data.a < 0 ) { - data.a = 0; - } else if ( data.a > 1 ) { - data.a = 1; - } + resetDraftValues() { + return this.props.onChange( { + state: 'reset', + } ); + } - this.props.onChange( { - h: this.props.hsl.h, - s: this.props.hsl.s, - l: this.props.hsl.l, - a: Math.round( data.a * 100 ) / 100, - source: 'rgb', - } ); - } else if ( data.h || data.s || data.l ) { - this.props.onChange( { - h: data.h || this.props.hsl.h, - s: data.s || this.props.hsl.s, - l: data.l || this.props.hsl.l, - source: 'hsl', - } ); + normalizeValue( valueKey, value ) { + if ( valueKey !== 'a' ) { + return value; } + + if ( value > 0 ) { + return 0; + } else if ( value > 1 ) { + return 1; + } + return Math.round( value * 100 ) / 100; + } + + handleChange( { source, state, value, valueKey } ) { + this.props.onChange( { + source, + state, + valueKey, + value: this.normalizeValue( valueKey, value ), + } ); } renderFields() { @@ -162,6 +164,7 @@ export class Inputs extends Component { return ( <div className="components-color-picker__inputs-fields"> <Input + source={ this.state.view } label={ __( 'Color value in hexadecimal' ) } valueKey="hex" value={ this.props.hex } @@ -177,6 +180,7 @@ export class Inputs extends Component { </legend> <div className="components-color-picker__inputs-fields"> <Input + source={ this.state.view } label="r" valueKey="r" value={ this.props.rgb.r } @@ -186,6 +190,7 @@ export class Inputs extends Component { max="255" /> <Input + source={ this.state.view } label="g" valueKey="g" value={ this.props.rgb.g } @@ -195,6 +200,7 @@ export class Inputs extends Component { max="255" /> <Input + source={ this.state.view } label="b" valueKey="b" value={ this.props.rgb.b } @@ -205,6 +211,7 @@ export class Inputs extends Component { /> { disableAlpha ? null : ( <Input + source={ this.state.view } label="a" valueKey="a" value={ this.props.rgb.a } @@ -226,6 +233,7 @@ export class Inputs extends Component { </legend> <div className="components-color-picker__inputs-fields"> <Input + source={ this.state.view } label="h" valueKey="h" value={ this.props.hsl.h } @@ -235,6 +243,7 @@ export class Inputs extends Component { max="359" /> <Input + source={ this.state.view } label="s" valueKey="s" value={ this.props.hsl.s } @@ -244,6 +253,7 @@ export class Inputs extends Component { max="100" /> <Input + source={ this.state.view } label="l" valueKey="l" value={ this.props.hsl.l } @@ -254,6 +264,7 @@ export class Inputs extends Component { /> { disableAlpha ? null : ( <Input + source={ this.state.view } label="a" valueKey="a" value={ this.props.hsl.a } @@ -275,7 +286,7 @@ export class Inputs extends Component { <div className="components-color-picker__inputs-wrapper"> { this.renderFields() } <div className="components-color-picker__inputs-toggle"> - <IconButton + <PureIconButton icon="arrow-down-alt2" label={ __( 'Change color format' ) } onClick={ this.toggleViews } diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index 90c6f8661bb90b..ac52818a6571c7 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -36,7 +36,7 @@ import { clamp, noop, throttle } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { TAB } from '@wordpress/keycodes'; -import { withInstanceId } from '@wordpress/compose'; +import { compose, pure, withInstanceId } from '@wordpress/compose'; /** * Internal dependencies @@ -185,4 +185,7 @@ export class Saturation extends Component { } } -export default withInstanceId( Saturation ); +export default compose( + pure, + withInstanceId +)( Saturation ); diff --git a/packages/components/src/color-picker/test/__snapshots__/index.js.snap b/packages/components/src/color-picker/test/__snapshots__/index.js.snap index c143801f9e29f5..1cb694aa24f833 100644 --- a/packages/components/src/color-picker/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-picker/test/__snapshots__/index.js.snap @@ -1,31 +1,224 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ColorPicker should render color picker 1`] = ` +exports[`ColorPicker should commit changes to all views on blur 1`] = ` <div className="components-color-picker is-alpha-disabled" > <div className="components-color-picker__saturation" > - <WithInstanceId(Saturation) - hsl={ - Object { - "a": 1, - "h": 0, - "l": 100, - "s": 0, + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } } - } - hsv={ - Object { - "a": 1, - "h": 0, - "s": 0, - "v": 100, + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-2" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-2" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(170, 187, 204)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-2" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-2" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-2" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-2" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should commit changes to all views on keyDown = DOWN 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } } - } - onChange={[Function]} - /> + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-4" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-4" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> </div> <div className="components-color-picker__body" @@ -40,7 +233,7 @@ exports[`ColorPicker should render color picker 1`] = ` className="components-color-picker__active" style={ Object { - "backgroundColor": "rgb(255, 255, 255)", + "backgroundColor": "rgb(170, 187, 204)", } } /> @@ -48,40 +241,799 @@ exports[`ColorPicker should render color picker 1`] = ` <div className="components-color-picker__toggles" > - <WithInstanceId(Hue) - hsl={ + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-4" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-4" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-4" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-4" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should commit changes to all views on keyDown = ENTER 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } + } + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-5" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-5" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(170, 187, 204)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-5" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-5" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-5" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-5" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should commit changes to all views on keyDown = UP 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(210,100%, 50%)", + } + } + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-3" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "17%", + "top": "20%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-3" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ Object { - "a": 1, - "h": 0, - "l": 100, - "s": 0, + "backgroundColor": "rgb(170, 187, 204)", } } - onChange={[Function]} /> </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-3" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={210} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "58.333333333333336%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-3" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-3" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-3" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#aabbcc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> </div> - <Inputs - disableAlpha={true} - hex="#ffffff" - hsl={ - Object { - "a": 1, - "h": 0, - "l": 100, - "s": 0, + </div> +</div> +`; + +exports[`ColorPicker should only update input view for draft changes 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(0,100%, 50%)", + } } - } - onChange={[Function]} - rgb={ - Object { - "a": 1, - "b": 255, - "g": 255, - "r": 255, + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-1" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "0%", + "top": "0%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-1" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(255, 255, 255)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-1" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={0} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "0%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-1" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-1" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-1" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#abc" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`ColorPicker should render color picker 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(0,100%, 50%)", + } } - } - /> + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-0" + aria-label="Choose a shade" + className="components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "0%", + "top": "0%", + } + } + /> + <div + className="screen-reader-text" + id="color-picker-saturation-0" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(255, 255, 255)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-0" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={0} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "0%", + } + } + tabIndex="0" + /> + <p + className="components-color-picker__hue-description screen-reader-text" + id="components-color-picker__hue-description-0" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-0" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-0" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#ffffff" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> </div> </div> `; diff --git a/packages/components/src/color-picker/test/index.js b/packages/components/src/color-picker/test/index.js index 5510da6e138e22..57e512bd65b8e6 100644 --- a/packages/components/src/color-picker/test/index.js +++ b/packages/components/src/color-picker/test/index.js @@ -1,7 +1,12 @@ /** * External dependencies */ -import ShallowRenderer from 'react-test-renderer/shallow'; +import TestRenderer from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { DOWN, ENTER, UP } from '@wordpress/keycodes'; /** * Internal dependencies @@ -10,17 +15,85 @@ import ColorPicker from '../'; describe( 'ColorPicker', () => { test( 'should render color picker', () => { - const color = '#fff'; + const color = '#FFF'; + + const renderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + + expect( renderer.toJSON() ).toMatchSnapshot(); + } ); - const renderer = new ShallowRenderer(); - renderer.render( + test( 'should only update input view for draft changes', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( <ColorPicker color={ color } onChangeComplete={ () => {} } disableAlpha /> ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); - expect( renderer.getRenderOutput() ).toMatchSnapshot(); + test( 'should commit changes to all views on blur', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onBlur(); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); + + test( 'should commit changes to all views on keyDown = UP', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: UP } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); + + test( 'should commit changes to all views on keyDown = DOWN', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: DOWN } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); + } ); + + test( 'should commit changes to all views on keyDown = ENTER', () => { + const color = '#FFF'; + const testRenderer = TestRenderer.create( + <ColorPicker + color={ color } + onChangeComplete={ () => {} } + disableAlpha + /> + ); + testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: ENTER } ); + expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); } ); diff --git a/packages/components/src/color-picker/test/input.js b/packages/components/src/color-picker/test/input.js new file mode 100644 index 00000000000000..8e4bbd8f78d3c8 --- /dev/null +++ b/packages/components/src/color-picker/test/input.js @@ -0,0 +1,162 @@ +/** + * External dependencies + */ +import TestRenderer from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { DOWN, ENTER, SPACE, UP } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { Input } from '../inputs'; + +describe( 'Input ', () => { + describe( 'calls onChange prop with commit state', () => { + test( 'onKeyDown = ENTER', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: ENTER } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + + test( 'onKeyDown = UP', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: UP } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + + test( 'onKeyDown = DOWN', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: DOWN } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + + test( 'onChange event for value.length > 4', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onChange( { target: { value: '#aaaaaa' } } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#aaaaaa', + valueKey: 'hex', + } ); + } ); + + test( 'onBlur', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onBlur(); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'commit', + value: '#fff', + valueKey: 'hex', + } ); + } ); + } ); + + describe( 'does call onChange with draft state', () => { + test( 'onChange event for value.length <= 4', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + source="rgb" + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onChange( { target: { value: '#aaaaa' } } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( { + source: 'rgb', + state: 'draft', + value: '#aaaaa', + valueKey: 'hex', + } ); + } ); + } ); + + describe( 'does not call onChange', () => { + test( 'onKeyDown not ENTER, DOWN, or UP', () => { + const onChange = jest.fn(); + const testInstance = TestRenderer.create( + <Input + label={ 'Color value in hexadecimal' } + valueKey="hex" + value={ '#fff' } + onChange={ onChange } + /> + ).root; + testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: SPACE } ); + expect( onChange ).not.toHaveBeenCalled(); + } ); + } ); +} ); From 17c35712866214c50d746cee05b7adce969643cd Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 8 May 2019 15:35:45 -0400 Subject: [PATCH 056/664] Testing: Skip test failures for font decoding warnings (#15502) * Testing: Skip test failures for font decoding warnings * Testing: Fix Classic block Add Media toolbar button click --- packages/e2e-test-utils/CHANGELOG.md | 5 +++++ packages/e2e-tests/config/setup-test-framework.js | 13 +++++++++++++ packages/e2e-tests/specs/blocks/classic.test.js | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index ff9db333694b7b..1b6edf2340b684 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -4,6 +4,11 @@ - The minimum version of Gutenberg `5.6.0` or the minimum version of WordPress `5.2.0`. +### Bug Fixes + +- WordPress 5.2: Fix a false positive build failure caused by Dashicons font file. +- WordPress 5.2: Fix a test failure for Classic Block media insertion caused by a change in tooltips text ([rWP45066](https://core.trac.wordpress.org/changeset/45066)). + ## 1.1.0 (2019-03-20) ### New Features diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 368f0540920d09..b14717620320ce 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -122,6 +122,19 @@ function observeConsoleLogging() { return; } + // A bug present in WordPress 5.2 will produce console warnings when + // loading the Dashicons font. These can be safely ignored, as they do + // not otherwise regress on application behavior. This logic should be + // removed once the associated ticket has been closed. + // + // See: https://core.trac.wordpress.org/ticket/47183 + if ( + text.startsWith( 'Failed to decode downloaded font:' ) || + text.startsWith( 'OTS parsing error:' ) + ) { + return; + } + const logFunction = OBSERVED_CONSOLE_MESSAGE_TYPES[ type ]; // As of Puppeteer 1.6.1, `message.text()` wrongly returns an object of diff --git a/packages/e2e-tests/specs/blocks/classic.test.js b/packages/e2e-tests/specs/blocks/classic.test.js index 2df2d1f2c89822..29d1605f0874b5 100644 --- a/packages/e2e-tests/specs/blocks/classic.test.js +++ b/packages/e2e-tests/specs/blocks/classic.test.js @@ -43,8 +43,8 @@ describe( 'Classic', () => { await page.keyboard.type( 'test' ); // Click the image button. - await page.waitForSelector( 'div[aria-label="Add Media"]' ); - await page.click( 'div[aria-label="Add Media"]' ); + await page.waitForSelector( 'div[aria-label^="Add Media"]' ); + await page.click( 'div[aria-label^="Add Media"]' ); // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); From 090586e6273dbd637b167d87b37b977e32a5a23f Mon Sep 17 00:00:00 2001 From: Hardip Parmar <parmarhardip1995@gmail.com> Date: Thu, 9 May 2019 02:13:38 +0530 Subject: [PATCH 057/664] #15493 I have fixed scrolling issue. (#15494) --- packages/components/src/select-control/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index c7a1008be7c0c9..30fcb148ba8cc6 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -6,9 +6,9 @@ SelectControl allow users to select from a single-option menu. It functions as a ## Table of contents -1. [Design guidelines](http://##design-guidelines) -2. [Development guidelines](http://##development-guidelines) -3. [Related components](http://##related-components) +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) ## Design guidelines From f6f7375d8dfa9ef52ef784e5e486c71656421770 Mon Sep 17 00:00:00 2001 From: Garrett Hyder <garrett@eclipse3sixty.com> Date: Wed, 8 May 2019 13:50:12 -0700 Subject: [PATCH 058/664] Fix the ToC for Text Area Control (#15506) Removing the http:// prefix on these anchor links fixes them. --- packages/components/src/textarea-control/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/textarea-control/README.md b/packages/components/src/textarea-control/README.md index f5cd78d3ad5082..bf5ff5164300e0 100644 --- a/packages/components/src/textarea-control/README.md +++ b/packages/components/src/textarea-control/README.md @@ -6,9 +6,9 @@ TextareaControls are TextControls that allow for multiple lines of text, and wra ## Table of contents -1. [Design guidelines](http://#design-guidelines) -2. [Development guidelines](http://#development-guidelines) -3. [Related components](http://#related-components) +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) ## Design guidelines From 59242335430a08819b69570367332cd66e705ffd Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 8 May 2019 23:48:17 +0100 Subject: [PATCH 059/664] Add WordPress Area CPT (#15014) This commit is straight to the point and just proposes an implementation for the wp_area CPT required to implement the widgets RFC. --- lib/widgets.php | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lib/widgets.php b/lib/widgets.php index c08d31de9f3f11..e26f0b5574f597 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -126,3 +126,57 @@ function gutenberg_legacy_widget_settings( $settings ) { return $settings; } add_filter( 'block_editor_settings', 'gutenberg_legacy_widget_settings' ); + +/** + * Registers a wp_area post type. + */ +function gutenberg_create_wp_area_post_type() { + register_post_type( + 'wp_area', + array( + 'labels' => array( + 'name' => _x( 'Block Area', 'post type general name', 'gutenberg' ), + 'singular_name' => _x( 'Block Area', 'post type singular name', 'gutenberg' ), + 'menu_name' => _x( 'Block Areas', 'admin menu', 'gutenberg' ), + 'name_admin_bar' => _x( 'Block Area', 'add new on admin bar', 'gutenberg' ), + 'add_new' => _x( 'Add New', 'Block', 'gutenberg' ), + 'add_new_item' => __( 'Add New Block Area', 'gutenberg' ), + 'new_item' => __( 'New Block Area', 'gutenberg' ), + 'edit_item' => __( 'Edit Block Area', 'gutenberg' ), + 'view_item' => __( 'View Block Area', 'gutenberg' ), + 'all_items' => __( 'All Block Areas', 'gutenberg' ), + 'search_items' => __( 'Search Block Areas', 'gutenberg' ), + 'not_found' => __( 'No block area found.', 'gutenberg' ), + 'not_found_in_trash' => __( 'No block areas found in Trash.', 'gutenberg' ), + 'filter_items_list' => __( 'Filter block areas list', 'gutenberg' ), + 'items_list_navigation' => __( 'Block areas list navigation', 'gutenberg' ), + 'items_list' => __( 'Block areas list', 'gutenberg' ), + 'item_published' => __( 'Block area published.', 'gutenberg' ), + 'item_published_privately' => __( 'Block area published privately.', 'gutenberg' ), + 'item_reverted_to_draft' => __( 'Block area reverted to draft.', 'gutenberg' ), + 'item_scheduled' => __( 'Block area scheduled.', 'gutenberg' ), + 'item_updated' => __( 'Block area updated.', 'gutenberg' ), + ), + 'public' => false, + 'show_ui' => false, + 'show_in_menu' => false, + 'show_in_rest' => true, + 'rest_base' => '__experimental/block-areas', + 'capabilities' => array( + 'read' => 'edit_posts', + 'create_posts' => 'edit_theme_options', + 'edit_posts' => 'edit_theme_options', + 'edit_published_posts' => 'edit_theme_options', + 'delete_published_posts' => 'edit_theme_options', + 'edit_others_posts' => 'edit_theme_options', + 'delete_others_posts' => 'edit_theme_options', + ), + 'map_meta_cap' => true, + 'supports' => array( + 'title', + 'editor', + ), + ) + ); +} +add_action( 'init', 'gutenberg_create_wp_area_post_type' ); From f628a8ae122f09702cd11daa59435e90dc65dd16 Mon Sep 17 00:00:00 2001 From: Nicola Heald <nicola@notnowlewis.com> Date: Thu, 9 May 2019 08:27:14 +0100 Subject: [PATCH 060/664] Ensure correct attributes are always used for embed preview (#14748) * Ensure correct attributes are always used for embed preview * Docs update * Update missed call * Fix up attributes function names, clarify render comment --- packages/block-library/src/embed/edit.js | 68 ++++++++++-------------- packages/block-library/src/embed/util.js | 40 +++++++++++++- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index e62a6173001833..97c28876280160 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { isFromWordPress, createUpgradedEmbedBlock, getClassNames, fallback } from './util'; +import { createUpgradedEmbedBlock, getClassNames, fallback, getAttributesFromPreview } from './util'; import EmbedControls from './embed-controls'; import EmbedLoading from './embed-loading'; import EmbedPlaceholder from './embed-placeholder'; @@ -10,7 +10,7 @@ import EmbedPreview from './embed-preview'; /** * External dependencies */ -import { kebabCase, toLower } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies @@ -24,8 +24,8 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { super( ...arguments ); this.switchBackToURLInput = this.switchBackToURLInput.bind( this ); this.setUrl = this.setUrl.bind( this ); - this.getAttributesFromPreview = this.getAttributesFromPreview.bind( this ); - this.setAttributesFromPreview = this.setAttributesFromPreview.bind( this ); + this.getMergedAttributes = this.getMergedAttributes.bind( this ); + this.setMergedAttributes = this.setMergedAttributes.bind( this ); this.getResponsiveHelp = this.getResponsiveHelp.bind( this ); this.toggleResponsive = this.toggleResponsive.bind( this ); this.handleIncomingPreview = this.handleIncomingPreview.bind( this ); @@ -41,11 +41,10 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } handleIncomingPreview() { - const { allowResponsive } = this.props.attributes; - this.setAttributesFromPreview(); + this.setMergedAttributes(); const upgradedBlock = createUpgradedEmbedBlock( this.props, - this.getAttributesFromPreview( this.props.preview, allowResponsive ) + this.getMergedAttributes() ); if ( upgradedBlock ) { this.props.onReplace( upgradedBlock ); @@ -90,42 +89,20 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } /*** - * Gets block attributes based on the preview and responsive state. - * - * @param {string} preview The preview data. - * @param {boolean} allowResponsive Apply responsive classes to fixed size content. - * @return {Object} Attributes and values. + * @return {Object} Attributes derived from the preview, merged with the current attributes. */ - getAttributesFromPreview( preview, allowResponsive = true ) { - const attributes = {}; - // Some plugins only return HTML with no type info, so default this to 'rich'. - let { type = 'rich' } = preview; - // If we got a provider name from the API, use it for the slug, otherwise we use the title, - // because not all embed code gives us a provider name. - const { html, provider_name: providerName } = preview; - const providerNameSlug = kebabCase( toLower( '' !== providerName ? providerName : title ) ); - - if ( isFromWordPress( html ) ) { - type = 'wp-embed'; - } - - if ( html || 'photo' === type ) { - attributes.type = type; - attributes.providerNameSlug = providerNameSlug; - } - - attributes.className = getClassNames( html, this.props.attributes.className, responsive && allowResponsive ); - - return attributes; + getMergedAttributes() { + const { preview } = this.props; + const { className, allowResponsive } = this.props.attributes; + return { ...this.props.attributes, ...getAttributesFromPreview( preview, title, className, responsive, allowResponsive ) }; } /*** - * Sets block attributes based on the preview data. + * Sets block attributes based on the current attributes and preview data. */ - setAttributesFromPreview() { - const { setAttributes, preview } = this.props; - const { allowResponsive } = this.props.attributes; - setAttributes( this.getAttributesFromPreview( preview, allowResponsive ) ); + setMergedAttributes() { + const { setAttributes } = this.props; + setAttributes( this.getMergedAttributes() ); } switchBackToURLInput() { @@ -151,8 +128,7 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { render() { const { url, editingURL } = this.state; - const { caption, type, allowResponsive } = this.props.attributes; - const { fetching, setAttributes, isSelected, className, preview, cannotEmbed, themeSupportsResponsive, tryAgain } = this.props; + const { fetching, setAttributes, isSelected, preview, cannotEmbed, themeSupportsResponsive, tryAgain } = this.props; if ( fetching ) { return ( @@ -179,6 +155,18 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { ); } + // Even though we set attributes that get derived from the preview, + // we don't access them directly because for the initial render, + // the `setAttributes` call will not have taken effect. If we're + // rendering responsive content, setting the responsive classes + // after the preview has been rendered can result in unwanted + // clipping or scrollbars. The `getAttributesFromPreview` function + // that `getMergedAttributes` uses is memoized so that we're not + // calculating them on every render. + const previewAttributes = this.getMergedAttributes(); + const { caption, type, allowResponsive } = previewAttributes; + const className = classnames( previewAttributes.className, this.props.className ); + return ( <> <EmbedControls diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 9cba9f50364745..f52716f2cb96f2 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -7,8 +7,9 @@ import { DEFAULT_EMBED_BLOCK, WORDPRESS_EMBED_BLOCK, ASPECT_RATIOS } from './con /** * External dependencies */ -import { includes } from 'lodash'; +import { includes, kebabCase, toLower } from 'lodash'; import classnames from 'classnames/dedupe'; +import memoize from 'memize'; /** * WordPress dependencies @@ -178,3 +179,40 @@ export function fallback( url, onReplace ) { createBlock( 'core/paragraph', { content: renderToString( link ) } ) ); } + +/*** + * Gets block attributes based on the preview and responsive state. + * + * @param {Object} preview The preview data. + * @param {string} title The block's title, e.g. Twitter. + * @param {Object} currentClassNames The block's current class names. + * @param {boolean} isResponsive Boolean indicating if the block supports responsive content. + * @param {boolean} allowResponsive Apply responsive classes to fixed size content. + * @return {Object} Attributes and values. + */ +export const getAttributesFromPreview = memoize( ( preview, title, currentClassNames, isResponsive, allowResponsive = true ) => { + if ( ! preview ) { + return {}; + } + + const attributes = {}; + // Some plugins only return HTML with no type info, so default this to 'rich'. + let { type = 'rich' } = preview; + // If we got a provider name from the API, use it for the slug, otherwise we use the title, + // because not all embed code gives us a provider name. + const { html, provider_name: providerName } = preview; + const providerNameSlug = kebabCase( toLower( '' !== providerName ? providerName : title ) ); + + if ( isFromWordPress( html ) ) { + type = 'wp-embed'; + } + + if ( html || 'photo' === type ) { + attributes.type = type; + attributes.providerNameSlug = providerNameSlug; + } + + attributes.className = getClassNames( html, currentClassNames, isResponsive && allowResponsive ); + + return attributes; +} ); From 48c67fbad6fe9c26f3cad02df678bb3cdeffad13 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Thu, 9 May 2019 15:54:37 +0800 Subject: [PATCH 061/664] Add table block header and footer toggles (#15409) * Add basic toggle controls for table header and footer * Add basic styling for table header and footer section * Update copy * Add appropriate cell dependent on section * Add tests * Update readme?! * Ok?!?!?!? * Remove thead/tfoot border * Add an additional test * Make striping style of table block only apply to tbody * Make th element have transparent borders when using striped table block * Add e2e tests for existing table block functionality * Add a test for table header and footer functionality --- packages/block-library/src/table/edit.js | 23 ++ packages/block-library/src/table/state.js | 30 ++- packages/block-library/src/table/style.scss | 11 +- .../block-library/src/table/test/state.js | 239 ++++++++++++++++++ .../blocks/__snapshots__/table.test.js.snap | 25 ++ packages/e2e-tests/specs/blocks/table.test.js | 116 +++++++++ 6 files changed, 435 insertions(+), 9 deletions(-) create mode 100644 packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap create mode 100644 packages/e2e-tests/specs/blocks/table.test.js diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 2bed02abc2c1bf..79af531cd2929b 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -34,6 +34,7 @@ import { deleteRow, insertColumn, deleteColumn, + toggleSection, } from './state'; const BACKGROUND_COLORS = [ @@ -80,6 +81,8 @@ export class TableEdit extends Component { this.onInsertColumnBefore = this.onInsertColumnBefore.bind( this ); this.onInsertColumnAfter = this.onInsertColumnAfter.bind( this ); this.onDeleteColumn = this.onDeleteColumn.bind( this ); + this.onToggleHeaderSection = this.onToggleHeaderSection.bind( this ); + this.onToggleFooterSection = this.onToggleFooterSection.bind( this ); this.state = { initialRowCount: 2, @@ -195,6 +198,16 @@ export class TableEdit extends Component { this.onInsertRow( 1 ); } + onToggleHeaderSection() { + const { attributes, setAttributes } = this.props; + setAttributes( toggleSection( attributes, 'head' ) ); + } + + onToggleFooterSection() { + const { attributes, setAttributes } = this.props; + setAttributes( toggleSection( attributes, 'foot' ) ); + } + /** * Deletes the currently selected row. */ @@ -448,6 +461,16 @@ export class TableEdit extends Component { checked={ !! hasFixedLayout } onChange={ this.onChangeFixedLayout } /> + <ToggleControl + label={ __( 'Header section' ) } + checked={ !! ( head && head.length ) } + onChange={ this.onToggleHeaderSection } + /> + <ToggleControl + label={ __( 'Footer section' ) } + checked={ !! ( foot && foot.length ) } + onChange={ this.onToggleFooterSection } + /> </PanelBody> <PanelColorSettings title={ __( 'Color Settings' ) } diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 233d181650c2dc..7c649d5dcdb097 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { times } from 'lodash'; +import { times, get } from 'lodash'; /** * Creates a table state. @@ -76,8 +76,9 @@ export function updateCellContent( state, { export function insertRow( state, { section, rowIndex, + columnCount, } ) { - const cellCount = state[ section ][ 0 ].cells.length; + const cellCount = columnCount || state[ section ][ 0 ].cells.length; return { [ section ]: [ @@ -85,7 +86,7 @@ export function insertRow( state, { { cells: times( cellCount, () => ( { content: '', - tag: 'td', + tag: section === 'head' ? 'th' : 'td', } ) ), }, ...state[ section ].slice( rowIndex ), @@ -130,7 +131,7 @@ export function insertColumn( state, { ...row.cells.slice( 0, columnIndex ), { content: '', - tag: 'td', + tag: section === 'head' ? 'th' : 'td', }, ...row.cells.slice( columnIndex ), ], @@ -157,3 +158,24 @@ export function deleteColumn( state, { } ) ).filter( ( row ) => row.cells.length ), }; } + +/** + * Toggles the existance of a section. + * + * @param {Object} state Current table state. + * @param {string} section Name of the section to toggle. + * + * @return {Object} New table state. + */ +export function toggleSection( state, section ) { + // Section exists, replace it with an empty row to remove it. + if ( state[ section ] && state[ section ].length ) { + return { [ section ]: [] }; + } + + // Get the length of the first row of the body to use when creating the header. + const columnCount = get( state, [ 'body', 0, 'cells', 'length' ], 1 ); + + // Section doesn't exist, insert an empty row to create the section. + return insertRow( state, { section, rowIndex: 0, columnCount } ); +} diff --git a/packages/block-library/src/table/style.scss b/packages/block-library/src/table/style.scss index 3fda38e27b8604..b79d044fac2b35 100644 --- a/packages/block-library/src/table/style.scss +++ b/packages/block-library/src/table/style.scss @@ -45,34 +45,35 @@ border-collapse: inherit; background-color: transparent; - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $light-gray-200; } &.has-subtle-light-gray-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-light-gray; } } &.has-subtle-pale-green-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-pale-green; } } &.has-subtle-pale-blue-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-pale-blue; } } &.has-subtle-pale-pink-background-color { - tr:nth-child(odd) { + tbody tr:nth-child(odd) { background-color: $subtle-pale-pink; } } + th, td { border-color: transparent; } diff --git a/packages/block-library/src/table/test/state.js b/packages/block-library/src/table/test/state.js index c684333283738b..63904298be2bba 100644 --- a/packages/block-library/src/table/test/state.js +++ b/packages/block-library/src/table/test/state.js @@ -13,6 +13,7 @@ import { deleteRow, insertColumn, deleteColumn, + toggleSection, } from '../state'; const table = deepFreeze( { @@ -144,6 +145,108 @@ describe( 'insertRow', () => { expect( state ).toEqual( expected ); } ); + + it( 'allows the number of columns to be specified', () => { + const state = insertRow( tableWithContent, { + section: 'body', + rowIndex: 2, + columnCount: 4, + } ); + + const expected = { + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: 'test', + tag: 'td', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'adds `th` cells to the head', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + const state = insertRow( tableWithHead, { + section: 'head', + rowIndex: 1, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); } ); describe( 'insertColumn', () => { @@ -192,6 +295,45 @@ describe( 'insertColumn', () => { expect( state ).toEqual( expected ); } ); + + it( 'adds `th` cells to the head', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + const state = insertColumn( tableWithHead, { + section: 'head', + columnIndex: 1, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); } ); describe( 'deleteRow', () => { @@ -286,3 +428,100 @@ describe( 'deleteColumn', () => { expect( state ).toEqual( expected ); } ); } ); + +describe( 'toggleSection', () => { + it( 'removes rows from the head section if the table already has them', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + const state = toggleSection( tableWithHead, 'head' ); + + const expected = { + head: [], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'adds a row to the head section if the table has none', () => { + const tableWithHead = { + head: [], + }; + + const state = toggleSection( tableWithHead, 'head' ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'uses the number of cells in the first row of the body for the added table row', () => { + const tableWithHead = { + head: [], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + const state = toggleSection( tableWithHead, 'head' ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); +} ); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap new file mode 100644 index 00000000000000..955d6f95e06fd4 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table allows header and footer rows to be switched on and off 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table> +<!-- /wp:table -->" +`; + +exports[`Table allows header and footer rows to be switched on and off 2`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody></table> +<!-- /wp:table -->" +`; + +exports[`Table allows text to by typed into cells 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><tbody><tr><td>This</td><td>is</td></tr><tr><td>table</td><td>block</td></tr></tbody></table> +<!-- /wp:table -->" +`; + +exports[`Table displays a form for choosing the row and column count of the table 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><tbody><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table> +<!-- /wp:table -->" +`; diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js new file mode 100644 index 00000000000000..3a61f5e5eda594 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -0,0 +1,116 @@ +/** + * WordPress dependencies + */ +import { createNewPost, insertBlock, getEditedPostContent } from '@wordpress/e2e-test-utils'; + +describe( 'Table', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'displays a form for choosing the row and column count of the table', async () => { + await insertBlock( 'Table' ); + + // Check for existence of the column count field. + const columnCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Column Count']" ); + expect( columnCountLabel ).toHaveLength( 1 ); + + // Modify the column count. + await columnCountLabel[ 0 ].click(); + const currentColumnCount = await page.evaluate( () => document.activeElement.value ); + expect( currentColumnCount ).toBe( '2' ); + await page.keyboard.press( 'Backspace' ); + await page.keyboard.type( '5' ); + + // // Check for existence of the row count field. + const rowCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Row Count']" ); + expect( rowCountLabel ).toHaveLength( 1 ); + + // // Modify the row count. + await rowCountLabel[ 0 ].click(); + const currentRowCount = await page.evaluate( () => document.activeElement.value ); + expect( currentRowCount ).toBe( '2' ); + await page.keyboard.press( 'Backspace' ); + await page.keyboard.type( '10' ); + + // // Create the table. + const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + await createButton[ 0 ].click(); + + // // Expect the post content to have a correctly sized table. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'allows text to by typed into cells', async () => { + await insertBlock( 'Table' ); + + // Create the table. + const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + await createButton[ 0 ].click(); + + // Click the first cell and add some text. + await page.click( '.wp-block-table__cell-content' ); + await page.keyboard.type( 'This' ); + + // Tab to the next cell and add some text. + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'is' ); + + // Tab to the next cell and add some text. + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'table' ); + + // Tab to the next cell and add some text. + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'block' ); + + // Expect the post to have the correct written content inside the table. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'allows header and footer rows to be switched on and off', async () => { + await insertBlock( 'Table' ); + + const headerSwitchSelector = "//label[text()='Header section']"; + const footerSwitchSelector = "//label[text()='Footer section']"; + + // Expect the header and footer switches not to be present before the table has been created. + let headerSwitch = await page.$x( headerSwitchSelector ); + let footerSwitch = await page.$x( footerSwitchSelector ); + expect( headerSwitch ).toHaveLength( 0 ); + expect( footerSwitch ).toHaveLength( 0 ); + + // Create the table. + const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + await createButton[ 0 ].click(); + + // Expect the header and footer switches to be present now that the table has been created. + headerSwitch = await page.$x( headerSwitchSelector ); + footerSwitch = await page.$x( footerSwitchSelector ); + expect( headerSwitch ).toHaveLength( 1 ); + expect( footerSwitch ).toHaveLength( 1 ); + + // Toggle on the switches and add some content. + await headerSwitch[ 0 ].click(); + await footerSwitch[ 0 ].click(); + + await page.click( 'thead .wp-block-table__cell-content' ); + await page.keyboard.type( 'header' ); + + await page.click( 'tbody .wp-block-table__cell-content' ); + await page.keyboard.type( 'body' ); + + await page.click( 'tfoot .wp-block-table__cell-content' ); + await page.keyboard.type( 'footer' ); + + // Expect the table to have a header, body and footer with written content. + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // Toggle off the switches + await headerSwitch[ 0 ].click(); + await footerSwitch[ 0 ].click(); + + // Expect the table to have only a body with written content. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); From edfc84f4b94e9c9584781d485b46f7aa0883db23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 9 May 2019 10:48:26 +0200 Subject: [PATCH 062/664] Update automated notifications for nosolosw (#15523) --- .github/CODEOWNERS | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9fe873dbb50e56..22ec9d8c002b3d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ # Documentation -/docs @youknowriad @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki -/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki -/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki +/docs @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki +/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @notnownikki +/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki # Data /packages/api-fetch @youknowriad @aduth @nerrad @mmtr @@ -28,23 +28,23 @@ /packages/edit-widgets @youknowriad # Tooling -/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /docs/tool @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki /packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/docgen @nosolosw @mkaz @gziolo /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan -/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz # UI Components From 7e0dce0da7177b94ffa58266c11f3b0ca8763ff7 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 9 May 2019 11:04:41 +0100 Subject: [PATCH 063/664] Proxy the code/block-editor replaceBlock action in the core/editor package (#15528) --- .../developers/data/data-core-editor.md | 6 ++++++ packages/editor/src/store/actions.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index b5cc78a84c0870..e3456d65168dbd 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1169,6 +1169,12 @@ _Related_ - removeBlocks in core/block-editor store. +<a name="replaceBlock" href="#replaceBlock">#</a> **replaceBlock** + +_Related_ + +- replaceBlock in core/block-editor store. + <a name="replaceBlocks" href="#replaceBlocks">#</a> **replaceBlocks** _Related_ diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index d7d5b06f2821fc..52f021d1a2f83c 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -824,6 +824,11 @@ export const toggleSelection = getBlockEditorAction( 'toggleSelection' ); */ export const replaceBlocks = getBlockEditorAction( 'replaceBlocks' ); +/** + * @see replaceBlock in core/block-editor store. + */ +export const replaceBlock = getBlockEditorAction( 'replaceBlock' ); + /** * @see moveBlocksDown in core/block-editor store. */ From 8e7f37cfbdfdb0c7825aca62a13f802881518087 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 9 May 2019 07:33:06 -0400 Subject: [PATCH 064/664] Build Tooling: Split JavaScript tasks to individual jobs (#15229) * Build Tooling: Split JavaScript tasks to individual jobs * Build Tooling: Reintroduce install script I thought it redundant. Alas, I was wrong * Build Tooling: Run packages custom build script in unit tests --- .travis.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b7158c74a1dfe4..dc51393e18fed0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,17 +27,40 @@ before_install: jobs: include: - - name: JS unit tests - env: WP_VERSION=latest + - name: Lint before_install: - nvm install --latest-npm install: - npm ci script: - - npm run build - npm run lint + + - name: Build artifacts + before_install: + - nvm install --latest-npm + install: + - npm ci + script: - npm run check-local-changes + + - name: License compatibility + before_install: + - nvm install --latest-npm + install: + - npm ci + script: - npm run check-licenses + + - name: JavaScript unit tests + before_install: + - nvm install --latest-npm + install: + - npm ci + script: + # It's not necessary to run the full build, since Jest can interpret + # source files with `babel-jest`. Some packages have their own custom + # build tasks, however. These must be run. + - npx lerna run build - npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" - name: PHP unit tests (Docker) From d17e867002e801598290a245da16497daed63a41 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 9 May 2019 05:29:18 -0700 Subject: [PATCH 065/664] Update save indicator contrast to pass AA (#15514) --- packages/editor/src/components/post-saved-state/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-saved-state/style.scss b/packages/editor/src/components/post-saved-state/style.scss index 7e6a3ce6e0682e..902888bd3aee50 100644 --- a/packages/editor/src/components/post-saved-state/style.scss +++ b/packages/editor/src/components/post-saved-state/style.scss @@ -3,7 +3,7 @@ align-items: center; width: $icon-button-size - 8px; padding: #{ $grid-size-small * 3 } $grid-size-small; - color: $light-gray-900; // Doesn't need to meet AA because button is disabled and it's supporting text. + color: $dark-gray-500; overflow: hidden; white-space: nowrap; From efbbebd44c94e5a910d2671a97ea9a54a290c37f Mon Sep 17 00:00:00 2001 From: Christian Nyffenegger <freakpants@gmail.com> Date: Fri, 10 May 2019 07:06:09 +0200 Subject: [PATCH 066/664] Fix broken Link (#15527) --- .../block-editor/src/components/button-block-appender/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/button-block-appender/README.md b/packages/block-editor/src/components/button-block-appender/README.md index 6f29d56bea1596..87d917ab50b582 100644 --- a/packages/block-editor/src/components/button-block-appender/README.md +++ b/packages/block-editor/src/components/button-block-appender/README.md @@ -40,4 +40,4 @@ A CSS `class` to be _prepended_ to the default class of `"button-block-appender" ## Examples -The [`<InnerBlocks>` component](packages/block-editor/src/components/inner-blocks/) exposes an enhanced version of `ButtonBlockAppender` to allow consumers to choose it as an alternative to the standard behaviour of auto-inserting the default Block (typically `core/paragraph`). \ No newline at end of file +The [`<InnerBlocks>` component](../inner-blocks/) exposes an enhanced version of `ButtonBlockAppender` to allow consumers to choose it as an alternative to the standard behaviour of auto-inserting the default Block (typically `core/paragraph`). From 18a5443521ae96a33d5de94652919a59185e259f Mon Sep 17 00:00:00 2001 From: Garrett Hyder <garrett@eclipse3sixty.com> Date: Fri, 10 May 2019 00:47:47 -0700 Subject: [PATCH 067/664] Update links in ToC for Checkbox Control (#15508) Removing the 'http://' prefix on the ToC to fix anchor links --- packages/components/src/checkbox-control/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/checkbox-control/README.md b/packages/components/src/checkbox-control/README.md index c011fd2c56fe1f..43165e8ae1b59c 100644 --- a/packages/components/src/checkbox-control/README.md +++ b/packages/components/src/checkbox-control/README.md @@ -8,9 +8,9 @@ Selected and unselected checkboxes ## Table of contents -1. [Design guidelines](http://#design-guidelines) -2. [Development guidelines](http://#development-guidelines) -3. [Related components](http://#related-components) +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) ## Design guidelines From ae0d1e96616d80a1974f101147c37fcf041a1740 Mon Sep 17 00:00:00 2001 From: Garrett Hyder <garrett@eclipse3sixty.com> Date: Fri, 10 May 2019 00:48:18 -0700 Subject: [PATCH 068/664] Removing Related components from Table of Contents as it's not present on the page (#15505) 3. [Related components](#related-components) --- packages/components/src/menu-items-choice/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/menu-items-choice/README.md b/packages/components/src/menu-items-choice/README.md index cbd7fd9f9d663a..bd294f35663894 100644 --- a/packages/components/src/menu-items-choice/README.md +++ b/packages/components/src/menu-items-choice/README.md @@ -10,7 +10,6 @@ 1. [Design guidelines](#design-guidelines) 2. [Development guidelines](#development-guidelines) -3. [Related components](#related-components) ## Design guidelines From f723d021799604607eabd14b0bfa500f28b1d2e8 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 10 May 2019 09:17:05 +0100 Subject: [PATCH 069/664] Fix: Use block-editor instead of editor in cover block. (#15547) --- packages/block-library/src/cover/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index bf9ba990cb9219..f908be2423d654 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -28,7 +28,7 @@ import { MediaUploadCheck, PanelColorSettings, withColors, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; import { Component, createRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; From 8195c7447ff2350055793b6e01a71b7a5b07720f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Fri, 10 May 2019 11:18:16 +0200 Subject: [PATCH 070/664] Fix gallery tab order (#15540) * Structural changes - Reorder: img goes first than the inline menu items - Always render the inline menu items. We want to keep a consistent tabbable experience. We'll style them down when not in focus. * Make button visible * Mark GalleryImage as selected on button focused * Fix color button on focus * Prevent the button from showing up if the GalleryImage is not selected * Make button and caption part of the tab path We can't use the isSelected property to conditionally render them, otherwise, they won't be tabbed through when tabbing backward. * We can actually disable the button depending on isSelected status The GalleryImage gets selected by focusing on the caption (tabbing backward) and the <img> element (tabbing forwards). At that time, the button is enabled and will get the focus. * Do not show caption when GalleryImage is not selected --- .../block-library/src/gallery/editor.scss | 24 +++++++----- .../src/gallery/gallery-image.js | 38 +++++++++---------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 419fc365f468af..ffd0f7847a09cd 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -59,6 +59,19 @@ ul.wp-block-gallery { } } + .is-selected .block-library-gallery-item__inline-menu { + background-color: theme(primary); + + .components-button { + color: $white; + } + + .components-button:focus { + color: inherit; + } + + } + .block-editor-rich-text figcaption { a { color: $white; @@ -71,25 +84,16 @@ ul.wp-block-gallery { position: absolute; top: -2px; right: -2px; - background-color: theme(primary); display: inline-flex; z-index: z-index(".block-library-gallery-item__inline-menu"); .components-button { - color: $white; - &:hover, - &:focus { - color: $white; - } + color: transparent; } } .blocks-gallery-item__remove { padding: 0; - - &.components-button:focus { - color: inherit; - } } .blocks-gallery-item .components-spinner { diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index e69b8385fe7e52..2c91f13df2c6ab 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -127,28 +127,26 @@ class GalleryImage extends Component { return ( <figure className={ className }> - { isSelected && - <div className="block-library-gallery-item__inline-menu"> - <IconButton - icon="no-alt" - onClick={ onRemove } - className="blocks-gallery-item__remove" - label={ __( 'Remove Image' ) } - /> - </div> - } { href ? <a href={ href }>{ img }</a> : img } - { ( ! RichText.isEmpty( caption ) || isSelected ) ? ( - <RichText - tagName="figcaption" - placeholder={ __( 'Write caption…' ) } - value={ caption } - isSelected={ this.state.captionSelected } - onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } - unstableOnFocus={ this.onSelectCaption } - inlineToolbar + <div className="block-library-gallery-item__inline-menu"> + <IconButton + icon="no-alt" + onClick={ onRemove } + onFocus={ this.onSelectImage } + className="blocks-gallery-item__remove" + label={ __( 'Remove Image' ) } + disabled={ ! isSelected } /> - ) : null } + </div> + <RichText + tagName="figcaption" + placeholder={ isSelected ? __( 'Write caption…' ) : null } + value={ caption } + isSelected={ this.state.captionSelected } + onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + unstableOnFocus={ this.onSelectCaption } + inlineToolbar + /> </figure> ); } From 5746ac33459f3dd5f6f2d191de975ad882e0f02b Mon Sep 17 00:00:00 2001 From: Florian Truchot <36343370+truchot@users.noreply.github.com> Date: Fri, 10 May 2019 11:56:11 +0200 Subject: [PATCH 071/664] Fix focus color contrast (#15544) --- assets/stylesheets/_mixins.scss | 4 ++-- packages/components/src/button/style.scss | 6 ++++-- packages/components/src/date-time/style.scss | 11 +++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 111f65dfc411bd..8551c934d6edb9 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -180,8 +180,8 @@ @mixin input-style__focus() { color: $dark-gray-900; - border-color: $blue-medium-500; - box-shadow: 0 0 0 1px $blue-medium-500; + border-color: $blue-medium-focus; + box-shadow: 0 0 0 1px $blue-medium-focus; // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 12cb7e45cb2f07..5eeb1fa0f8a559 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -40,7 +40,8 @@ border-color: #999; box-shadow: inset 0 -1px 0 #999, - 0 0 0 2px $blue-medium-200; + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; text-decoration: none; } @@ -88,7 +89,8 @@ &:focus:enabled { box-shadow: inset 0 -1px 0 color(theme(button) shade(50%)), - 0 0 0 2px $blue-medium-200; + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; } &:active:enabled { diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 9412de832e3ea6..e9c84135c0a320 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -50,6 +50,11 @@ .DayPickerNavigation_button__horizontalDefault { padding: 2px 8px; top: 20px; + + &:focus { + border-color: $blue-medium-focus; + box-shadow: 0 0 0 1px $blue-medium-focus; + } } .DayPicker_weekHeader { @@ -94,6 +99,12 @@ border-radius: 0 3px 3px 0; } + .components-datetime__time-am-button:focus, + .components-datetime__time-pm-button:focus { + position: relative; + z-index: 1; + } + .components-datetime__time-am-button.is-toggled, .components-datetime__time-pm-button.is-toggled { background: $light-gray-300; From 94d48e09dcfb547ad1b142d137a51a378d57077f Mon Sep 17 00:00:00 2001 From: Jorge Bernal <jbernal@gmail.com> Date: Fri, 10 May 2019 13:03:38 +0200 Subject: [PATCH 072/664] Improve accessibility on missing block (#15457) * Improve accessibility on missing block * Update packages/block-library/src/missing/edit.native.js Co-Authored-By: koke <jbernal@gmail.com> * Remove unnecessary dot for accessibility label Co-Authored-By: koke <jbernal@gmail.com> --- .../block-library/src/missing/edit.native.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index 838c64d88b56c4..970975aa711418 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -10,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { coreBlocks } from '@wordpress/block-library'; import { normalizeIconObject } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -25,7 +25,20 @@ export default class UnsupportedBlockEdit extends Component { const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins'; return ( - <View style={ styles.unsupportedBlock }> + <View style={ styles.unsupportedBlock } + accessible={ true } + accessibilityLabel={ + blockType ? + sprintf( + /* translators: accessibility text. %s: unsupported block type. */ + __( 'Unsupported block: %s' ), + title + ) : + /* translators: accessibility text. */ + __( 'Unsupported block' ) + } + onAccessibilityTap={ this.props.onFocus } + > <Icon className="unsupported-icon" icon={ icon && icon.src ? icon.src : icon } /> <Text style={ styles.unsupportedBlockMessage }>{ title }</Text> </View> From 4108e37d3771b5871cd720216432b5bbd5255551 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 10 May 2019 18:43:58 +0100 Subject: [PATCH 073/664] Add experimental indication to name and description of block area cpt (#15563) --- lib/widgets.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/widgets.php b/lib/widgets.php index e26f0b5574f597..e35ccf5b0ef385 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -134,9 +134,10 @@ function gutenberg_create_wp_area_post_type() { register_post_type( 'wp_area', array( + 'description' => __( 'Experimental custom post type that will store block areas referenced by themes.', 'gutenberg' ), 'labels' => array( - 'name' => _x( 'Block Area', 'post type general name', 'gutenberg' ), - 'singular_name' => _x( 'Block Area', 'post type singular name', 'gutenberg' ), + 'name' => _x( 'Block Area (Experimental)', 'post type general name', 'gutenberg' ), + 'singular_name' => _x( 'Block Area (Experimental)', 'post type singular name', 'gutenberg' ), 'menu_name' => _x( 'Block Areas', 'admin menu', 'gutenberg' ), 'name_admin_bar' => _x( 'Block Area', 'add new on admin bar', 'gutenberg' ), 'add_new' => _x( 'Add New', 'Block', 'gutenberg' ), From 9c5bc045e0a964375c1d6be537849e3291c200cd Mon Sep 17 00:00:00 2001 From: Kerry Liu <gwwar@users.noreply.github.com> Date: Sat, 11 May 2019 11:59:37 -0700 Subject: [PATCH 074/664] Check for selection range count before calling getRangeAt (#15576) --- packages/format-library/src/link/inline.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index e94ce68514fb3b..c4b64e9e3c45ce 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -79,7 +79,8 @@ const LinkViewerUrl = ( { url } ) => { const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { const anchorRect = useMemo( () => { - const range = window.getSelection().getRangeAt( 0 ); + const selection = window.getSelection(); + const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; if ( ! range ) { return; } From 295a381838c03ace2d239a6cbb426b1285c1cc69 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Sun, 12 May 2019 08:36:01 +0300 Subject: [PATCH 075/664] [Mobile]Add video block (first iteration) (#14912) Added video block (first iteration --- .../src/components/index.native.js | 1 + .../media-placeholder/index.native.js | 88 +++++-- .../components/media-upload/index.native.js | 116 +++++++++ .../block-library/src/image/edit.native.js | 214 ++++------------ .../src/image/media-upload-progress.native.js | 167 +++++++++++++ packages/block-library/src/index.native.js | 1 + .../block-library/src/video/edit.native.js | 232 ++++++++++++++++++ .../block-library/src/video/style.native.scss | 25 ++ .../src/video/video-player.android.js | 25 ++ .../src/video/video-player.ios.js | 73 ++++++ .../src/video/video-player.native.scss | 32 +++ packages/edit-post/src/index.native.js | 1 + 12 files changed, 795 insertions(+), 180 deletions(-) create mode 100644 packages/block-editor/src/components/media-upload/index.native.js create mode 100644 packages/block-library/src/image/media-upload-progress.native.js create mode 100644 packages/block-library/src/video/edit.native.js create mode 100644 packages/block-library/src/video/style.native.scss create mode 100644 packages/block-library/src/video/video-player.android.js create mode 100644 packages/block-library/src/video/video-player.ios.js create mode 100644 packages/block-library/src/video/video-player.native.scss diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index c1cdb633b190b8..47683399491f17 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -13,6 +13,7 @@ export { __unstableRichTextInputEvent, } from './rich-text'; export { default as MediaPlaceholder } from './media-placeholder'; +export { default as MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from './media-upload'; export { default as URLInput } from './url-input'; // Content Related Components diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index 8a50b464a79146..db98ff4f206753 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -6,8 +6,9 @@ import { View, Text, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { Dashicon } from '@wordpress/components'; +import { MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/block-editor'; /** * Internal dependencies @@ -15,24 +16,75 @@ import { Dashicon } from '@wordpress/components'; import styles from './styles.scss'; function MediaPlaceholder( props ) { + const { mediaType, labels = {}, icon, onSelectURL } = props; + + const isImage = MEDIA_TYPE_IMAGE === mediaType; + const isVideo = MEDIA_TYPE_VIDEO === mediaType; + + let placeholderTitle = labels.title; + if ( placeholderTitle === undefined ) { + placeholderTitle = __( 'Media' ); + if ( isImage ) { + placeholderTitle = __( 'Image' ); + } else if ( isVideo ) { + placeholderTitle = __( 'Video' ); + } + } + + let placeholderIcon = icon; + if ( placeholderIcon === undefined ) { + if ( isImage ) { + placeholderIcon = 'format-image'; + } else if ( isVideo ) { + placeholderIcon = 'format-video'; + } + } + + let instructions = labels.instructions; + if ( instructions === undefined ) { + if ( isImage ) { + instructions = __( 'CHOOSE IMAGE' ); + } else if ( isVideo ) { + instructions = __( 'CHOOSE VIDEO' ); + } + } + + let accessibilityHint = __( 'Double tap to select' ); + if ( isImage ) { + accessibilityHint = __( 'Double tap to select an image' ); + } else if ( isVideo ) { + accessibilityHint = __( 'Double tap to select a video' ); + } + return ( - <TouchableWithoutFeedback - /* translators: accessibility text for the Image block empty state */ - accessibilityLabel={ __( 'Image block. Empty' ) } - accessibilityRole={ 'button' } - accessibilityHint={ __( 'Double tap to select an image' ) } - onPress={ props.onMediaOptionsPressed } - > - <View style={ styles.emptyStateContainer }> - <Dashicon icon={ 'format-image' } /> - <Text style={ styles.emptyStateTitle }> - { __( 'Image' ) } - </Text> - <Text style={ styles.emptyStateDescription }> - { __( 'CHOOSE IMAGE' ) } - </Text> - </View> - </TouchableWithoutFeedback> + <MediaUpload + mediaType={ mediaType } + onSelectURL={ onSelectURL } + render={ ( { open, getMediaOptions } ) => { + return ( + <TouchableWithoutFeedback + accessibilityLabel={ sprintf( + /* translators: accessibility text for the media block empty state. %s: media type */ + __( '%s block. Empty' ), + placeholderTitle + ) } + accessibilityRole={ 'button' } + accessibilityHint={ accessibilityHint } + onPress={ open } + > + <View style={ styles.emptyStateContainer }> + { getMediaOptions() } + <Dashicon icon={ placeholderIcon } /> + <Text style={ styles.emptyStateTitle }> + { placeholderTitle } + </Text> + <Text style={ styles.emptyStateDescription }> + { instructions } + </Text> + </View> + </TouchableWithoutFeedback> + ); + } } /> ); } diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js new file mode 100644 index 00000000000000..d34b3061403ef3 --- /dev/null +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -0,0 +1,116 @@ +/** + * External dependencies + */ +import React from 'react'; +import { + requestMediaPickFromMediaLibrary, + requestMediaPickFromDeviceLibrary, + requestMediaPickFromDeviceCamera, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Picker } from '@wordpress/block-editor'; + +export const MEDIA_TYPE_IMAGE = 'image'; +export const MEDIA_TYPE_VIDEO = 'video'; + +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA = 'take_media'; +const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; + +class MediaUpload extends React.Component { + getTakeMediaLabel() { + const { mediaType } = this.props; + + if ( mediaType === MEDIA_TYPE_IMAGE ) { + return __( 'Take a Photo' ); + } else if ( mediaType === MEDIA_TYPE_VIDEO ) { + return __( 'Take a Video' ); + } + } + + getMediaOptionsItems() { + return [ + { icon: this.getChooseFromDeviceIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, + { icon: this.getTakeMediaIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, label: this.getTakeMediaLabel() }, + { icon: this.getWordPressLibraryIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, + ]; + } + + getChooseFromDeviceIcon() { + const { mediaType } = this.props; + + if ( mediaType === MEDIA_TYPE_IMAGE ) { + return 'format-image'; + } else if ( mediaType === MEDIA_TYPE_VIDEO ) { + return 'format-video'; + } + } + + getTakeMediaIcon() { + return 'camera'; + } + + getWordPressLibraryIcon() { + return 'wordpress-alt'; + } + + render() { + const { mediaType } = this.props; + + const onMediaLibraryButtonPressed = () => { + requestMediaPickFromMediaLibrary( [ mediaType ], ( mediaId, mediaUrl ) => { + if ( mediaId ) { + this.props.onSelectURL( mediaId, mediaUrl ); + } + } ); + }; + + const onMediaUploadButtonPressed = () => { + requestMediaPickFromDeviceLibrary( [ mediaType ], ( mediaId, mediaUrl ) => { + if ( mediaId ) { + this.props.onSelectURL( mediaId, mediaUrl ); + } + } ); + }; + + const onMediaCaptureButtonPressed = () => { + requestMediaPickFromDeviceCamera( [ mediaType ], ( mediaId, mediaUrl ) => { + if ( mediaId ) { + this.props.onSelectURL( mediaId, mediaUrl ); + } + } ); + }; + + const mediaOptions = this.getMediaOptionsItems(); + + let picker; + + const onPickerPresent = () => { + picker.presentPicker(); + }; + + const getMediaOptions = () => ( + <Picker + hideCancelButton={ true } + ref={ ( instance ) => picker = instance } + options={ mediaOptions } + onChange={ ( value ) => { + if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) { + onMediaUploadButtonPressed(); + } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA ) { + onMediaCaptureButtonPressed(); + } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) { + onMediaLibraryButtonPressed(); + } + } } + /> + ); + return this.props.render( { open: onPickerPresent, getMediaOptions } ); + } +} + +export default MediaUpload; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 8f2aa785d3f341..93e21246f5e74a 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,12 +2,8 @@ * External dependencies */ import React from 'react'; -import { View, ImageBackground, TextInput, Text, TouchableWithoutFeedback } from 'react-native'; +import { View, TextInput, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; import { - subscribeMediaUpload, - requestMediaPickFromMediaLibrary, - requestMediaPickFromDeviceLibrary, - requestMediaPickFromDeviceCamera, requestMediaImport, mediaUploadSync, requestImageFailedRetryDialog, @@ -21,16 +17,16 @@ import { isEmpty } from 'lodash'; import { Toolbar, ToolbarButton, - Spinner, Dashicon, } from '@wordpress/components'; import { MediaPlaceholder, + MediaUpload, + MEDIA_TYPE_IMAGE, RichText, BlockControls, InspectorControls, BottomSheet, - Picker, } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; @@ -39,17 +35,8 @@ import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies */ -import ImageSize from './image-size'; import styles from './styles.scss'; - -const MEDIA_UPLOAD_STATE_UPLOADING = 1; -const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; -const MEDIA_UPLOAD_STATE_FAILED = 3; -const MEDIA_UPLOAD_STATE_RESET = 4; - -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO = 'take_photo'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; +import MediaUploadProgress from './media-upload-progress'; const LINK_DESTINATION_CUSTOM = 'custom'; const LINK_DESTINATION_NONE = 'none'; @@ -60,14 +47,12 @@ class ImageEdit extends React.Component { this.state = { showSettings: false, - progress: 0, - isUploadInProgress: false, - isUploadFailed: false, }; - this.mediaUpload = this.mediaUpload.bind( this ); this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); this.updateAlt = this.updateAlt.bind( this ); this.updateImageURL = this.updateImageURL.bind( this ); @@ -77,11 +62,9 @@ class ImageEdit extends React.Component { } componentDidMount() { - this.addMediaUploadListener(); - const { attributes, setAttributes } = this.props; - if ( attributes.id && ! isURL( attributes.url ) ) { + if ( attributes.id && attributes.url && ! isURL( attributes.url ) ) { if ( attributes.url.indexOf( 'file:' ) === 0 ) { requestMediaImport( attributes.url, ( mediaId, mediaUri ) => { if ( mediaUri ) { @@ -98,7 +81,6 @@ class ImageEdit extends React.Component { if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); } - this.removeMediaUploadListener(); } onImagePressed() { @@ -111,35 +93,15 @@ class ImageEdit extends React.Component { } } - mediaUpload( payload ) { - const { attributes } = this.props; - - if ( payload.mediaId !== attributes.id ) { - return; - } - - switch ( payload.state ) { - case MEDIA_UPLOAD_STATE_UPLOADING: - this.updateMediaProgress( payload ); - break; - case MEDIA_UPLOAD_STATE_SUCCEEDED: - this.finishMediaUploadWithSuccess( payload ); - break; - case MEDIA_UPLOAD_STATE_FAILED: - this.finishMediaUploadWithFailure( payload ); - break; - case MEDIA_UPLOAD_STATE_RESET: - this.mediaUploadStateReset( payload ); - break; - } - } - updateMediaProgress( payload ) { const { setAttributes } = this.props; - this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); if ( payload.mediaUrl ) { setAttributes( { url: payload.mediaUrl } ); } + + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); + } } finishMediaUploadWithSuccess( payload ) { @@ -153,30 +115,14 @@ class ImageEdit extends React.Component { const { setAttributes } = this.props; setAttributes( { id: payload.mediaId } ); - this.setState( { isUploadInProgress: false, isUploadFailed: true } ); + this.setState( { isUploadInProgress: false } ); } - mediaUploadStateReset( payload ) { + mediaUploadStateReset() { const { setAttributes } = this.props; - setAttributes( { id: payload.mediaId, url: null } ); - this.setState( { isUploadInProgress: false, isUploadFailed: false } ); - } - - addMediaUploadListener() { - //if we already have a subscription not worth doing it again - if ( this.subscriptionParentMediaUpload ) { - return; - } - this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { - this.mediaUpload( payload ); - } ); - } - - removeMediaUploadListener() { - if ( this.subscriptionParentMediaUpload ) { - this.subscriptionParentMediaUpload.remove(); - } + setAttributes( { id: null, url: null } ); + this.setState( { isUploadInProgress: false } ); } updateAlt( newAlt ) { @@ -202,41 +148,14 @@ class ImageEdit extends React.Component { } ); } - getMediaOptionsItems() { - return [ - { icon: 'format-image', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, - { icon: 'camera', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO, label: __( 'Take a Photo' ) }, - { icon: 'wordpress-alt', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, - ]; + onSelectMediaUploadOption( mediaId, mediaUrl ) { + const { setAttributes } = this.props; + setAttributes( { url: mediaUrl, id: mediaId } ); } render() { const { attributes, isSelected, setAttributes } = this.props; - const { url, caption, height, width, alt, href } = attributes; - - const onMediaLibraryButtonPressed = () => { - requestMediaPickFromMediaLibrary( ( mediaId, mediaUrl ) => { - if ( mediaUrl ) { - setAttributes( { id: mediaId, url: mediaUrl } ); - } - } ); - }; - - const onMediaUploadButtonPressed = () => { - requestMediaPickFromDeviceLibrary( ( mediaId, mediaUri ) => { - if ( mediaUri ) { - setAttributes( { url: mediaUri, id: mediaId } ); - } - } ); - }; - - const onMediaCaptureButtonPressed = () => { - requestMediaPickFromDeviceCamera( ( mediaId, mediaUri ) => { - if ( mediaUri ) { - setAttributes( { url: mediaUri, id: mediaId } ); - } - } ); - }; + const { url, caption, height, width, alt, href, id } = attributes; const onImageSettingsButtonPressed = () => { this.setState( { showSettings: true } ); @@ -246,20 +165,22 @@ class ImageEdit extends React.Component { this.setState( { showSettings: false } ); }; - let picker; - - const onMediaOptionsButtonPressed = () => { - picker.presentPicker(); - }; - const toolbarEditButton = ( - <Toolbar> - <ToolbarButton - title={ __( 'Edit image' ) } - icon="edit" - onClick={ onMediaOptionsButtonPressed } - /> - </Toolbar> + <MediaUpload mediaType={ MEDIA_TYPE_IMAGE } + onSelectURL={ this.onSelectMediaUploadOption } + render={ ( { open, getMediaOptions } ) => { + return ( + <Toolbar> + { getMediaOptions() } + <ToolbarButton + title={ __( 'Edit image' ) } + icon="edit" + onClick={ open } + /> + </Toolbar> + ); + } } > + </MediaUpload> ); const getInspectorControls = () => ( @@ -294,40 +215,17 @@ class ImageEdit extends React.Component { </BottomSheet> ); - const mediaOptions = this.getMediaOptionsItems(); - - const getMediaOptions = () => ( - <Picker - hideCancelButton={ true } - ref={ ( instance ) => picker = instance } - options={ mediaOptions } - onChange={ ( value ) => { - if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) { - onMediaUploadButtonPressed(); - } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO ) { - onMediaCaptureButtonPressed(); - } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) { - onMediaLibraryButtonPressed(); - } - } } - /> - ); - if ( ! url ) { return ( <View style={ { flex: 1 } } > - { getMediaOptions() } <MediaPlaceholder - onMediaOptionsPressed={ onMediaOptionsButtonPressed } + mediaType={ MEDIA_TYPE_IMAGE } + onSelectURL={ this.onSelectMediaUploadOption } /> </View> ); } - const showSpinner = this.state.isUploadInProgress; - const opacity = this.state.isUploadInProgress ? 0.3 : 1; - const progress = this.state.progress * 100; - return ( <TouchableWithoutFeedback accessible={ ! isSelected } @@ -343,7 +241,7 @@ class ImageEdit extends React.Component { disabled={ ! isSelected } > <View style={ { flex: 1 } }> - { showSpinner && <Spinner progress={ progress } /> } + { getInspectorControls() } <BlockControls> { toolbarEditButton } </BlockControls> @@ -354,27 +252,19 @@ class ImageEdit extends React.Component { onClick={ onImageSettingsButtonPressed } /> </InspectorControls> - <ImageSize src={ url } > - { ( sizes ) => { - const { - imageWidthWithinContainer, - imageHeightWithinContainer, - } = sizes; - - let finalHeight = imageHeightWithinContainer; - if ( height > 0 && height < imageHeightWithinContainer ) { - finalHeight = height; - } - - let finalWidth = imageWidthWithinContainer; - if ( width > 0 && width < imageWidthWithinContainer ) { - finalWidth = width; - } - + <MediaUploadProgress + height={ height } + width={ width } + coverUrl={ url } + mediaId={ id } + onUpdateMediaProgress={ this.updateMediaProgress } + onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } + onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onMediaUploadStateReset={ this.mediaUploadStateReset } + renderContent={ ( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryIconName, retryMessage } ) => { + const opacity = isUploadInProgress ? 0.3 : 1; return ( <View style={ { flex: 1 } } > - { getInspectorControls() } - { getMediaOptions() } { ! imageWidthWithinContainer && <View style={ styles.imageContainer } > <Dashicon icon={ 'format-image' } size={ 300 } /> </View> } @@ -386,17 +276,17 @@ class ImageEdit extends React.Component { accessible={ true } accessibilityLabel={ alt } > - { this.state.isUploadFailed && + { isUploadFailed && <View style={ styles.imageContainer } > - <Dashicon icon={ 'image-rotate' } ariaPressed={ 'dashicon-active' } /> - <Text style={ styles.uploadFailedText }>{ __( 'Failed to insert media.\nPlease tap for options.' ) }</Text> + <Dashicon icon={ retryIconName } ariaPressed={ 'dashicon-active' } /> + <Text style={ styles.uploadFailedText }>{ retryMessage }</Text> </View> } </ImageBackground> </View> ); } } - </ImageSize> + /> { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( <View style={ { padding: 12, flex: 1 } } diff --git a/packages/block-library/src/image/media-upload-progress.native.js b/packages/block-library/src/image/media-upload-progress.native.js new file mode 100644 index 00000000000000..6abf295339cc42 --- /dev/null +++ b/packages/block-library/src/image/media-upload-progress.native.js @@ -0,0 +1,167 @@ +/** + * External dependencies + */ +import React from 'react'; +import { View } from 'react-native'; +import { + subscribeMediaUpload, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { + Spinner, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import ImageSize from './image-size'; + +const MEDIA_UPLOAD_STATE_UPLOADING = 1; +const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; +const MEDIA_UPLOAD_STATE_FAILED = 3; +const MEDIA_UPLOAD_STATE_RESET = 4; + +class MediaUploadProgress extends React.Component { + constructor( props ) { + super( props ); + + this.state = { + progress: 0, + isUploadInProgress: false, + isUploadFailed: false, + }; + + this.mediaUpload = this.mediaUpload.bind( this ); + } + + componentDidMount() { + this.addMediaUploadListener(); + } + + componentWillUnmount() { + this.removeMediaUploadListener(); + } + + mediaUpload( payload ) { + const { mediaId } = this.props; + + if ( payload.mediaId !== mediaId ) { + return; + } + + switch ( payload.state ) { + case MEDIA_UPLOAD_STATE_UPLOADING: + this.updateMediaProgress( payload ); + break; + case MEDIA_UPLOAD_STATE_SUCCEEDED: + this.finishMediaUploadWithSuccess( payload ); + break; + case MEDIA_UPLOAD_STATE_FAILED: + this.finishMediaUploadWithFailure( payload ); + break; + case MEDIA_UPLOAD_STATE_RESET: + this.mediaUploadStateReset(); + break; + } + } + + updateMediaProgress( payload ) { + this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); + if ( this.props.onUpdateMediaProgress ) { + this.props.onUpdateMediaProgress( payload ); + } + } + + finishMediaUploadWithSuccess( payload ) { + this.setState( { isUploadInProgress: false } ); + if ( this.props.onFinishMediaUploadWithSuccess ) { + this.props.onFinishMediaUploadWithSuccess( payload ); + } + } + + finishMediaUploadWithFailure( payload ) { + this.setState( { isUploadInProgress: false, isUploadFailed: true } ); + if ( this.props.onFinishMediaUploadWithFailure ) { + this.props.onFinishMediaUploadWithFailure( payload ); + } + } + + mediaUploadStateReset( ) { + this.setState( { isUploadInProgress: false, isUploadFailed: false } ); + if ( this.props.onMediaUploadStateReset ) { + this.props.onMediaUploadStateReset(); + } + } + + addMediaUploadListener() { + //if we already have a subscription not worth doing it again + if ( this.subscriptionParentMediaUpload ) { + return; + } + this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { + this.mediaUpload( payload ); + } ); + } + + removeMediaUploadListener() { + if ( this.subscriptionParentMediaUpload ) { + this.subscriptionParentMediaUpload.remove(); + } + } + + render() { + const { coverUrl, width, height } = this.props; + const { isUploadInProgress, isUploadFailed } = this.state; + const showSpinner = this.state.isUploadInProgress; + const progress = this.state.progress * 100; + const retryIconName = 'image-rotate'; + const retryMessage = __( 'Failed to insert media.\nPlease tap for options.' ); + + return ( + <View style={ { flex: 1 } }> + { showSpinner && <Spinner progress={ progress } /> } + { coverUrl && + <ImageSize src={ coverUrl } > + { ( sizes ) => { + const { + imageWidthWithinContainer, + imageHeightWithinContainer, + } = sizes; + + let finalHeight = imageHeightWithinContainer; + if ( height > 0 && height < imageHeightWithinContainer ) { + finalHeight = height; + } + + let finalWidth = imageWidthWithinContainer; + if ( width > 0 && width < imageWidthWithinContainer ) { + finalWidth = width; + } + return ( this.props.renderContent( { + isUploadInProgress, + isUploadFailed, + finalWidth, + finalHeight, + imageWidthWithinContainer, + retryIconName, + retryMessage, + } ) ); + } } + </ImageSize> + } + { ! coverUrl && this.props.renderContent( { + isUploadInProgress, + isUploadFailed, + retryIconName, + retryMessage, + } ) } + </View> + ); + } +} + +export default MediaUploadProgress; diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index c4c0449049a067..fbe7ad59bb3b2d 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -109,6 +109,7 @@ export const registerCoreBlocks = () => { missing, more, image, + video, nextpage, separator, list, diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js new file mode 100644 index 00000000000000..4f32732ed61bec --- /dev/null +++ b/packages/block-library/src/video/edit.native.js @@ -0,0 +1,232 @@ +/** + * External dependencies + */ +import React from 'react'; +import { View, TextInput, TouchableWithoutFeedback, Text } from 'react-native'; +/** + * Internal dependencies + */ +import Video from './video-player'; +import { + mediaUploadSync, + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { + Toolbar, + ToolbarButton, + Dashicon, +} from '@wordpress/components'; +import { + MediaPlaceholder, + MediaUpload, + MEDIA_TYPE_VIDEO, + RichText, + BlockControls, + InspectorControls, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { isURL } from '@wordpress/url'; +import { doAction, hasAction } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import styles from '../image/styles.scss'; +import MediaUploadProgress from '../image/media-upload-progress'; +import style from './style.scss'; + +const VIDEO_ASPECT_RATIO = 1.7; + +class VideoEdit extends React.Component { + constructor( props ) { + super( props ); + + this.state = { + showSettings: false, + isMediaRequested: false, + videoContainerHeight: 0, + }; + + this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.updateMediaProgress = this.updateMediaProgress.bind( this ); + this.onVideoPressed = this.onVideoPressed.bind( this ); + this.onVideoContanerLayout = this.onVideoContanerLayout.bind( this ); + } + + componentDidMount() { + const { attributes } = this.props; + if ( attributes.id && attributes.url && ! isURL( attributes.src ) ) { + mediaUploadSync(); + } + } + + componentWillUnmount() { + // this action will only exist if the user pressed the trash button on the block holder + if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { + doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); + } + } + + onVideoPressed() { + const { attributes } = this.props; + + if ( this.state.isUploadInProgress ) { + requestImageUploadCancelDialog( attributes.id ); + } else if ( attributes.id && ! isURL( attributes.src ) ) { + requestImageFailedRetryDialog( attributes.id ); + } + } + + updateMediaProgress( payload ) { + const { setAttributes } = this.props; + if ( payload.mediaUrl ) { + setAttributes( { url: payload.mediaUrl } ); + } + + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); + } + } + + finishMediaUploadWithSuccess( payload ) { + const { setAttributes } = this.props; + setAttributes( { src: payload.mediaUrl, id: payload.mediaServerId } ); + this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + } + + finishMediaUploadWithFailure( payload ) { + const { setAttributes } = this.props; + setAttributes( { id: payload.mediaId } ); + this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + } + + mediaUploadStateReset() { + const { setAttributes } = this.props; + setAttributes( { id: null, src: null } ); + this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + } + + onSelectMediaUploadOption( mediaId, mediaUrl ) { + const { setAttributes } = this.props; + setAttributes( { id: mediaId, src: mediaUrl } ); + this.setState( { isMediaRequested: true } ); + } + + onVideoContanerLayout( event ) { + const { width } = event.nativeEvent.layout; + const height = width / VIDEO_ASPECT_RATIO; + if ( height !== this.state.videoContainerHeight ) { + this.setState( { videoContainerHeight: height } ); + } + } + + render() { + const { attributes, isSelected, setAttributes } = this.props; + const { caption, id, src } = attributes; + const { isMediaRequested, videoContainerHeight } = this.state; + + const toolbarEditButton = ( + <MediaUpload mediaType={ MEDIA_TYPE_VIDEO } + onSelectURL={ this.onSelectMediaUploadOption } + render={ ( { open, getMediaOptions } ) => { + return ( + <Toolbar> + { getMediaOptions() } + <ToolbarButton + label={ __( 'Edit video' ) } + icon="edit" + onClick={ open } + /> + </Toolbar> + ); + } } > + </MediaUpload> + ); + + if ( ! isMediaRequested && ! src ) { + return ( + <View style={ { flex: 1 } } > + <MediaPlaceholder + mediaType={ MEDIA_TYPE_VIDEO } + onSelectURL={ this.onSelectMediaUploadOption } + /> + </View> + ); + } + + return ( + <TouchableWithoutFeedback onPress={ this.onVideoPressed } disabled={ ! isSelected }> + <View style={ { flex: 1 } }> + <BlockControls> + { toolbarEditButton } + </BlockControls> + <InspectorControls> + <ToolbarButton + label={ __( 'Video Settings' ) } + icon="admin-generic" + onClick={ () => ( null ) } + /> + </InspectorControls> + <MediaUploadProgress + mediaId={ id } + onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } + onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onUpdateMediaProgress={ this.updateMediaProgress } + onMediaUploadStateReset={ this.mediaUploadStateReset } + renderContent={ ( { isUploadInProgress, isUploadFailed, retryIconName, retryMessage } ) => { + const opacity = ( isUploadInProgress || isUploadFailed ) ? 0.3 : 1; + const showVideo = src && ! isUploadInProgress && ! isUploadFailed; + const iconName = isUploadFailed ? retryIconName : 'format-video'; + + const videoStyle = { + height: videoContainerHeight, + ...style.video, + }; + + return ( + <View onLayout={ this.onVideoContanerLayout } style={ { flex: 1 } }> + { showVideo && + <Video + source={ { uri: src } } + style={ videoStyle } + paused={ true } + muted={ true } + /> + } + { ! showVideo && + <View style={ { ...videoStyle, ...style.placeholder, opacity } }> + { videoContainerHeight > 0 && <Dashicon icon={ iconName } size={ 80 } style={ style.placeholderIcon } /> } + { isUploadFailed && <Text style={ style.uploadFailedText }>{ retryMessage }</Text> } + </View> + } + </View> + ); + } } + /> + { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( + <View style={ { padding: 12, flex: 1 } }> + <TextInput + style={ { textAlign: 'center' } } + fontFamily={ this.props.fontFamily || ( styles[ 'caption-text' ].fontFamily ) } + underlineColorAndroid="transparent" + value={ caption } + placeholder={ __( 'Write caption…' ) } + onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + /> + </View> + ) } + </View> + </TouchableWithoutFeedback> + ); + } +} + +export default VideoEdit; diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss new file mode 100644 index 00000000000000..4d0c557203f15f --- /dev/null +++ b/packages/block-library/src/video/style.native.scss @@ -0,0 +1,25 @@ +// @format + +.video { + background-color: #e9eff3; + width: 100%; +} + +.placeholder { + flex: 1; + justify-content: center; + align-items: center; +} + +.placeholderIcon { + justify-content: center; + align-items: center; + fill: $gray-dark; + background-color: #e9eff3; +} + +.uploadFailedText { + color: $gray-dark; + font-size: 14; + margin-top: 5; +} diff --git a/packages/block-library/src/video/video-player.android.js b/packages/block-library/src/video/video-player.android.js new file mode 100644 index 00000000000000..a9dcd4cbdf52ea --- /dev/null +++ b/packages/block-library/src/video/video-player.android.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +import { default as VideoPlayer } from 'react-native-video'; + +/** + * Internal dependencies + */ +import styles from './video-player.scss'; + +const Video = ( props ) => { + return ( + <View style={ styles.videoContainer }> + <VideoPlayer + { ...props } + // We are using built-in player controls becasue manually + // calling presentFullscreenPlayer() is not working for android + controls={ true } + /> + </View> + ); +}; + +export default Video; diff --git a/packages/block-library/src/video/video-player.ios.js b/packages/block-library/src/video/video-player.ios.js new file mode 100644 index 00000000000000..47ea7f7cfc0b7e --- /dev/null +++ b/packages/block-library/src/video/video-player.ios.js @@ -0,0 +1,73 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { Dashicon } from '@wordpress/components'; + +/** + * External dependencies + */ +import { View, TouchableOpacity } from 'react-native'; +import { default as VideoPlayer } from 'react-native-video'; + +/** + * Internal dependencies + */ +import styles from './video-player.scss'; + +class Video extends Component { + constructor() { + super( ...arguments ); + this.state = { + isLoaded: false, + }; + this.onPressPlay = this.onPressPlay.bind( this ); + this.onLoad = this.onLoad.bind( this ); + this.onLoadStart = this.onLoadStart.bind( this ); + } + + onLoad() { + this.setState( { isLoaded: true } ); + } + + onLoadStart() { + this.setState( { isLoaded: false } ); + } + + onPressPlay() { + if ( this.player ) { + this.player.presentFullscreenPlayer(); + } + } + + render() { + const { style } = this.props; + const { isLoaded } = this.state; + + return ( + <View style={ styles.videoContainer }> + <VideoPlayer + { ...this.props } + ref={ ( ref ) => { + this.player = ref; + } } + // Using built-in player controls is messing up the layout on iOS. + // So we are setting controls=false and adding a play button that + // will trigger presentFullscreenPlayer() + controls={ false } + onLoad={ this.onLoad } + onLoadStart={ this.onLoadStart } + /> + { isLoaded && + <TouchableOpacity onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> + <View style={ styles.playIcon }> + <Dashicon icon={ 'controls-play' } ariaPressed={ 'dashicon-active' } size={ styles.playIcon.width } /> + </View> + </TouchableOpacity> + } + </View> + ); + } +} + +export default Video; diff --git a/packages/block-library/src/video/video-player.native.scss b/packages/block-library/src/video/video-player.native.scss new file mode 100644 index 00000000000000..ec746de8288916 --- /dev/null +++ b/packages/block-library/src/video/video-player.native.scss @@ -0,0 +1,32 @@ +// @format + +$play-icon-size: 50; + +.videoContainer { + flex: 1; + justify-content: center; + align-items: center; +} + +.overlay { + justify-content: center; + align-items: center; + align-self: center; + position: absolute; + background-color: #e9eff3; + opacity: 0.3; +} + +.playIcon { + justify-content: center; + align-items: center; + align-self: center; + position: absolute; + background-color: #000; + height: $play-icon-size; + width: $play-icon-size; + border-bottom-left-radius: $play-icon-size/8; + border-bottom-right-radius: $play-icon-size/8; + border-top-right-radius: $play-icon-size/8; + border-top-left-radius: $play-icon-size/8; +} diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 6ada9eb995eadd..a1afa49808b038 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -23,6 +23,7 @@ export function initializeEditor() { if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); unregisterBlockType( 'core/more' ); + unregisterBlockType( 'core/video' ); } } From d77c70a3769876f7e76396c2c45cc70d099711b8 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Sun, 12 May 2019 04:06:33 -0700 Subject: [PATCH 076/664] Update i18n docs to use make-json command from wp-cli (#15303) * Update i18m docs to use make-json command from wp-cli * Apply suggestions from code review Co-Authored-By: Pascal Birchler <pascal.birchler@gmail.com> --- .../developers/internationalization.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 2ae1b8f8777f38..084459697cff3b 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -176,31 +176,32 @@ msgid "Hello World" msgstr "Saltuon mundo" ``` -The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use the [po2json utility](https://github.com/mikeedwards/po2json) which you install using npm. It might be easiest to install globally using: `npm install -g po2json`. Once installed, use the following command to convert to JED format: +The last step to create the translation file is to convert the `myguten-eo.po` to the JSON format needed. For this, you can use WP-CLI's [`wp i18n make-json` command](https://developer.wordpress.org/cli/commands/i18n/make-json/), which requires WP-CLI v2.2.0 and later. ``` -po2json myguten-eo.po myguten-eo.json -f jed +wp i18n make-json myguten-eo.po --no-purge ``` -This will generate the JSON file `myguten-eo.json` which looks like: +This will generate the JSON file `myguten-eo-[md5].json` with the contents: ```json { + "translation-revision-date": "2019-04-26T13:30:11-07:00", + "generator": "WP-CLI/2.2.0", + "source": "block.js", "domain": "messages", "locale_data": { "messages": { "": { "domain": "messages", - "lang": "eo" + "lang": "eo", + "plural-forms": "nplurals=2; plural=(n != 1);" }, - "Scratch Plugin": [ - "Scratch kromprogrameto" - ], "Simple Block": [ - "Simpla bloko" + "Simpla Bloko" ], "Hello World": [ - "Saltuon mundo" + "Salunton mondo" ] } } @@ -222,7 +223,7 @@ The final part is to tell WordPress where it can look to find the translation fi WordPress will check for a file in that path with the format `${domain}-${locale}-${handle}.json` as the source of translations. Alternatively, instead of the registered handle you can use the md5 hash of the relative path of the file, `${domain}-${locale} in the form of ${domain}-${locale}-${md5}.json.` -This example uses the handle, rename the `myguten-eo.json` file to `myguten-eo-myguten-script.json`. +Using `make-json` automatically names the file with the md5 hash, so it is ready as-is. You could rename the file to use the handle instead, in which case the file name would be `myguten-eo-myguten-script.json`. ### Test Translations From bd1c61642c0ce0d0c69d80088876e53df30a2e98 Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <nicolai.vadim@gmail.com> Date: Sun, 12 May 2019 14:10:07 +0300 Subject: [PATCH 077/664] Accessibility: Fixed focus state of pressed AM/PM buttons. (#15582) --- packages/components/src/date-time/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index e9c84135c0a320..3b0609258f88ca 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -110,6 +110,12 @@ background: $light-gray-300; border-color: $dark-gray-100; box-shadow: inset 0 2px 5px -3px $dark-gray-500; + &:focus { + box-shadow: + inset 0 2px 5px -3px $dark-gray-500, + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; + } } .components-datetime__time-field { From 5494bc57fc77de6cada4295823a3fb047e168f89 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Sun, 12 May 2019 14:47:55 -0400 Subject: [PATCH 078/664] Block Library: Add width attribute for resizable Column blocks (#15499) * Block Library: Render Column block with ButtonBlockAppender * Block Library: Add width attribute for resizable Column blocks * Block Library: Update Column width input label to clarify percentage * Block Library: Refactor Columns width redistribution to reusable utilities * Plugin: Filter safe CSS to allow column flex-basis CSS attribute * Block Library: Return undefined for non-finite column width precision * Components: Fix display of RangeControl reset button * Block Library: Allow column block width resetting * Block Library: Ensure Column width RangeControl treated as controlled input * Testing: Update E2E tests for button inserter columns block * Testing: Try to appease the E2E gods * Block Library: Restore flex-basis for mid-range viewports --- lib/compat.php | 26 ++ lib/load.php | 1 + packages/block-library/src/column/block.json | 5 + packages/block-library/src/column/edit.js | 115 +++++++-- packages/block-library/src/column/index.js | 10 + packages/block-library/src/column/save.js | 10 +- packages/block-library/src/columns/edit.js | 145 +++++++---- .../block-library/src/columns/editor.scss | 11 - packages/block-library/src/columns/style.scss | 9 +- .../block-library/src/columns/test/utils.js | 229 ++++++++++++++++++ packages/block-library/src/columns/utils.js | 128 +++++++++- packages/components/CHANGELOG.md | 8 +- .../components/src/range-control/index.js | 12 +- .../components/src/range-control/style.scss | 4 + .../specs/block-hierarchy-navigation.test.js | 22 +- packages/e2e-tests/specs/writing-flow.test.js | 23 +- 16 files changed, 658 insertions(+), 100 deletions(-) create mode 100644 lib/compat.php create mode 100644 packages/block-library/src/columns/test/utils.js diff --git a/lib/compat.php b/lib/compat.php new file mode 100644 index 00000000000000..fb09a6e49ef97f --- /dev/null +++ b/lib/compat.php @@ -0,0 +1,26 @@ +<?php +/** + * Temporary compatibility shims for features present in Gutenberg, pending + * upstream commit to the WordPress core source repository. Functions here + * exist only as long as necessary for corresponding WordPress support, and + * each should be associated with a Trac ticket. + * + * @package gutenberg + */ + +/** + * Filters allowed CSS attributes to include `flex-basis`, included in saved + * markup of the Column block. + * + * @since 5.7.0 + * + * @param string[] $attr Array of allowed CSS attributes. + * + * @return string[] Filtered array of allowed CSS attributes. + */ +function gutenberg_safe_style_css_column_flex_basis( $attr ) { + $attr[] = 'flex-basis'; + + return $attr; +} +add_filter( 'safe_style_css', 'gutenberg_safe_style_css_column_flex_basis' ); diff --git a/lib/load.php b/lib/load.php index a291cd965f053a..9f845d95a8bf9d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -24,6 +24,7 @@ require dirname( __FILE__ ) . '/rest-api.php'; } +require dirname( __FILE__ ) . '/compat.php'; require dirname( __FILE__ ) . '/blocks.php'; require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/demo.php'; diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json index 9fc52d8e47f909..1d9b3917310344 100644 --- a/packages/block-library/src/column/block.json +++ b/packages/block-library/src/column/block.json @@ -4,6 +4,11 @@ "attributes": { "verticalAlignment": { "type": "string" + }, + "width": { + "type": "number", + "min": 0, + "max": 100 } } } diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index 147dbc2b4c8b67..2587229664bfe5 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -2,55 +2,132 @@ * External dependencies */ import classnames from 'classnames'; +import { forEach, find, difference } from 'lodash'; /** * WordPress dependencies */ -import { InnerBlocks, BlockControls, BlockVerticalAlignmentToolbar } from '@wordpress/block-editor'; +import { + InnerBlocks, + BlockControls, + BlockVerticalAlignmentToolbar, + InspectorControls, +} from '@wordpress/block-editor'; +import { PanelBody, RangeControl } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; -const ColumnEdit = ( { attributes, updateAlignment } ) => { - const { verticalAlignment } = attributes; +/** + * Internal dependencies + */ +import { + toWidthPrecision, + getTotalColumnsWidth, + getColumnWidths, + getAdjacentBlocks, + getRedistributedColumnWidths, +} from '../columns/utils'; + +function ColumnEdit( { + attributes, + updateAlignment, + updateWidth, + hasChildBlocks, +} ) { + const { verticalAlignment, width } = attributes; const classes = classnames( 'block-core-columns', { [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); - const onChange = ( alignment ) => updateAlignment( alignment ); - return ( <div className={ classes }> <BlockControls> <BlockVerticalAlignmentToolbar - onChange={ onChange } + onChange={ updateAlignment } value={ verticalAlignment } /> </BlockControls> - <InnerBlocks templateLock={ false } /> + <InspectorControls> + <PanelBody title={ __( 'Column Settings' ) }> + <RangeControl + label={ __( 'Percentage width' ) } + value={ width || '' } + onChange={ updateWidth } + min={ 0 } + max={ 100 } + required + allowReset + /> + </PanelBody> + </InspectorControls> + <InnerBlocks + templateLock={ false } + renderAppender={ ( + hasChildBlocks ? + undefined : + () => <InnerBlocks.ButtonBlockAppender /> + ) } + /> </div> ); -}; +} export default compose( - withSelect( ( select, { clientId } ) => { - const { getBlockRootClientId } = select( 'core/editor' ); + withSelect( ( select, ownProps ) => { + const { clientId } = ownProps; + const { getBlockOrder } = select( 'core/block-editor' ); return { - parentColumnsBlockClientId: getBlockRootClientId( clientId ), + hasChildBlocks: getBlockOrder( clientId ).length > 0, }; } ), - withDispatch( ( dispatch, { clientId, parentColumnsBlockClientId } ) => { + withDispatch( ( dispatch, ownProps, registry ) => { return { - updateAlignment( alignment ) { - // Update self... - dispatch( 'core/editor' ).updateBlockAttributes( clientId, { - verticalAlignment: alignment, - } ); + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockRootClientId } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); // Reset Parent Columns Block - dispatch( 'core/editor' ).updateBlockAttributes( parentColumnsBlockClientId, { - verticalAlignment: null, + const rootClientId = getBlockRootClientId( clientId ); + updateBlockAttributes( rootClientId, { verticalAlignment: null } ); + }, + updateWidth( width ) { + const { clientId } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockRootClientId, getBlocks } = registry.select( 'core/block-editor' ); + + // Constrain or expand siblings to account for gain or loss of + // total columns area. + const columns = getBlocks( getBlockRootClientId( clientId ) ); + const adjacentColumns = getAdjacentBlocks( columns, clientId ); + + // The occupied width is calculated as the sum of the new width + // and the total width of blocks _not_ in the adjacent set. + const occupiedWidth = width + getTotalColumnsWidth( + difference( columns, [ + find( columns, { clientId } ), + ...adjacentColumns, + ] ) + ); + + // Compute _all_ next column widths, in case the updated column + // is in the middle of a set of columns which don't yet have + // any explicit widths assigned (include updates to those not + // part of the adjacent blocks). + const nextColumnWidths = { + ...getColumnWidths( columns, columns.length ), + [ clientId ]: toWidthPrecision( width ), + ...getRedistributedColumnWidths( adjacentColumns, 100 - occupiedWidth, columns.length ), + }; + + forEach( nextColumnWidths, ( nextColumnWidth, columnClientId ) => { + updateBlockAttributes( columnClientId, { width: nextColumnWidth } ); } ); }, }; diff --git a/packages/block-library/src/column/index.js b/packages/block-library/src/column/index.js index 869093459a83bb..250ce0bad65a42 100644 --- a/packages/block-library/src/column/index.js +++ b/packages/block-library/src/column/index.js @@ -25,6 +25,16 @@ export const settings = { reusable: false, html: false, }, + getEditWrapperProps( attributes ) { + const { width } = attributes; + if ( Number.isFinite( width ) ) { + return { + style: { + flexBasis: width + '%', + }, + }; + } + }, edit, save, }; diff --git a/packages/block-library/src/column/save.js b/packages/block-library/src/column/save.js index 9e8ea1ac4a3b32..d0dda9de3174b3 100644 --- a/packages/block-library/src/column/save.js +++ b/packages/block-library/src/column/save.js @@ -9,13 +9,19 @@ import classnames from 'classnames'; import { InnerBlocks } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { verticalAlignment } = attributes; + const { verticalAlignment, width } = attributes; + const wrapperClasses = classnames( { [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); + let style; + if ( Number.isFinite( width ) ) { + style = { flexBasis: width + '%' }; + } + return ( - <div className={ wrapperClasses }> + <div className={ wrapperClasses } style={ style }> <InnerBlocks.Content /> </div> ); diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 15adfff384dfff..71625625233815 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -2,12 +2,12 @@ * External dependencies */ import classnames from 'classnames'; +import { dropRight } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { compose } from '@wordpress/compose'; import { PanelBody, RangeControl, @@ -18,12 +18,19 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { withDispatch } from '@wordpress/data'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import { getColumnsTemplate } from './utils'; +import { + getColumnsTemplate, + hasExplicitColumnWidths, + getMappedColumnWidths, + getRedistributedColumnWidths, + toWidthPrecision, +} from './utils'; /** * Allowed blocks constant is passed to InnerBlocks precisely as specified here. @@ -36,18 +43,18 @@ import { getColumnsTemplate } from './utils'; */ const ALLOWED_BLOCKS = [ 'core/column' ]; -export const ColumnsEdit = function( { attributes, setAttributes, className, updateAlignment } ) { +export function ColumnsEdit( { + attributes, + className, + updateAlignment, + updateColumns, +} ) { const { columns, verticalAlignment } = attributes; const classes = classnames( className, `has-${ columns }-columns`, { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); - const onChange = ( alignment ) => { - // Update all the (immediate) child Column Blocks - updateAlignment( alignment ); - }; - return ( <> <InspectorControls> @@ -55,11 +62,7 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd <RangeControl label={ __( 'Columns' ) } value={ columns } - onChange={ ( nextColumns ) => { - setAttributes( { - columns: nextColumns, - } ); - } } + onChange={ updateColumns } min={ 2 } max={ 6 } /> @@ -67,7 +70,7 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd </InspectorControls> <BlockControls> <BlockVerticalAlignmentToolbar - onChange={ onChange } + onChange={ updateAlignment } value={ verticalAlignment } /> </BlockControls> @@ -79,45 +82,81 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd </div> </> ); -}; +} + +export default withDispatch( ( dispatch, ownProps, registry ) => ( { + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} verticalAlignment the vertical alignment setting + */ + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockOrder } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); -const DEFAULT_EMPTY_ARRAY = []; + // Update all child Column Blocks to match + const innerBlockClientIds = getBlockOrder( clientId ); + innerBlockClientIds.forEach( ( innerBlockClientId ) => { + updateBlockAttributes( innerBlockClientId, { + verticalAlignment, + } ); + } ); + }, -export default compose( /** - * Selects the child column Blocks for this parent Column + * Updates the column count, including necessary revisions to child Column + * blocks to grant required or redistribute available space. + * + * @param {number} columns New column count. */ - withSelect( ( select, { clientId } ) => { - const { getBlocksByClientId } = select( 'core/editor' ); - const block = getBlocksByClientId( clientId )[ 0 ]; - - return { - childColumns: block ? block.innerBlocks : DEFAULT_EMPTY_ARRAY, - }; - } ), - withDispatch( ( dispatch, { clientId, childColumns } ) => { - return { - /** - * Update all child column Blocks with a new - * vertical alignment setting based on whatever - * alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis - * - * @param {string} alignment the vertical alignment setting - */ - updateAlignment( alignment ) { - // Update self... - dispatch( 'core/editor' ).updateBlockAttributes( clientId, { - verticalAlignment: alignment, - } ); - - // Update all child Column Blocks to match - childColumns.forEach( ( childColumn ) => { - dispatch( 'core/editor' ).updateBlockAttributes( childColumn.clientId, { - verticalAlignment: alignment, - } ); - } ); - }, - }; - } ), -)( ColumnsEdit ); + updateColumns( columns ) { + const { clientId, setAttributes, attributes } = ownProps; + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + // Update columns count. + setAttributes( { columns } ); + + let innerBlocks = getBlocks( clientId ); + if ( ! hasExplicitColumnWidths( innerBlocks ) ) { + return; + } + + // Redistribute available width for existing inner blocks. + const { columns: previousColumns } = attributes; + const isAddingColumn = columns > previousColumns; + + if ( isAddingColumn ) { + // If adding a new column, assign width to the new column equal to + // as if it were `1 / columns` of the total available space. + const newColumnWidth = toWidthPrecision( 100 / columns ); + + // Redistribute in consideration of pending block insertion as + // constraining the available working width. + const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth ); + + innerBlocks = [ + ...getMappedColumnWidths( innerBlocks, widths ), + createBlock( 'core/column', { + width: newColumnWidth, + } ), + ]; + } else { + // The removed column will be the last of the inner blocks. + innerBlocks = dropRight( innerBlocks ); + + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( innerBlocks, 100 ); + + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } + + replaceInnerBlocks( clientId, innerBlocks, false ); + }, +} ) )( ColumnsEdit ); diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index 7830f7780880a9..c771c79f5f9d3c 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -178,14 +178,3 @@ div.block-core-columns.is-vertically-aligned-bottom { display: none; } } - -// In absence of making the individual columns resizable, we prevent them from being clickable. -// This makes them less fiddly. @todo: This should be revisited as the interface is refined. -.wp-block-columns [data-type="core/column"] { - pointer-events: none; - - // This selector re-enables clicking on any child of a column block. - .block-core-columns .block-editor-block-list__layout { - pointer-events: all; - } -} diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss index ab2ec375ca1e8e..7a2c0877889332 100644 --- a/packages/block-library/src/columns/style.scss +++ b/packages/block-library/src/columns/style.scss @@ -14,8 +14,11 @@ margin-bottom: 1em; flex-grow: 1; - // Responsiveness: Show at most one columns on mobile. - flex-basis: 100%; + @media (max-width: #{ ($break-small - 1) }) { + // Responsiveness: Show at most one columns on mobile. This must be + // important since the Column assigns its own width as an inline style. + flex-basis: 100% !important; + } // Prevent the columns from growing wider than their distributed sizes. min-width: 0; @@ -30,7 +33,7 @@ flex-basis: calc(50% - #{$grid-size-large}); flex-grow: 0; - // Add space between the 2 columns. Themes can customize this if they wish to work differently. + // Add space between the multiple columns. Themes can customize this if they wish to work differently. // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. &:nth-child(even) { margin-left: $grid-size-large * 2; diff --git a/packages/block-library/src/columns/test/utils.js b/packages/block-library/src/columns/test/utils.js new file mode 100644 index 00000000000000..cb69e1740e1f32 --- /dev/null +++ b/packages/block-library/src/columns/test/utils.js @@ -0,0 +1,229 @@ +/** + * Internal dependencies + */ +import { + getColumnsTemplate, + toWidthPrecision, + getAdjacentBlocks, + getEffectiveColumnWidth, + getTotalColumnsWidth, + getColumnWidths, + getRedistributedColumnWidths, + hasExplicitColumnWidths, + getMappedColumnWidths, +} from '../utils'; + +describe( 'getColumnsTemplate', () => { + it( 'should return a template corresponding to columns count', () => { + const template = getColumnsTemplate( 4 ); + + expect( template ).toEqual( [ + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + ] ); + } ); +} ); + +describe( 'toWidthPrecision', () => { + it( 'should round value to standard precision', () => { + const value = toWidthPrecision( 50.108 ); + + expect( value ).toBe( 50.11 ); + } ); + + it( 'should return undefined for invalid number', () => { + expect( toWidthPrecision( null ) ).toBe( undefined ); + expect( toWidthPrecision( undefined ) ).toBe( undefined ); + } ); +} ); + +describe( 'getAdjacentBlocks', () => { + const blockA = { clientId: 'a' }; + const blockB = { clientId: 'b' }; + const blockC = { clientId: 'c' }; + const blocks = [ blockA, blockB, blockC ]; + + it( 'should return blocks after clientId', () => { + const result = getAdjacentBlocks( blocks, 'b' ); + + expect( result ).toEqual( [ blockC ] ); + } ); + + it( 'should return blocks before clientId if clientId is last', () => { + const result = getAdjacentBlocks( blocks, 'c' ); + + expect( result ).toEqual( [ blockA, blockB ] ); + } ); +} ); + +describe( 'getEffectiveColumnWidth', () => { + it( 'should return attribute value if set, rounded to precision', () => { + const block = { attributes: { width: 50.108 } }; + + const width = getEffectiveColumnWidth( block, 3 ); + + expect( width ).toBe( 50.11 ); + } ); + + it( 'should return assumed width if attribute value not set, rounded to precision', () => { + const block = { attributes: {} }; + + const width = getEffectiveColumnWidth( block, 3 ); + + expect( width ).toBe( 33.33 ); + } ); +} ); + +describe( 'getTotalColumnsWidth', () => { + describe( 'explicit width', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30 } }, + { clientId: 'b', attributes: { width: 40 } }, + ]; + + it( 'returns the sum total of columns width', () => { + const width = getTotalColumnsWidth( blocks ); + + expect( width ).toBe( 70 ); + } ); + } ); + + describe( 'implicit width', () => { + const blocks = [ + { clientId: 'a', attributes: {} }, + { clientId: 'b', attributes: {} }, + ]; + + it( 'returns the sum total of columns width', () => { + const widths = getTotalColumnsWidth( blocks ); + + expect( widths ).toBe( 100 ); + } ); + } ); +} ); + +describe( 'getColumnWidths', () => { + describe( 'explicit width', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30.459 } }, + { clientId: 'b', attributes: { width: 29.543 } }, + ]; + + it( 'returns the column widths', () => { + const widths = getColumnWidths( blocks ); + + expect( widths ).toEqual( { + a: 30.46, + b: 29.54, + } ); + } ); + } ); + + describe( 'implicit width', () => { + const blocks = [ + { clientId: 'a', attributes: {} }, + { clientId: 'b', attributes: {} }, + ]; + + it( 'returns the column widths', () => { + const widths = getColumnWidths( blocks ); + + expect( widths ).toEqual( { + a: 50, + b: 50, + } ); + } ); + } ); +} ); + +describe( 'getRedistributedColumnWidths', () => { + describe( 'explicit width', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30 } }, + { clientId: 'b', attributes: { width: 40 } }, + ]; + + it( 'should constrain to fit available width', () => { + const widths = getRedistributedColumnWidths( blocks, 60 ); + + expect( widths ).toEqual( { + a: 25, + b: 35, + } ); + } ); + + it( 'should expand to fit available width', () => { + const widths = getRedistributedColumnWidths( blocks, 80 ); + + expect( widths ).toEqual( { + a: 35, + b: 45, + } ); + } ); + } ); + + describe( 'implicit width', () => { + const blocks = [ + { clientId: 'a', attributes: {} }, + { clientId: 'b', attributes: {} }, + ]; + + it( 'should equally distribute to available width', () => { + const widths = getRedistributedColumnWidths( blocks, 60 ); + + expect( widths ).toEqual( { + a: 30, + b: 30, + } ); + } ); + + it( 'should constrain to fit available width', () => { + const widths = getRedistributedColumnWidths( blocks, 66.66, 3 ); + + expect( widths ).toEqual( { + a: 33.33, + b: 33.33, + } ); + } ); + } ); +} ); + +describe( 'hasExplicitColumnWidths', () => { + it( 'returns false if no blocks have explicit width', () => { + const blocks = [ { attributes: {} } ]; + + const result = hasExplicitColumnWidths( blocks ); + + expect( result ).toBe( false ); + } ); + + it( 'returns true if a block has explicit width', () => { + const blocks = [ { attributes: { width: 10 } } ]; + + const result = hasExplicitColumnWidths( blocks ); + + expect( result ).toBe( true ); + } ); +} ); + +describe( 'getMappedColumnWidths', () => { + it( 'merges to block attributes using provided widths', () => { + const blocks = [ + { clientId: 'a', attributes: { width: 30 } }, + { clientId: 'b', attributes: { width: 40 } }, + ]; + const widths = { + a: 25, + b: 35, + }; + + const result = getMappedColumnWidths( blocks, widths ); + + expect( result ).toEqual( [ + { clientId: 'a', attributes: { width: 25 } }, + { clientId: 'b', attributes: { width: 35 } }, + ] ); + } ); +} ); diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index e7e3f90df70fd2..0c4e4c59e9ccd3 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -2,7 +2,7 @@ * External dependencies */ import memoize from 'memize'; -import { times } from 'lodash'; +import { times, findIndex, sumBy, merge, mapValues } from 'lodash'; /** * Returns the layouts configuration for a given number of columns. @@ -14,3 +14,129 @@ import { times } from 'lodash'; export const getColumnsTemplate = memoize( ( columns ) => { return times( columns, () => [ 'core/column' ] ); } ); + +/** + * Returns a column width attribute value rounded to standard precision. + * Returns `undefined` if the value is not a valid finite number. + * + * @param {?number} value Raw value. + * + * @return {number} Value rounded to standard precision. + */ +export const toWidthPrecision = ( value ) => + Number.isFinite( value ) ? + parseFloat( value.toFixed( 2 ) ) : + undefined; + +/** + * Returns the considered adjacent to that of the specified `clientId` for + * resizing consideration. Adjacent blocks are those occurring after, except + * when the given block is the last block in the set. For the last block, the + * behavior is reversed. + * + * @param {WPBlock[]} blocks Block objects. + * @param {string} clientId Client ID to consider for adjacent blocks. + * + * @return {WPBlock[]} Adjacent block objects. + */ +export function getAdjacentBlocks( blocks, clientId ) { + const index = findIndex( blocks, { clientId } ); + const isLastBlock = index === blocks.length - 1; + + return isLastBlock ? blocks.slice( 0, index ) : blocks.slice( index + 1 ); +} + +/** + * Returns an effective width for a given block. An effective width is equal to + * its attribute value if set, or a computed value assuming equal distribution. + * + * @param {WPBlock} block Block object. + * @param {number} totalBlockCount Total number of blocks in Columns. + * + * @return {number} Effective column width. + */ +export function getEffectiveColumnWidth( block, totalBlockCount ) { + const { width = 100 / totalBlockCount } = block.attributes; + return toWidthPrecision( width ); +} + +/** + * Returns the total width occupied by the given set of column blocks. + * + * @param {WPBlock[]} blocks Block objects. + * @param {?number} totalBlockCount Total number of blocks in Columns. + * Defaults to number of blocks passed. + * + * @return {number} Total width occupied by blocks. + */ +export function getTotalColumnsWidth( blocks, totalBlockCount = blocks.length ) { + return sumBy( blocks, ( block ) => getEffectiveColumnWidth( block, totalBlockCount ) ); +} + +/** + * Returns an object of `clientId` → `width` of effective column widths. + * + * @param {WPBlock[]} blocks Block objects. + * @param {?number} totalBlockCount Total number of blocks in Columns. + * Defaults to number of blocks passed. + * + * @return {Object<string,number>} Column widths. + */ +export function getColumnWidths( blocks, totalBlockCount = blocks.length ) { + return blocks.reduce( ( result, block ) => { + const width = getEffectiveColumnWidth( block, totalBlockCount ); + return Object.assign( result, { [ block.clientId ]: width } ); + }, {} ); +} + +/** + * Returns an object of `clientId` → `width` of column widths as redistributed + * proportional to their current widths, constrained or expanded to fit within + * the given available width. + * + * @param {WPBlock[]} blocks Block objects. + * @param {number} availableWidth Maximum width to fit within. + * @param {?number} totalBlockCount Total number of blocks in Columns. + * Defaults to number of blocks passed. + * + * @return {Object<string,number>} Redistributed column widths. + */ +export function getRedistributedColumnWidths( blocks, availableWidth, totalBlockCount = blocks.length ) { + const totalWidth = getTotalColumnsWidth( blocks, totalBlockCount ); + const difference = availableWidth - totalWidth; + const adjustment = difference / blocks.length; + + return mapValues( + getColumnWidths( blocks, totalBlockCount ), + ( width ) => toWidthPrecision( width + adjustment ), + ); +} + +/** + * Returns true if column blocks within the provided set are assigned with + * explicit widths, or false otherwise. + * + * @param {WPBlock[]} blocks Block objects. + * + * @return {boolean} Whether columns have explicit widths. + */ +export function hasExplicitColumnWidths( blocks ) { + return blocks.some( ( block ) => Number.isFinite( block.attributes.width ) ); +} + +/** + * Returns a copy of the given set of blocks with new widths assigned from the + * provided object of redistributed column widths. + * + * @param {WPBlock[]} blocks Block objects. + * @param {Object<string,number>} widths Redistributed column widths. + * + * @return {WPBlock[]} blocks Mapped block objects. + */ +export function getMappedColumnWidths( blocks, widths ) { + return blocks.map( ( block ) => merge( {}, block, { + attributes: { + width: widths[ block.clientId ], + }, + } ) ); +} diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 97028d2638a19d..abde63ef28198d 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,7 +1,13 @@ -## 7.4.0 (Unreleased) +## Master + +### New Features - Added a new `HorizontalRule` component. +### Bug Fixes + +- Fixed display of reset button when using RangeControl `allowReset` prop. + ## 7.3.0 (2019-04-16) ### New Features diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index 2fd28d31bcae13..3b33f2b499c34b 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -98,11 +98,17 @@ function RangeControl( { onBlur={ resetCurrentInput } { ...props } /> - { allowReset && - <Button onClick={ resetValue } disabled={ value === undefined }> + { allowReset && ( + <Button + onClick={ resetValue } + disabled={ value === undefined } + isSmall + isDefault + className="components-range-control__reset" + > { __( 'Reset' ) } </Button> - } + ) } </BaseControl> ); } diff --git a/packages/components/src/range-control/style.scss b/packages/components/src/range-control/style.scss index e3dfca023ca837..b67e503592650e 100644 --- a/packages/components/src/range-control/style.scss +++ b/packages/components/src/range-control/style.scss @@ -22,6 +22,10 @@ } } +.components-range-control__reset { + margin-left: $grid-size; +} + // creating mixin because we can't do multiline variables, and we can't comma-group the selectors for styling the range slider @mixin range-thumb() { height: 18px; diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 9153526a262a8f..77d424d7e6381c 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -22,6 +22,11 @@ describe( 'Navigating the block hierarchy', () => { await insertBlock( 'Columns' ); // Add a paragraph in the first column. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First column' ); // Navigate to the columns blocks. @@ -44,7 +49,11 @@ describe( 'Navigating the block hierarchy', () => { await lastColumnsBlockMenuItem.click(); // Insert text in the last column block. - await pressKeyTimes( 'Tab', 5 ); // Navigate to the appender. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Third column' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -54,6 +63,11 @@ describe( 'Navigating the block hierarchy', () => { await insertBlock( 'Columns' ); // Add a paragraph in the first column. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First column' ); // Navigate to the columns blocks using the keyboard. @@ -76,7 +90,11 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Enter' ); // Insert text in the last column block - await pressKeyTimes( 'Tab', 5 ); // Navigate to the appender. + await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await page.keyboard.press( 'Enter' ); // Activate inserter. + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Third column' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index c9ca7bf2021df7..fe801a3617284d 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -16,6 +16,10 @@ describe( 'adding blocks', () => { } ); it( 'Should navigate inner blocks with arrow keys', async () => { + // TODO: The `waitForSelector` calls in this function should ultimately + // not be necessary for interactions, and exist as a stop-gap solution + // where rendering delays in slower CPU can cause intermittent failure. + let activeElementText; // Add demo content @@ -24,13 +28,22 @@ describe( 'adding blocks', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( '/columns' ); await page.keyboard.press( 'Enter' ); + await page.click( ':focus .block-editor-button-block-appender' ); + await page.waitForSelector( ':focus.block-editor-inserter__search' ); + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'First col' ); - // Arrow down should navigate through layouts in columns block (to - // its default appender). Two key presses are required since the first - // will land user on the Column wrapper block. - await page.keyboard.press( 'ArrowDown' ); - await page.keyboard.press( 'ArrowDown' ); + // TODO: ArrowDown should traverse into the second column. In slower + // CPUs, it can sometimes remain in the first column paragraph. This + // is a temporary solution. + await page.focus( '.wp-block[data-type="core/column"]:nth-child(2)' ); + await page.click( ':focus .block-editor-button-block-appender' ); + await page.waitForSelector( ':focus.block-editor-inserter__search' ); + await page.keyboard.type( 'Paragraph' ); + await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. + await page.keyboard.press( 'Enter' ); // Insert paragraph. await page.keyboard.type( 'Second col' ); // Arrow down from last of layouts exits nested context to default From 1d82701925bdc47b4f41e2691def7a3e7152b452 Mon Sep 17 00:00:00 2001 From: Brent Swisher <swisherb@gvsu.edu> Date: Mon, 13 May 2019 05:21:42 -0400 Subject: [PATCH 079/664] Add text warning about usability when autoplay is selected in audio or video blocks (#15575) --- packages/block-library/src/audio/edit.js | 5 +++++ packages/block-library/src/video/edit.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 7a704382e36c0b..1545378ddc0c74 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -98,6 +98,10 @@ class AudioEdit extends Component { this.setState( { editing: false } ); } + getAutoplayHelp( checked ) { + return checked ? __( 'Note: Autoplaying audio may cause usability issues for some visitors.' ) : null; + } + render() { const { autoplay, caption, loop, preload, src } = this.props.attributes; const { setAttributes, isSelected, className, noticeOperations, noticeUI } = this.props; @@ -153,6 +157,7 @@ class AudioEdit extends Component { label={ __( 'Autoplay' ) } onChange={ this.toggleAttribute( 'autoplay' ) } checked={ autoplay } + help={ this.getAutoplayHelp } /> <ToggleControl label={ __( 'Loop' ) } diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 2ec1ad42041c83..0f2939fe1f6a5e 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -126,6 +126,10 @@ class VideoEdit extends Component { this.posterImageButton.current.focus(); } + getAutoplayHelp( checked ) { + return checked ? __( 'Note: Autoplaying videos may cause usability issues for some visitors.' ) : null; + } + render() { const { autoplay, @@ -200,6 +204,7 @@ class VideoEdit extends Component { label={ __( 'Autoplay' ) } onChange={ this.toggleAttribute( 'autoplay' ) } checked={ autoplay } + help={ this.getAutoplayHelp } /> <ToggleControl label={ __( 'Loop' ) } From ee7c19acf998d99455939dc2501a66179d82cd97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20G=C3=A4chter?= <hello@lukasgaechter.ch> Date: Mon, 13 May 2019 11:22:21 +0200 Subject: [PATCH 080/664] Fix id attribute to match aria-owns attribute (#15564) * Fix id attribute to match aria-owns attribute * Use variables for ids used for ARIA attributes --- .../block-editor/src/components/url-input/index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 900f21fb3eebd0..88562b719802b0 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -226,6 +226,10 @@ class URLInput extends Component { render() { const { value = '', autoFocus = true, instanceId, className } = this.props; const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state; + + const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`; + const suggestionOptionIdPrefix = `block-editor-url-input-suggestion-${ instanceId }`; + /* eslint-disable jsx-a11y/no-autofocus */ return ( <div className={ classnames( 'editor-url-input block-editor-url-input', className ) }> @@ -242,8 +246,8 @@ class URLInput extends Component { role="combobox" aria-expanded={ showSuggestions } aria-autocomplete="list" - aria-owns={ `block-editor-url-input-suggestions-${ instanceId }` } - aria-activedescendant={ selectedSuggestion !== null ? `block-editor-url-input-suggestion-${ instanceId }-${ selectedSuggestion }` : undefined } + aria-owns={ suggestionsListboxId } + aria-activedescendant={ selectedSuggestion !== null ? `${ suggestionOptionIdPrefix }-${ selectedSuggestion }` : undefined } ref={ this.inputRef } /> @@ -253,7 +257,7 @@ class URLInput extends Component { <Popover position="bottom" noArrow focusOnMount={ false }> <div className="editor-url-input__suggestions block-editor-url-input__suggestions" - id={ `editor-url-input-suggestions-${ instanceId }` } + id={ suggestionsListboxId } ref={ this.autocompleteRef } role="listbox" > @@ -262,7 +266,7 @@ class URLInput extends Component { key={ suggestion.id } role="option" tabIndex="-1" - id={ `block-editor-url-input-suggestion-${ instanceId }-${ index }` } + id={ `${ suggestionOptionIdPrefix }-${ index }` } ref={ this.bindSuggestionNode( index ) } className={ classnames( 'editor-url-input__suggestion block-editor-url-input__suggestion', { 'is-selected': index === selectedSuggestion, From c7a8d04186d96618ce50d1ecf05425f48cd18ad2 Mon Sep 17 00:00:00 2001 From: Brent Swisher <swisherb@gvsu.edu> Date: Mon, 13 May 2019 05:27:51 -0400 Subject: [PATCH 081/664] Remove aria-label from block inserter list item (#15382) * Remove aria-label from block inserter list item Because the text in the button is always visible, there is no need ot add the aria-label. Having duplicated aria tags increases the chance of a conflict with the visible text from future changes. Brought up in item GUT-18 of the wpcampus accessibliity audit. * Update e2e tests that relied on aria-label on block inserter button that no longer exists * Add waitForSelector to ensure the block navigator is open before attempting to space into the first paragraph --- .../src/components/inserter-list-item/index.js | 1 - .../src/get-available-block-transforms.js | 2 +- packages/e2e-test-utils/src/insert-block.js | 5 ++++- packages/e2e-test-utils/src/transform-block-to.js | 6 ++++-- .../specs/block-hierarchy-navigation.test.js | 1 + .../specs/plugins/allowed-blocks.test.js | 15 ++++++++++----- .../specs/plugins/container-blocks.test.js | 5 ++++- .../plugins/inner-blocks-allowed-blocks.test.js | 5 ++++- 8 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index 69d9e047418f3c..5e995c4d7a7a2a 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -43,7 +43,6 @@ function InserterListItem( { onClick(); } } disabled={ isDisabled } - aria-label={ title } // Fix for IE11 and JAWS 2018. { ...props } > <span diff --git a/packages/e2e-test-utils/src/get-available-block-transforms.js b/packages/e2e-test-utils/src/get-available-block-transforms.js index 36e33f3846f543..d3d1192798a8ef 100644 --- a/packages/e2e-test-utils/src/get-available-block-transforms.js +++ b/packages/e2e-test-utils/src/get-available-block-transforms.js @@ -21,7 +21,7 @@ export const getAvailableBlockTransforms = async () => { ) ).map( ( button ) => { - return button.getAttribute( 'aria-label' ); + return button.textContent; } ); }, '.block-editor-block-types-list .block-editor-block-types-list__list-item button' ); diff --git a/packages/e2e-test-utils/src/insert-block.js b/packages/e2e-test-utils/src/insert-block.js index f32cb2da1aba87..452f0432d0ed97 100644 --- a/packages/e2e-test-utils/src/insert-block.js +++ b/packages/e2e-test-utils/src/insert-block.js @@ -18,5 +18,8 @@ export async function insertBlock( searchTerm, panelName = null ) { ) )[ 0 ]; await panelButton.click(); } - await page.click( `button[aria-label="${ searchTerm }"]` ); + const insertButton = ( await page.$x( + `//button//span[contains(text(), '${ searchTerm }')]` + ) )[ 0 ]; + await insertButton.click(); } diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 079adbea2f379f..64132aac7f45be 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -7,8 +7,10 @@ export async function transformBlockTo( name ) { await page.mouse.move( 200, 300, { steps: 10 } ); await page.mouse.move( 250, 350, { steps: 10 } ); await page.click( '.block-editor-block-switcher__toggle' ); - await page.waitForSelector( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); - await page.click( `.block-editor-block-types-list__item[aria-label="${ name }"]` ); + const insertButton = ( await page.$x( + `//button//span[contains(text(), '${ name }')]` + ) )[ 0 ]; + await insertButton.click(); const BLOCK_SELECTOR = '.block-editor-block-list__block'; const BLOCK_NAME_SELECTOR = `[aria-label="Block: ${ name }"]`; // Wait for the transformed block to appear. diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 77d424d7e6381c..56d1ce2c3694b2 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -114,6 +114,7 @@ describe( 'Navigating the block hierarchy', () => { // Return to first block. await openBlockNavigator(); + await page.waitForSelector( '.editor-block-navigation__container' ); await page.keyboard.press( 'Space' ); // Replace its content. diff --git a/packages/e2e-tests/specs/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/allowed-blocks.test.js index 9e86e4ad019908..f7bdffcf3a703a 100644 --- a/packages/e2e-tests/specs/plugins/allowed-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/allowed-blocks.test.js @@ -24,13 +24,18 @@ describe( 'Allowed Blocks Filter', () => { it( 'should restrict the allowed blocks in the inserter', async () => { // The paragraph block is available. await searchForBlock( 'Paragraph' ); - const paragraphBlock = await page.$( `button[aria-label="Paragraph"]` ); - expect( paragraphBlock ).not.toBeNull(); - await paragraphBlock.click(); + const paragraphBlockButton = ( await page.$x( + `//button//span[contains(text(), 'Paragraph')]` + ) )[ 0 ]; + expect( paragraphBlockButton ).not.toBeNull(); + await paragraphBlockButton.click(); // The gallery block is not available. await searchForBlock( 'Gallery' ); - const galleryBlock = await page.$( `button[aria-label="Gallery"]` ); - expect( galleryBlock ).toBeNull(); + + const galleryBlockButton = ( await page.$x( + `//button//span[contains(text(), 'Gallery')]` + ) )[ 0 ]; + expect( galleryBlockButton ).toBeUndefined(); } ); } ); diff --git a/packages/e2e-tests/specs/plugins/container-blocks.test.js b/packages/e2e-tests/specs/plugins/container-blocks.test.js index c01eb6eeca9a4d..dc52be82d5f61f 100644 --- a/packages/e2e-tests/specs/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/container-blocks.test.js @@ -82,7 +82,10 @@ describe( 'Container block without paragraph support', () => { await page.click( '.block-editor-inner-blocks .block-list-appender .block-list-appender__toggle' ); // Insert an image block. - await page.click( '.block-editor-inserter__results button[aria-label="Image"]' ); + const insertButton = ( await page.$x( + `//button//span[contains(text(), 'Image')]` + ) )[ 0 ]; + await insertButton.click(); // Check the inserted content. expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js index 05c3a98b73ff43..c0a2055ade7623 100644 --- a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js @@ -71,7 +71,10 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { 'Image', 'List', ] ); - await page.click( `.block-editor-block-types-list__item[aria-label="List"]` ); + const insertButton = ( await page.$x( + `//button//span[contains(text(), 'List')]` + ) )[ 0 ]; + await insertButton.click(); await insertBlock( 'Image' ); await page.click( appenderSelector ); await openAllBlockInserterCategories(); From eea04cf2023871351ce486b02f8749aa51085fd8 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Mon, 13 May 2019 12:16:15 +0200 Subject: [PATCH 082/664] Fix broken active state on formatting buttons (#15592) At some point recently a small regression was introduced to the formatting button styles where the `:active` state box shadow that is inherited from the IconButton component lingered. To reproduce in master, click and drag out on a formatting button and note a gray box shadow lingering. This PR fixes that. I'm unsure as to how the regression was introduced, but it was likely due to the rather long selectors the IconButton component uses, which would be good to refactor. --- packages/components/src/toolbar-button/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/toolbar-button/style.scss b/packages/components/src/toolbar-button/style.scss index 65f3c0e56c11a5..b35d826ebdfc7b 100644 --- a/packages/components/src/toolbar-button/style.scss +++ b/packages/components/src/toolbar-button/style.scss @@ -10,7 +10,7 @@ height: $icon-button-size; // Unset icon button styles - &:active, + &:not([aria-disabled="true"]):not(.is-default):active, &:not([aria-disabled="true"]):hover, &:not([aria-disabled="true"]):focus { outline: none; From 6171097d4688fa28e739260616c157110f24e93f Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Mon, 13 May 2019 15:32:27 +0100 Subject: [PATCH 083/664] Fix/buttons should not appear as links (#15460) * Removes colors that visually imply a hyperlink Addresses https://github.com/WordPress/gutenberg/issues/15358#issue-438944848 * Revert "Removes colors that visually imply a hyperlink" This reverts commit 1aaec62d47cd95f3787e2f24d5e25b06bb6580a6. * Removes link styling from buttons in prepublish sidebar Addresses https://github.com/WordPress/gutenberg/issues/15358#issuecomment-489615758 * Removes superfluous text decoration rule Addresses https://github.com/WordPress/gutenberg/pull/15460#discussion_r281625728 --- packages/editor/src/components/post-publish-panel/style.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/editor/src/components/post-publish-panel/style.scss b/packages/editor/src/components/post-publish-panel/style.scss index 0faa264e871ad7..31654dcc64b001 100644 --- a/packages/editor/src/components/post-publish-panel/style.scss +++ b/packages/editor/src/components/post-publish-panel/style.scss @@ -54,10 +54,8 @@ } .editor-post-publish-panel__link { - color: $blue-medium-700; font-weight: 400; padding-left: 4px; - text-decoration: underline; } .editor-post-publish-panel__prepublish { From 449c25459c917b546cdfd786dbfd56dd3cb9a43a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 13 May 2019 11:48:20 -0400 Subject: [PATCH 084/664] Rich Text: Set applied format as active in applyFormats (#15573) * Rich Text: Set applied format as active in applyFormats * Rich Text: Update toggleFormat tests per activeFormats revision * Rich Text: Replace existing active format in applied * Rich Text: Remove active format in all cases for removeFormat * Rich Text: Update toggleFormat tests per removeFormats revision * Testing: Update E2E test case to verify link display regression * Rich Text: Verify by test of activeFormats format replacement --- .../specs/__snapshots__/links.test.js.snap | 6 +++ packages/e2e-tests/specs/links.test.js | 43 +++++++++++++++++++ packages/rich-text/src/apply-format.js | 23 +++++----- packages/rich-text/src/remove-format.js | 11 +++-- packages/rich-text/src/test/apply-format.js | 11 +++++ packages/rich-text/src/test/remove-format.js | 2 + packages/rich-text/src/test/toggle-format.js | 2 + 7 files changed, 82 insertions(+), 16 deletions(-) diff --git a/packages/e2e-tests/specs/__snapshots__/links.test.js.snap b/packages/e2e-tests/specs/__snapshots__/links.test.js.snap index d63515e5d2ee34..292ac9e5781a92 100644 --- a/packages/e2e-tests/specs/__snapshots__/links.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/links.test.js.snap @@ -47,3 +47,9 @@ exports[`Links should contain a label when it should open in a new tab 1`] = ` <p>This is <a href=\\"http://w.org\\" target=\\"_blank\\" rel=\\"noreferrer noopener\\" aria-label=\\"WordPress (opens in a new tab)\\">WordPress</a></p> <!-- /wp:paragraph -->" `; + +exports[`Links should contain a label when it should open in a new tab 2`] = ` +"<!-- wp:paragraph --> +<p>This is <a href=\\"http://wordpress.org\\">WordPress</a></p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index 84563375547b2e..a1d1dd3a46934b 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -503,5 +503,48 @@ describe( 'Links', () => { await page.keyboard.press( 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); + + // Regression Test: This verifies that the UI is updated according to + // the expected changed values, where previously the value could have + // fallen out of sync with how the UI is displayed (specifically for + // collapsed selections). + // + // See: https://github.com/WordPress/gutenberg/pull/15573 + + // Collapse selection. + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowRight' ); + // Edit link. + await pressKeyWithModifier( 'primary', 'k' ); + await waitForAutoFocus(); + await pressKeyWithModifier( 'primary', 'a' ); + await page.keyboard.type( 'wordpress.org' ); + // Navigate to the settings toggle. + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); + // Open settings. + await page.keyboard.press( 'Space' ); + // Navigate to the "Open in New Tab" checkbox. + await page.keyboard.press( 'Tab' ); + // Uncheck the checkbox. + await page.keyboard.press( 'Space' ); + // Navigate back to the input field. + await page.keyboard.press( 'Tab' ); + // Submit the form. + await page.keyboard.press( 'Enter' ); + + // Navigate back to inputs to verify appears as changed. + await pressKeyWithModifier( 'primary', 'k' ); + await waitForAutoFocus(); + const link = await page.evaluate( () => document.activeElement.value ); + expect( link ).toBe( 'http://wordpress.org' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Space' ); + await page.keyboard.press( 'Tab' ); + const isChecked = await page.evaluate( () => document.activeElement.checked ); + expect( isChecked ).toBe( false ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index c2b96af10d6002..03a9ddc9f94d5c 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -2,7 +2,7 @@ * External dependencies */ -import { find } from 'lodash'; +import { find, reject } from 'lodash'; /** * Internal dependencies @@ -34,7 +34,7 @@ export function applyFormat( startIndex = value.start, endIndex = value.end ) { - const { formats, activeFormats = [] } = value; + const { formats, activeFormats } = value; const newFormats = formats.slice(); // The selection is collapsed. @@ -59,13 +59,6 @@ export function applyFormat( replace( newFormats[ endIndex ], index, format ); endIndex++; } - // Otherwise, insert a placeholder with the format so new input appears - // with the format applied. - } else { - return { - ...value, - activeFormats: [ ...activeFormats, format ], - }; } } else { // Determine the highest position the new format can be inserted at. @@ -92,5 +85,15 @@ export function applyFormat( } } - return normaliseFormats( { ...value, formats: newFormats } ); + return normaliseFormats( { + ...value, + formats: newFormats, + // Always revise active formats. This serves as a placeholder for new + // inputs with the format so new input appears with the format applied, + // and ensures a format of the same type uses the latest values. + activeFormats: [ + ...reject( activeFormats, { type: format.type } ), + format, + ], + } ); } diff --git a/packages/rich-text/src/remove-format.js b/packages/rich-text/src/remove-format.js index 4a4c9c820f9008..5ce2fcd7ce8333 100644 --- a/packages/rich-text/src/remove-format.js +++ b/packages/rich-text/src/remove-format.js @@ -48,11 +48,6 @@ export function removeFormat( filterFormats( newFormats, endIndex, formatType ); endIndex++; } - } else { - return { - ...value, - activeFormats: reject( activeFormats, { type: formatType } ), - }; } } else { for ( let i = startIndex; i < endIndex; i++ ) { @@ -62,7 +57,11 @@ export function removeFormat( } } - return normaliseFormats( { ...value, formats: newFormats } ); + return normaliseFormats( { + ...value, + formats: newFormats, + activeFormats: reject( activeFormats, { type: formatType } ), + } ); } function filterFormats( formats, index, formatType ) { diff --git a/packages/rich-text/src/test/apply-format.js b/packages/rich-text/src/test/apply-format.js index 67d2c3ff082971..2c74bc1d9aa269 100644 --- a/packages/rich-text/src/test/apply-format.js +++ b/packages/rich-text/src/test/apply-format.js @@ -23,6 +23,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ em ], formats: [ [ em ], [ em ], [ em ], [ em ] ], }; const result = applyFormat( deepFreeze( record ), em, 0, 4 ); @@ -39,6 +40,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ em ], formats: [ [ strong, em ], [ strong, em ], [ strong, em ], [ strong, em ] ], }; const result = applyFormat( deepFreeze( record ), em, 0, 4 ); @@ -55,6 +57,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ em ], formats: [ [ strong, em ], [ strong, em ], [ strong, em ], [ strong, em ] ], }; const result = applyFormat( deepFreeze( record ), em, 0, 4 ); @@ -71,6 +74,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ strong ], formats: [ [ strong ], [ strong, em ], [ strong, em ], [ strong ] ], }; const result = applyFormat( deepFreeze( record ), strong, 0, 4 ); @@ -87,6 +91,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ strong ], formats: [ [ strong ], [ strong, em ], [ strong, em ], , ], }; const result = applyFormat( deepFreeze( record ), strong, 0, 3 ); @@ -103,6 +108,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ strong ], formats: [ , [ strong, em ], [ strong, em ], [ strong ] ], }; const result = applyFormat( deepFreeze( record ), strong, 1, 4 ); @@ -119,6 +125,7 @@ describe( 'applyFormat', () => { }; const expected = { ...record, + activeFormats: [ strong ], formats: [ , [ strong, em ], [ strong ], [ strong, em ] ], }; const result = applyFormat( deepFreeze( record ), strong, 1, 4 ); @@ -134,6 +141,7 @@ describe( 'applyFormat', () => { text: 'one two three', }; const expected = { + activeFormats: [ strong ], formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ], text: 'one two three', }; @@ -152,6 +160,7 @@ describe( 'applyFormat', () => { end: 6, }; const expected = { + activeFormats: [ strong ], formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ], text: 'one two three', start: 3, @@ -184,12 +193,14 @@ describe( 'applyFormat', () => { it( 'should apply format on existing format if selection is collapsed', () => { const record = { + activeFormats: [ a ], formats: [ , , , , [ a ], [ a ], [ a ], , , , , , , ], text: 'one two three', start: 4, end: 4, }; const expected = { + activeFormats: [ a2 ], formats: [ , , , , [ a2 ], [ a2 ], [ a2 ], , , , , , , ], text: 'one two three', start: 4, diff --git a/packages/rich-text/src/test/remove-format.js b/packages/rich-text/src/test/remove-format.js index 4d6b10f0d27fbe..343eb8c47e011f 100644 --- a/packages/rich-text/src/test/remove-format.js +++ b/packages/rich-text/src/test/remove-format.js @@ -21,6 +21,7 @@ describe( 'removeFormat', () => { }; const expected = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + activeFormats: [], text: 'one two three', }; const result = removeFormat( deepFreeze( record ), 'strong', 3, 6 ); @@ -37,6 +38,7 @@ describe( 'removeFormat', () => { }; const expected = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + activeFormats: [], text: 'one two three', }; const result = removeFormat( deepFreeze( record ), 'strong', 4, 4 ); diff --git a/packages/rich-text/src/test/toggle-format.js b/packages/rich-text/src/test/toggle-format.js index 28762a37b1603c..9613e970d40a4e 100644 --- a/packages/rich-text/src/test/toggle-format.js +++ b/packages/rich-text/src/test/toggle-format.js @@ -23,6 +23,7 @@ describe( 'toggleFormat', () => { }; const expected = { formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , ], + activeFormats: [], text: 'one two three', start: 3, end: 6, @@ -43,6 +44,7 @@ describe( 'toggleFormat', () => { }; const expected = { formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ], + activeFormats: [ strong ], text: 'one two three', start: 3, end: 6, From f6d816dd080794b8523bb0f2f8a0e49923660f92 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Mon, 13 May 2019 23:39:48 +0200 Subject: [PATCH 085/664] Fix pinned plugins button styles when toggled. (#15609) --- .../src/components/header/pinned-plugins/style.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/header/pinned-plugins/style.scss b/packages/edit-post/src/components/header/pinned-plugins/style.scss index b01dd0480d5ee5..030d6305ae9033 100644 --- a/packages/edit-post/src/components/header/pinned-plugins/style.scss +++ b/packages/edit-post/src/components/header/pinned-plugins/style.scss @@ -7,6 +7,10 @@ .components-icon-button { margin-left: 4px; + + &.is-toggled { + margin-left: 5px; + } } // Colorize plugin icons to ensure contrast and cohesion, but allow plugin developers to override. @@ -19,7 +23,9 @@ // Forcefully colorize hover and toggled plugin icon states to ensure legibility and consistency. .components-icon-button.is-toggled svg, - .components-icon-button.is-toggled svg * { + .components-icon-button.is-toggled svg *, + .components-icon-button.is-toggled:hover svg, + .components-icon-button.is-toggled:hover svg * { stroke: $white !important; fill: $white !important; stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. From b695b53fe536b85ca655c68d48b4297015b56bba Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 13 May 2019 14:49:10 -0700 Subject: [PATCH 086/664] Docs: Clarify migrate in deprecated blocks (#15612) The wording in migrate was confusing around which attributes should be supplied and returned. Added old/new to clarify the old attributes are passed in, and new attributes are returned. Confirmed this with the code, which you can see in the test here: https://github.com/WordPress/gutenberg/blob/master/packages/blocks/src/api/test/parser.js#L558 --- .../developers/block-api/block-deprecation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index 795a5951b2fd04..0e5be3c05f27a8 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -12,7 +12,7 @@ Deprecations are defined on a block type as its `deprecated` property, an array - `attributes` (Object): The [attributes definition](/docs/designers-developers/developers/block-api/block-attributes.md) of the deprecated form of the block. - `support` (Object): The [supports definition](/docs/designers-developers/developers/block-api/block-registration.md) of the deprecated form of the block. - `save` (Function): The [save implementation](/docs/designers-developers/developers/block-api/block-edit-save.md) of the deprecated form of the block. -- `migrate` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, is expected to return either the attributes compatible with the deprecated block, or a tuple array of `[ attributes, innerBlocks ]`. +- `migrate` (Function, Optional): A function which, given the old attributes and inner blocks is expected to return either the new attributes or a tuple array of `[ attributes, innerBlocks ]` compatible with the block. - `isEligible` (Function, Optional): A function which, given the attributes and inner blocks of the parsed block, returns true if the deprecation can handle the block migration. This is particularly useful in cases where a block is technically valid even once deprecated, and requires updates to its attributes or inner blocks. It's important to note that `attributes`, `support`, and `save` are not automatically inherited from the current version, since they can impact parsing and serialization of a block, so they must be defined on the deprecated object in order to be processed during a migration. From 60643a384054bcea6ff9b2586bf9d0e589cfc12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Tue, 14 May 2019 09:27:52 +0200 Subject: [PATCH 087/664] Block library: refactor edit components in Legacy Widget block (#15555) --- .../dom-manager.js} | 4 ++-- .../{WidgetEditHandler.js => edit/handler.js} | 8 ++++---- .../src/legacy-widget/{edit.js => edit/index.js} | 15 +++++++-------- 3 files changed, 13 insertions(+), 14 deletions(-) rename packages/block-library/src/legacy-widget/{WidgetEditDomManager.js => edit/dom-manager.js} (97%) rename packages/block-library/src/legacy-widget/{WidgetEditHandler.js => edit/handler.js} (93%) rename packages/block-library/src/legacy-widget/{edit.js => edit/index.js} (96%) diff --git a/packages/block-library/src/legacy-widget/WidgetEditDomManager.js b/packages/block-library/src/legacy-widget/edit/dom-manager.js similarity index 97% rename from packages/block-library/src/legacy-widget/WidgetEditDomManager.js rename to packages/block-library/src/legacy-widget/edit/dom-manager.js index 30ded7a7a7a6f8..d4a0b43829f29c 100644 --- a/packages/block-library/src/legacy-widget/WidgetEditDomManager.js +++ b/packages/block-library/src/legacy-widget/edit/dom-manager.js @@ -9,7 +9,7 @@ import { includes } from 'lodash'; import { Component, createRef } from '@wordpress/element'; import isShallowEqual from '@wordpress/is-shallow-equal'; -class WidgetEditDomManager extends Component { +class LegacyWidgetEditDomManager extends Component { constructor() { super( ...arguments ); @@ -139,5 +139,5 @@ class WidgetEditDomManager extends Component { } } -export default WidgetEditDomManager; +export default LegacyWidgetEditDomManager; diff --git a/packages/block-library/src/legacy-widget/WidgetEditHandler.js b/packages/block-library/src/legacy-widget/edit/handler.js similarity index 93% rename from packages/block-library/src/legacy-widget/WidgetEditHandler.js rename to packages/block-library/src/legacy-widget/edit/handler.js index 527e864c90037a..157ab653fb89f5 100644 --- a/packages/block-library/src/legacy-widget/WidgetEditHandler.js +++ b/packages/block-library/src/legacy-widget/edit/handler.js @@ -9,9 +9,9 @@ import { withInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ -import WidgetEditDomManager from './WidgetEditDomManager'; +import LegacyWidgetEditDomManager from './dom-manager'; -class WidgetEditHandler extends Component { +class LegacyWidgetEditHandler extends Component { constructor() { super( ...arguments ); this.state = { @@ -64,7 +64,7 @@ class WidgetEditHandler extends Component { display: this.props.isVisible ? 'block' : 'none', } } > - <WidgetEditDomManager + <LegacyWidgetEditDomManager ref={ ( ref ) => { this.widgetEditDomManagerRef = ref; } } @@ -118,5 +118,5 @@ class WidgetEditHandler extends Component { } } -export default withInstanceId( WidgetEditHandler ); +export default withInstanceId( LegacyWidgetEditHandler ); diff --git a/packages/block-library/src/legacy-widget/edit.js b/packages/block-library/src/legacy-widget/edit/index.js similarity index 96% rename from packages/block-library/src/legacy-widget/edit.js rename to packages/block-library/src/legacy-widget/edit/index.js index 89621422bc6650..4b645318eb2277 100644 --- a/packages/block-library/src/legacy-widget/edit.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -17,18 +17,17 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ import { BlockControls, BlockIcon, InspectorControls, - ServerSideRender, -} from '@wordpress/editor'; +} from '@wordpress/block-editor'; +import { ServerSideRender } from '@wordpress/editor'; -import WidgetEditHandler from './WidgetEditHandler'; +/** + * Internal dependencies + */ +import LegacyWidgetEditHandler from './handler'; class LegacyWidgetEdit extends Component { constructor() { @@ -136,7 +135,7 @@ class LegacyWidgetEdit extends Component { </BlockControls> { inspectorControls } { ! isCallbackWidget && ( - <WidgetEditHandler + <LegacyWidgetEditHandler isVisible={ ! isPreview } identifier={ attributes.identifier } instance={ attributes.instance } From 599e6bdaa4344eb73551890a19bb78f13f89d03f Mon Sep 17 00:00:00 2001 From: Chris Van Patten <hello@chrisvanpatten.com> Date: Tue, 14 May 2019 04:07:41 -0400 Subject: [PATCH 088/664] Add a DevHub manifest (#15254) * Add a devhub manifest * Move reference docs to bottom * Extract tutorials to the top-level * Rename Designers & Developers Handbook to Block Editor Handbook * Rename Contributors Guide to Contributor Documentation * Rename Components to Component Reference * Rename Packages to Package Reference * Substitute old top-level README The front-page of the handbook is the one whose slug is handbook. This is generated by looking at the path (docs/readme.md), so we substitute the old README with the new one. * Add a reference to contributors as well * Delete unused file that wasnt removed on rebase * Use manifest-devhub instead of old manifest * Fix toc.json from rebase * Updame manifest-devhub.json * Use relative paths to docs --- docs/contributors/readme.md | 2 +- .../developers/packages.md | 2 +- docs/designers-developers/readme.md | 11 - docs/manifest-devhub.json | 1322 +++++++++++++++++ docs/readme.md | 23 +- docs/toc.json | 228 ++- docs/tool/index.js | 2 +- docs/tool/manifest.js | 2 +- packages/components/README.md | 2 +- 9 files changed, 1446 insertions(+), 148 deletions(-) delete mode 100644 docs/designers-developers/readme.md create mode 100644 docs/manifest-devhub.json diff --git a/docs/contributors/readme.md b/docs/contributors/readme.md index 685af4590b4737..96c3a85424e5d8 100644 --- a/docs/contributors/readme.md +++ b/docs/contributors/readme.md @@ -1,4 +1,4 @@ -# Contributors Guide +# Contributor Documentation Welcome to the Gutenberg Project Contributors Guide. diff --git a/docs/designers-developers/developers/packages.md b/docs/designers-developers/developers/packages.md index b5ef06c18782a2..fda7d986209efd 100644 --- a/docs/designers-developers/developers/packages.md +++ b/docs/designers-developers/developers/packages.md @@ -1,4 +1,4 @@ -# Packages +# Package Reference WordPress exposes a list of JavaScript packages and tools for WordPress development. diff --git a/docs/designers-developers/readme.md b/docs/designers-developers/readme.md deleted file mode 100644 index 7b8da012e063ff..00000000000000 --- a/docs/designers-developers/readme.md +++ /dev/null @@ -1,11 +0,0 @@ -# Designer & Developer Handbook - -The Gutenberg project is transforming the way content is created on WordPress. A block editor was the first product launched creating a new methodology for working with content. This handbook provides documentation for how designers and developers can extend the editor. - -![Gutenberg Demo](https://cldup.com/kZXGDcGPMU.gif) - -Using a system of Blocks to compose and format content, the new block-based editor is designed to create rich, flexible layouts for websites and digital products. Content is created in the unit of blocks instead of freeform text with inserted media, embeds and Shortcodes (there's a Shortcode block though). - -Blocks treat Paragraphs, Headings, Media, Embeds all as components that strung together make up the content stored in the WordPress database, replacing the traditional concept of freeform text with embedded media and shortcodes. The new editor is designed with progressive enhancement, meaning it is back-compatible with all legacy content, offers a process to try to convert and split a Classic block into block equivalents using client-side parsing and finally the blocks offer enhanced editing and format controls. - -The Editor offers rich new value to users with visual, drag-and-drop creation tools and powerful developer enhancements with modern vendor packages, reusable components, rich APIs and hooks to modify and extend the editor through Custom Blocks, Custom Block Styles and Plugins. diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json new file mode 100644 index 00000000000000..09544a23854c3e --- /dev/null +++ b/docs/manifest-devhub.json @@ -0,0 +1,1322 @@ +[ + { + "title": "Block Editor Handbook", + "slug": "handbook", + "markdown_source": "../docs/readme.md", + "parent": null + }, + { + "title": "Key Concepts", + "slug": "key-concepts", + "markdown_source": "../docs/designers-developers/key-concepts.md", + "parent": null + }, + { + "title": "Developer Documentation", + "slug": "developers", + "markdown_source": "../docs/designers-developers/developers/README.md", + "parent": null + }, + { + "title": "Block API Reference", + "slug": "block-api", + "markdown_source": "../docs/designers-developers/developers/block-api/README.md", + "parent": "developers" + }, + { + "title": "Block Registration", + "slug": "block-registration", + "markdown_source": "../docs/designers-developers/developers/block-api/block-registration.md", + "parent": "block-api" + }, + { + "title": "Edit and Save", + "slug": "block-edit-save", + "markdown_source": "../docs/designers-developers/developers/block-api/block-edit-save.md", + "parent": "block-api" + }, + { + "title": "Attributes", + "slug": "block-attributes", + "markdown_source": "../docs/designers-developers/developers/block-api/block-attributes.md", + "parent": "block-api" + }, + { + "title": "Deprecated Blocks", + "slug": "block-deprecation", + "markdown_source": "../docs/designers-developers/developers/block-api/block-deprecation.md", + "parent": "block-api" + }, + { + "title": "Templates", + "slug": "block-templates", + "markdown_source": "../docs/designers-developers/developers/block-api/block-templates.md", + "parent": "block-api" + }, + { + "title": "Annotations", + "slug": "block-annotations", + "markdown_source": "../docs/designers-developers/developers/block-api/block-annotations.md", + "parent": "block-api" + }, + { + "title": "Filter Reference", + "slug": "filters", + "markdown_source": "../docs/designers-developers/developers/filters/README.md", + "parent": "developers" + }, + { + "title": "Block Filters", + "slug": "block-filters", + "markdown_source": "../docs/designers-developers/developers/filters/block-filters.md", + "parent": "filters" + }, + { + "title": "Editor Filters (Experimental)", + "slug": "editor-filters", + "markdown_source": "../docs/designers-developers/developers/filters/editor-filters.md", + "parent": "filters" + }, + { + "title": "Parser Filters", + "slug": "parser-filters", + "markdown_source": "../docs/designers-developers/developers/filters/parser-filters.md", + "parent": "filters" + }, + { + "title": "Autocomplete", + "slug": "autocomplete-filters", + "markdown_source": "../docs/designers-developers/developers/filters/autocomplete-filters.md", + "parent": "filters" + }, + { + "title": "Internationalization", + "slug": "internationalization", + "markdown_source": "../docs/designers-developers/developers/internationalization.md", + "parent": "developers" + }, + { + "title": "Accessibility", + "slug": "accessibility", + "markdown_source": "../docs/designers-developers/developers/accessibility.md", + "parent": "developers" + }, + { + "title": "Feature Flags", + "slug": "feature-flags", + "markdown_source": "../docs/designers-developers/developers/feature-flags.md", + "parent": "developers" + }, + { + "title": "Theming for the Block Editor", + "slug": "themes", + "markdown_source": "../docs/designers-developers/developers/themes/README.md", + "parent": "developers" + }, + { + "title": "Theme Support", + "slug": "theme-support", + "markdown_source": "../docs/designers-developers/developers/themes/theme-support.md", + "parent": "themes" + }, + { + "title": "Backward Compatibility", + "slug": "backward-compatibility", + "markdown_source": "../docs/designers-developers/developers/backward-compatibility/README.md", + "parent": "developers" + }, + { + "title": "Deprecations", + "slug": "deprecations", + "markdown_source": "../docs/designers-developers/developers/backward-compatibility/deprecations.md", + "parent": "backward-compatibility" + }, + { + "title": "Meta Boxes", + "slug": "meta-box", + "markdown_source": "../docs/designers-developers/developers/backward-compatibility/meta-box.md", + "parent": "backward-compatibility" + }, + { + "title": "Designer Documentation", + "slug": "designers", + "markdown_source": "../docs/designers-developers/designers/README.md", + "parent": null + }, + { + "title": "Block Design", + "slug": "block-design", + "markdown_source": "../docs/designers-developers/designers/block-design.md", + "parent": "designers" + }, + { + "title": "Patterns", + "slug": "design-patterns", + "markdown_source": "../docs/designers-developers/designers/design-patterns.md", + "parent": "designers" + }, + { + "title": "Resources", + "slug": "design-resources", + "markdown_source": "../docs/designers-developers/designers/design-resources.md", + "parent": "designers" + }, + { + "title": "Animation", + "slug": "animation", + "markdown_source": "../docs/designers-developers/designers/animation.md", + "parent": "designers" + }, + { + "title": "Contributor Documentation", + "slug": "contributors", + "markdown_source": "../docs/contributors/readme.md", + "parent": null + }, + { + "title": "Principles", + "slug": "principles", + "markdown_source": "../docs/contributors/principles.md", + "parent": "contributors" + }, + { + "title": "Design Principles & Vision", + "slug": "design", + "markdown_source": "../docs/contributors/design.md", + "parent": "contributors" + }, + { + "title": "Blocks are the Interface", + "slug": "the-block", + "markdown_source": "../docs/contributors/principles/the-block.md", + "parent": "design" + }, + { + "title": "Reference", + "slug": "reference", + "markdown_source": "../docs/contributors/reference.md", + "parent": "design" + }, + { + "title": "Developer Contributions", + "slug": "develop", + "markdown_source": "../docs/contributors/develop.md", + "parent": "contributors" + }, + { + "title": "Getting Started", + "slug": "getting-started", + "markdown_source": "../docs/contributors/getting-started.md", + "parent": "develop" + }, + { + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "../docs/contributors/git-workflow.md", + "parent": "develop" + }, + { + "title": "Coding Guidelines", + "slug": "coding-guidelines", + "markdown_source": "../docs/contributors/coding-guidelines.md", + "parent": "develop" + }, + { + "title": "Testing Overview", + "slug": "testing-overview", + "markdown_source": "../docs/contributors/testing-overview.md", + "parent": "develop" + }, + { + "title": "Block Grammar", + "slug": "grammar", + "markdown_source": "../docs/contributors/grammar.md", + "parent": "develop" + }, + { + "title": "Scripts", + "slug": "scripts", + "markdown_source": "../docs/contributors/scripts.md", + "parent": "develop" + }, + { + "title": "Managing Packages", + "slug": "managing-packages", + "markdown_source": "../docs/contributors/managing-packages.md", + "parent": "develop" + }, + { + "title": "Gutenberg Release Process", + "slug": "release", + "markdown_source": "../docs/contributors/release.md", + "parent": "develop" + }, + { + "title": "Localizing Gutenberg Plugin", + "slug": "localizing", + "markdown_source": "../docs/contributors/localizing.md", + "parent": "develop" + }, + { + "title": "Documentation Contributions", + "slug": "document", + "markdown_source": "../docs/contributors/document.md", + "parent": "contributors" + }, + { + "title": "Copy Guidelines", + "slug": "copy-guide", + "markdown_source": "../docs/contributors/copy-guide.md", + "parent": "document" + }, + { + "title": "History", + "slug": "history", + "markdown_source": "../docs/contributors/history.md", + "parent": "contributors" + }, + { + "title": "Glossary", + "slug": "glossary", + "markdown_source": "../docs/designers-developers/glossary.md", + "parent": "contributors" + }, + { + "title": "Frequently Asked Questions", + "slug": "faq", + "markdown_source": "../docs/designers-developers/faq.md", + "parent": "contributors" + }, + { + "title": "Repository Management", + "slug": "repository-management", + "markdown_source": "../docs/contributors/repository-management.md", + "parent": "contributors" + }, + { + "title": "Outreach", + "slug": "outreach", + "markdown_source": "../docs/contributors/outreach.md", + "parent": "contributors" + }, + { + "title": "Tutorials", + "slug": "tutorials", + "markdown_source": "../docs/designers-developers/developers/tutorials/readme.md", + "parent": null + }, + { + "title": "Getting Started with JavaScript", + "slug": "javascript", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/readme.md", + "parent": "tutorials" + }, + { + "title": "Plugins Background", + "slug": "plugins-background", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/plugins-background.md", + "parent": "javascript" + }, + { + "title": "Loading JavaScript", + "slug": "loading-javascript", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/loading-javascript.md", + "parent": "javascript" + }, + { + "title": "Extending the Block Editor", + "slug": "extending-the-block-editor", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md", + "parent": "javascript" + }, + { + "title": "Troubleshooting", + "slug": "troubleshooting", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/troubleshooting.md", + "parent": "javascript" + }, + { + "title": "JavaScript Versions and Build Step", + "slug": "versions-and-building", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/versions-and-building.md", + "parent": "javascript" + }, + { + "title": "Scope Your Code", + "slug": "scope-your-code", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/scope-your-code.md", + "parent": "javascript" + }, + { + "title": "JavaScript Build Setup", + "slug": "js-build-setup", + "markdown_source": "../docs/designers-developers/developers/tutorials/javascript/js-build-setup.md", + "parent": "javascript" + }, + { + "title": "Blocks", + "slug": "block-tutorial", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/readme.md", + "parent": "tutorials" + }, + { + "title": "Writing Your First Block Type", + "slug": "writing-your-first-block-type", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md", + "parent": "block-tutorial" + }, + { + "title": "Applying Styles From a Stylesheet", + "slug": "applying-styles-with-stylesheets", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md", + "parent": "block-tutorial" + }, + { + "title": "Introducing Attributes and Editable Fields", + "slug": "introducing-attributes-and-editable-fields", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md", + "parent": "block-tutorial" + }, + { + "title": "Block Controls: Toolbars and Inspector", + "slug": "block-controls-toolbars-and-inspector", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", + "parent": "block-tutorial" + }, + { + "title": "Creating dynamic blocks", + "slug": "creating-dynamic-blocks", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md", + "parent": "block-tutorial" + }, + { + "title": "Generate Blocks with WP-CLI", + "slug": "generate-blocks-with-wp-cli", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md", + "parent": "block-tutorial" + }, + { + "title": "Meta Boxes", + "slug": "metabox", + "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/readme.md", + "parent": "tutorials" + }, + { + "title": "Store Post Meta with a Block", + "slug": "meta-block-1-intro", + "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md", + "parent": "metabox" + }, + { + "title": "Register Meta Field", + "slug": "meta-block-2-register-meta", + "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md", + "parent": "metabox" + }, + { + "title": "Create Meta Block", + "slug": "meta-block-3-add", + "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md", + "parent": "metabox" + }, + { + "title": "Use Post Meta Data", + "slug": "meta-block-4-use-data", + "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md", + "parent": "metabox" + }, + { + "title": "Finishing Touches", + "slug": "meta-block-5-finishing", + "markdown_source": "../docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md", + "parent": "metabox" + }, + { + "title": "Displaying Notices from Your Plugin or Theme", + "slug": "notices", + "markdown_source": "../docs/designers-developers/developers/tutorials/notices/README.md", + "parent": "tutorials" + }, + { + "title": "Creating a Sidebar for Your Plugin", + "slug": "plugin-sidebar-0", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md", + "parent": "tutorials" + }, + { + "title": "Get a Sidebar up and Running", + "slug": "plugin-sidebar-1-up-and-running", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Tweak the sidebar style and add controls", + "slug": "plugin-sidebar-2-styles-and-controls", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Register the Meta Field", + "slug": "plugin-sidebar-3-register-meta", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Initialize the Input Control", + "slug": "plugin-sidebar-4-initialize-input", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Update the Meta Field When the Input's Content Changes", + "slug": "plugin-sidebar-5-update-meta", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Finishing Touches", + "slug": "plugin-sidebar-6-finishing-touches", + "markdown_source": "../docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md", + "parent": "plugin-sidebar-0" + }, + { + "title": "Introduction to the Format API", + "slug": "format-api", + "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/README.md", + "parent": "tutorials" + }, + { + "title": "Register a New Format", + "slug": "1-register-format", + "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/1-register-format.md", + "parent": "format-api" + }, + { + "title": "Add a Button to the Toolbar", + "slug": "2-toolbar-button", + "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md", + "parent": "format-api" + }, + { + "title": "Apply the Format When the Button Is Clicked", + "slug": "3-apply-format", + "markdown_source": "../docs/designers-developers/developers/tutorials/format-api/3-apply-format.md", + "parent": "format-api" + }, + { + "title": "Component Reference", + "slug": "components", + "markdown_source": "../packages/components/README.md", + "parent": null + }, + { + "title": "Animate", + "slug": "animate", + "markdown_source": "../packages/components/src/animate/README.md", + "parent": "components" + }, + { + "title": "Autocomplete", + "slug": "autocomplete", + "markdown_source": "../packages/components/src/autocomplete/README.md", + "parent": "components" + }, + { + "title": "BaseControl", + "slug": "base-control", + "markdown_source": "../packages/components/src/base-control/README.md", + "parent": "components" + }, + { + "title": "ButtonGroup", + "slug": "button-group", + "markdown_source": "../packages/components/src/button-group/README.md", + "parent": "components" + }, + { + "title": "Button", + "slug": "button", + "markdown_source": "../packages/components/src/button/README.md", + "parent": "components" + }, + { + "title": "CheckboxControl", + "slug": "checkbox-control", + "markdown_source": "../packages/components/src/checkbox-control/README.md", + "parent": "components" + }, + { + "title": "ClipboardButton", + "slug": "clipboard-button", + "markdown_source": "../packages/components/src/clipboard-button/README.md", + "parent": "components" + }, + { + "title": "ColorIndicator", + "slug": "color-indicator", + "markdown_source": "../packages/components/src/color-indicator/README.md", + "parent": "components" + }, + { + "title": "ColorPalette", + "slug": "color-palette", + "markdown_source": "../packages/components/src/color-palette/README.md", + "parent": "components" + }, + { + "title": "ColorPicker", + "slug": "color-picker", + "markdown_source": "../packages/components/src/color-picker/README.md", + "parent": "components" + }, + { + "title": "Dashicon", + "slug": "dashicon", + "markdown_source": "../packages/components/src/dashicon/README.md", + "parent": "components" + }, + { + "title": "DateTime", + "slug": "date-time", + "markdown_source": "../packages/components/src/date-time/README.md", + "parent": "components" + }, + { + "title": "Disabled", + "slug": "disabled", + "markdown_source": "../packages/components/src/disabled/README.md", + "parent": "components" + }, + { + "title": "Draggable", + "slug": "draggable", + "markdown_source": "../packages/components/src/draggable/README.md", + "parent": "components" + }, + { + "title": "DropZone", + "slug": "drop-zone", + "markdown_source": "../packages/components/src/drop-zone/README.md", + "parent": "components" + }, + { + "title": "DropdownMenu", + "slug": "dropdown-menu", + "markdown_source": "../packages/components/src/dropdown-menu/README.md", + "parent": "components" + }, + { + "title": "Dropdown", + "slug": "dropdown", + "markdown_source": "../packages/components/src/dropdown/README.md", + "parent": "components" + }, + { + "title": "ExternalLink", + "slug": "external-link", + "markdown_source": "../packages/components/src/external-link/README.md", + "parent": "components" + }, + { + "title": "FocalPointPicker", + "slug": "focal-point-picker", + "markdown_source": "../packages/components/src/focal-point-picker/README.md", + "parent": "components" + }, + { + "title": "FocusableIframe", + "slug": "focusable-iframe", + "markdown_source": "../packages/components/src/focusable-iframe/README.md", + "parent": "components" + }, + { + "title": "FontSizePicker", + "slug": "font-size-picker", + "markdown_source": "../packages/components/src/font-size-picker/README.md", + "parent": "components" + }, + { + "title": "FormFileUpload", + "slug": "form-file-upload", + "markdown_source": "../packages/components/src/form-file-upload/README.md", + "parent": "components" + }, + { + "title": "FormToggle", + "slug": "form-toggle", + "markdown_source": "../packages/components/src/form-toggle/README.md", + "parent": "components" + }, + { + "title": "FormTokenField", + "slug": "form-token-field", + "markdown_source": "../packages/components/src/form-token-field/README.md", + "parent": "components" + }, + { + "title": "NavigateRegions", + "slug": "navigate-regions", + "markdown_source": "../packages/components/src/higher-order/navigate-regions/README.md", + "parent": "components" + }, + { + "title": "HigherOrder", + "slug": "higher-order", + "markdown_source": "../packages/components/src/higher-order/README.md", + "parent": "components" + }, + { + "title": "WithConstrainedTabbing", + "slug": "with-constrained-tabbing", + "markdown_source": "../packages/components/src/higher-order/with-constrained-tabbing/README.md", + "parent": "components" + }, + { + "title": "WithFallbackStyles", + "slug": "with-fallback-styles", + "markdown_source": "../packages/components/src/higher-order/with-fallback-styles/README.md", + "parent": "components" + }, + { + "title": "WithFilters", + "slug": "with-filters", + "markdown_source": "../packages/components/src/higher-order/with-filters/README.md", + "parent": "components" + }, + { + "title": "WithFocusOutside", + "slug": "with-focus-outside", + "markdown_source": "../packages/components/src/higher-order/with-focus-outside/README.md", + "parent": "components" + }, + { + "title": "WithFocusReturn", + "slug": "with-focus-return", + "markdown_source": "../packages/components/src/higher-order/with-focus-return/README.md", + "parent": "components" + }, + { + "title": "WithNotices", + "slug": "with-notices", + "markdown_source": "../packages/components/src/higher-order/with-notices/README.md", + "parent": "components" + }, + { + "title": "WithSpokenMessages", + "slug": "with-spoken-messages", + "markdown_source": "../packages/components/src/higher-order/with-spoken-messages/README.md", + "parent": "components" + }, + { + "title": "IconButton", + "slug": "icon-button", + "markdown_source": "../packages/components/src/icon-button/README.md", + "parent": "components" + }, + { + "title": "Icon", + "slug": "icon", + "markdown_source": "../packages/components/src/icon/README.md", + "parent": "components" + }, + { + "title": "IsolatedEventContainer", + "slug": "isolated-event-container", + "markdown_source": "../packages/components/src/isolated-event-container/README.md", + "parent": "components" + }, + { + "title": "KeyboardShortcuts", + "slug": "keyboard-shortcuts", + "markdown_source": "../packages/components/src/keyboard-shortcuts/README.md", + "parent": "components" + }, + { + "title": "MenuGroup", + "slug": "menu-group", + "markdown_source": "../packages/components/src/menu-group/README.md", + "parent": "components" + }, + { + "title": "MenuItem", + "slug": "menu-item", + "markdown_source": "../packages/components/src/menu-item/README.md", + "parent": "components" + }, + { + "title": "MenuItemsChoice", + "slug": "menu-items-choice", + "markdown_source": "../packages/components/src/menu-items-choice/README.md", + "parent": "components" + }, + { + "title": "Modal", + "slug": "modal", + "markdown_source": "../packages/components/src/modal/README.md", + "parent": "components" + }, + { + "title": "NavigableContainer", + "slug": "navigable-container", + "markdown_source": "../packages/components/src/navigable-container/README.md", + "parent": "components" + }, + { + "title": "Notice", + "slug": "notice", + "markdown_source": "../packages/components/src/notice/README.md", + "parent": "components" + }, + { + "title": "Panel", + "slug": "panel", + "markdown_source": "../packages/components/src/panel/README.md", + "parent": "components" + }, + { + "title": "Placeholder", + "slug": "placeholder", + "markdown_source": "../packages/components/src/placeholder/README.md", + "parent": "components" + }, + { + "title": "Popover", + "slug": "popover", + "markdown_source": "../packages/components/src/popover/README.md", + "parent": "components" + }, + { + "title": "HorizontalRule", + "slug": "horizontal-rule", + "markdown_source": "../packages/components/src/primitives/horizontal-rule/README.md", + "parent": "components" + }, + { + "title": "Svg", + "slug": "svg", + "markdown_source": "../packages/components/src/primitives/svg/README.md", + "parent": "components" + }, + { + "title": "QueryControls", + "slug": "query-controls", + "markdown_source": "../packages/components/src/query-controls/README.md", + "parent": "components" + }, + { + "title": "RadioControl", + "slug": "radio-control", + "markdown_source": "../packages/components/src/radio-control/README.md", + "parent": "components" + }, + { + "title": "RangeControl", + "slug": "range-control", + "markdown_source": "../packages/components/src/range-control/README.md", + "parent": "components" + }, + { + "title": "ResizableBox", + "slug": "resizable-box", + "markdown_source": "../packages/components/src/resizable-box/README.md", + "parent": "components" + }, + { + "title": "ResponsiveWrapper", + "slug": "responsive-wrapper", + "markdown_source": "../packages/components/src/responsive-wrapper/README.md", + "parent": "components" + }, + { + "title": "Sandbox", + "slug": "sandbox", + "markdown_source": "../packages/components/src/sandbox/README.md", + "parent": "components" + }, + { + "title": "ScrollLock", + "slug": "scroll-lock", + "markdown_source": "../packages/components/src/scroll-lock/README.md", + "parent": "components" + }, + { + "title": "SelectControl", + "slug": "select-control", + "markdown_source": "../packages/components/src/select-control/README.md", + "parent": "components" + }, + { + "title": "ServerSideRender", + "slug": "server-side-render", + "markdown_source": "../packages/components/src/server-side-render/README.md", + "parent": "components" + }, + { + "title": "SlotFill", + "slug": "slot-fill", + "markdown_source": "../packages/components/src/slot-fill/README.md", + "parent": "components" + }, + { + "title": "Spinner", + "slug": "spinner", + "markdown_source": "../packages/components/src/spinner/README.md", + "parent": "components" + }, + { + "title": "TabPanel", + "slug": "tab-panel", + "markdown_source": "../packages/components/src/tab-panel/README.md", + "parent": "components" + }, + { + "title": "TextControl", + "slug": "text-control", + "markdown_source": "../packages/components/src/text-control/README.md", + "parent": "components" + }, + { + "title": "TextareaControl", + "slug": "textarea-control", + "markdown_source": "../packages/components/src/textarea-control/README.md", + "parent": "components" + }, + { + "title": "ToggleControl", + "slug": "toggle-control", + "markdown_source": "../packages/components/src/toggle-control/README.md", + "parent": "components" + }, + { + "title": "Toolbar", + "slug": "toolbar", + "markdown_source": "../packages/components/src/toolbar/README.md", + "parent": "components" + }, + { + "title": "Tooltip", + "slug": "tooltip", + "markdown_source": "../packages/components/src/tooltip/README.md", + "parent": "components" + }, + { + "title": "TreeSelect", + "slug": "tree-select", + "markdown_source": "../packages/components/src/tree-select/README.md", + "parent": "components" + }, + { + "title": "Data Module Reference", + "slug": "data", + "markdown_source": "../docs/designers-developers/developers/data/README.md", + "parent": null + }, + { + "title": "WordPress Core Data", + "slug": "data-core", + "markdown_source": "../docs/designers-developers/developers/data/data-core.md", + "parent": "data" + }, + { + "title": "Annotations", + "slug": "data-core-annotations", + "markdown_source": "../docs/designers-developers/developers/data/data-core-annotations.md", + "parent": "data" + }, + { + "title": "Block Types Data", + "slug": "data-core-blocks", + "markdown_source": "../docs/designers-developers/developers/data/data-core-blocks.md", + "parent": "data" + }, + { + "title": "The Block Editor’s Data", + "slug": "data-core-block-editor", + "markdown_source": "../docs/designers-developers/developers/data/data-core-block-editor.md", + "parent": "data" + }, + { + "title": "The Post Editor’s Data", + "slug": "data-core-editor", + "markdown_source": "../docs/designers-developers/developers/data/data-core-editor.md", + "parent": "data" + }, + { + "title": "The Editor’s UI Data", + "slug": "data-core-edit-post", + "markdown_source": "../docs/designers-developers/developers/data/data-core-edit-post.md", + "parent": "data" + }, + { + "title": "Notices Data", + "slug": "data-core-notices", + "markdown_source": "../docs/designers-developers/developers/data/data-core-notices.md", + "parent": "data" + }, + { + "title": "The NUX (New User Experience) Data", + "slug": "data-core-nux", + "markdown_source": "../docs/designers-developers/developers/data/data-core-nux.md", + "parent": "data" + }, + { + "title": "The Viewport Data", + "slug": "data-core-viewport", + "markdown_source": "../docs/designers-developers/developers/data/data-core-viewport.md", + "parent": "data" + }, + { + "title": "Package Reference", + "slug": "packages", + "markdown_source": "../docs/designers-developers/developers/packages.md", + "parent": null + }, + { + "title": "@wordpress/a11y", + "slug": "packages-a11y", + "markdown_source": "../packages/a11y/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/annotations", + "slug": "packages-annotations", + "markdown_source": "../packages/annotations/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/api-fetch", + "slug": "packages-api-fetch", + "markdown_source": "../packages/api-fetch/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/autop", + "slug": "packages-autop", + "markdown_source": "../packages/autop/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/babel-plugin-import-jsx-pragma", + "slug": "packages-babel-plugin-import-jsx-pragma", + "markdown_source": "../packages/babel-plugin-import-jsx-pragma/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/babel-plugin-makepot", + "slug": "packages-babel-plugin-makepot", + "markdown_source": "../packages/babel-plugin-makepot/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/babel-preset-default", + "slug": "packages-babel-preset-default", + "markdown_source": "../packages/babel-preset-default/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/blob", + "slug": "packages-blob", + "markdown_source": "../packages/blob/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-editor", + "slug": "packages-block-editor", + "markdown_source": "../packages/block-editor/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-library", + "slug": "packages-block-library", + "markdown_source": "../packages/block-library/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-serialization-default-parser", + "slug": "packages-block-serialization-default-parser", + "markdown_source": "../packages/block-serialization-default-parser/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-serialization-spec-parser", + "slug": "packages-block-serialization-spec-parser", + "markdown_source": "../packages/block-serialization-spec-parser/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/blocks", + "slug": "packages-blocks", + "markdown_source": "../packages/blocks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/browserslist-config", + "slug": "packages-browserslist-config", + "markdown_source": "../packages/browserslist-config/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/components", + "slug": "packages-components", + "markdown_source": "../packages/components/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/compose", + "slug": "packages-compose", + "markdown_source": "../packages/compose/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/core-data", + "slug": "packages-core-data", + "markdown_source": "../packages/core-data/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/custom-templated-path-webpack-plugin", + "slug": "packages-custom-templated-path-webpack-plugin", + "markdown_source": "../packages/custom-templated-path-webpack-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/data", + "slug": "packages-data", + "markdown_source": "../packages/data/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/date", + "slug": "packages-date", + "markdown_source": "../packages/date/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dependency-extraction-webpack-plugin", + "slug": "packages-dependency-extraction-webpack-plugin", + "markdown_source": "../packages/dependency-extraction-webpack-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/deprecated", + "slug": "packages-deprecated", + "markdown_source": "../packages/deprecated/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/docgen", + "slug": "packages-docgen", + "markdown_source": "../packages/docgen/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dom-ready", + "slug": "packages-dom-ready", + "markdown_source": "../packages/dom-ready/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dom", + "slug": "packages-dom", + "markdown_source": "../packages/dom/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/e2e-test-utils", + "slug": "packages-e2e-test-utils", + "markdown_source": "../packages/e2e-test-utils/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/e2e-tests", + "slug": "packages-e2e-tests", + "markdown_source": "../packages/e2e-tests/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/edit-post", + "slug": "packages-edit-post", + "markdown_source": "../packages/edit-post/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/edit-widgets", + "slug": "packages-edit-widgets", + "markdown_source": "../packages/edit-widgets/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/editor", + "slug": "packages-editor", + "markdown_source": "../packages/editor/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/element", + "slug": "packages-element", + "markdown_source": "../packages/element/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/escape-html", + "slug": "packages-escape-html", + "markdown_source": "../packages/escape-html/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/eslint-plugin", + "slug": "packages-eslint-plugin", + "markdown_source": "../packages/eslint-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/format-library", + "slug": "packages-format-library", + "markdown_source": "../packages/format-library/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/hooks", + "slug": "packages-hooks", + "markdown_source": "../packages/hooks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/html-entities", + "slug": "packages-html-entities", + "markdown_source": "../packages/html-entities/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/i18n", + "slug": "packages-i18n", + "markdown_source": "../packages/i18n/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/is-shallow-equal", + "slug": "packages-is-shallow-equal", + "markdown_source": "../packages/is-shallow-equal/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/jest-console", + "slug": "packages-jest-console", + "markdown_source": "../packages/jest-console/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/jest-preset-default", + "slug": "packages-jest-preset-default", + "markdown_source": "../packages/jest-preset-default/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/jest-puppeteer-axe", + "slug": "packages-jest-puppeteer-axe", + "markdown_source": "../packages/jest-puppeteer-axe/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/keycodes", + "slug": "packages-keycodes", + "markdown_source": "../packages/keycodes/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/library-export-default-webpack-plugin", + "slug": "packages-library-export-default-webpack-plugin", + "markdown_source": "../packages/library-export-default-webpack-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/list-reusable-blocks", + "slug": "packages-list-reusable-blocks", + "markdown_source": "../packages/list-reusable-blocks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/notices", + "slug": "packages-notices", + "markdown_source": "../packages/notices/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/npm-package-json-lint-config", + "slug": "packages-npm-package-json-lint-config", + "markdown_source": "../packages/npm-package-json-lint-config/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/nux", + "slug": "packages-nux", + "markdown_source": "../packages/nux/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/plugins", + "slug": "packages-plugins", + "markdown_source": "../packages/plugins/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/postcss-themes", + "slug": "packages-postcss-themes", + "markdown_source": "../packages/postcss-themes/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/priority-queue", + "slug": "packages-priority-queue", + "markdown_source": "../packages/priority-queue/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/redux-routine", + "slug": "packages-redux-routine", + "markdown_source": "../packages/redux-routine/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/rich-text", + "slug": "packages-rich-text", + "markdown_source": "../packages/rich-text/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/scripts", + "slug": "packages-scripts", + "markdown_source": "../packages/scripts/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/shortcode", + "slug": "packages-shortcode", + "markdown_source": "../packages/shortcode/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/token-list", + "slug": "packages-token-list", + "markdown_source": "../packages/token-list/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/url", + "slug": "packages-url", + "markdown_source": "../packages/url/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/viewport", + "slug": "packages-viewport", + "markdown_source": "../packages/viewport/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/wordcount", + "slug": "packages-wordcount", + "markdown_source": "../packages/wordcount/README.md", + "parent": "packages" + } +] \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md index 20f6d156253889..0f7817cd0b5a3d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,22 +1,11 @@ -# Gutenberg Handbook +# Block Editor Handbook -The Gutenberg project provides three sources of documentation: +The Gutenberg project is transforming the way content is created on WordPress. A block editor was the first product launched creating a new methodology for working with content. This handbook provides documentation for how designers and developers can extend the editor, and also how you can start contributing to the project. -## Designer & Developer Handbook +![Gutenberg Demo](https://cldup.com/kZXGDcGPMU.gif) -Learn how to build blocks and extend the editor, best practices for designing block interfaces, and how to create themes that make the most of the new features Gutenberg provides. +Using a system of Blocks to compose and format content, the new block-based editor is designed to create rich, flexible layouts for websites and digital products. Content is created in the unit of blocks instead of freeform text with inserted media, embeds and Shortcodes (there's a Shortcode block though). -Start with [the Designer & Developer Handbook](/docs/designers-developers/readme.md) to learn the background and concepts, or jump straight to [Gutenberg Tutorials](/docs/designers-developers/developers/tutorials/readme.md) for detailed examples. +Blocks treat Paragraphs, Headings, Media, Embeds all as components that strung together make up the content stored in the WordPress database, replacing the traditional concept of freeform text with embedded media and shortcodes. The new editor is designed with progressive enhancement, meaning it is back-compatible with all legacy content, offers a process to try to convert and split a Classic block into block equivalents using client-side parsing and finally the blocks offer enhanced editing and format controls. -## User Handbook - -Discover the new features WordPress offers, learn how your site will be affected by the new editor and how to keep using the old interface, and tips for creating beautiful posts and pages. - -See [the WordPress Editor](https://wordpress.org/support/article/wordpress-editor/) support documentation. - - -## Contributor Handbook - -Help make Gutenberg better by contributing ideas, code, testing, and more. - -[Visit the Contributor Handbook](/docs/contributors/readme.md) +The Editor offers rich new value to users with visual, drag-and-drop creation tools and powerful developer enhancements with modern vendor packages, reusable components, rich APIs and hooks to modify and extend the editor through Custom Blocks, Custom Block Styles and Plugins. diff --git a/docs/toc.json b/docs/toc.json index e8bbe9f0a6419e..5672ab70545ad5 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -1,117 +1,115 @@ [ - {"docs/readme.md": []}, - {"docs/designers-developers/readme.md": [ - {"docs/designers-developers/key-concepts.md": []}, - {"docs/designers-developers/developers/README.md": [ - {"docs/designers-developers/developers/block-api/README.md": [ - {"docs/designers-developers/developers/block-api/block-registration.md": []}, - {"docs/designers-developers/developers/block-api/block-edit-save.md": []}, - {"docs/designers-developers/developers/block-api/block-attributes.md": []}, - {"docs/designers-developers/developers/block-api/block-deprecation.md": []}, - {"docs/designers-developers/developers/block-api/block-templates.md": []}, - {"docs/designers-developers/developers/block-api/block-annotations.md": []} - ]}, - {"docs/designers-developers/developers/filters/README.md": [ - {"docs/designers-developers/developers/filters/block-filters.md": []}, - {"docs/designers-developers/developers/filters/editor-filters.md": []}, - {"docs/designers-developers/developers/filters/parser-filters.md": []}, - {"docs/designers-developers/developers/filters/autocomplete-filters.md": []} - ]}, - {"docs/designers-developers/developers/internationalization.md": []}, - {"docs/designers-developers/developers/accessibility.md": []}, - {"docs/designers-developers/developers/feature-flags.md": []}, - {"docs/designers-developers/developers/data/README.md": [ - {"docs/designers-developers/developers/data/data-core.md": []}, - {"docs/designers-developers/developers/data/data-core-annotations.md": []}, - {"docs/designers-developers/developers/data/data-core-blocks.md": []}, - {"docs/designers-developers/developers/data/data-core-block-editor.md": []}, - {"docs/designers-developers/developers/data/data-core-editor.md": []}, - {"docs/designers-developers/developers/data/data-core-edit-post.md": []}, - {"docs/designers-developers/developers/data/data-core-notices.md": []}, - {"docs/designers-developers/developers/data/data-core-nux.md": []}, - {"docs/designers-developers/developers/data/data-core-viewport.md": []} - ]}, - {"docs/designers-developers/developers/packages.md": "{{packages}}"}, - {"packages/components/README.md": "{{components}}"}, - {"docs/designers-developers/developers/themes/README.md": [ - {"docs/designers-developers/developers/themes/theme-support.md": []} - ]}, - {"docs/designers-developers/developers/backward-compatibility/README.md": [ - {"docs/designers-developers/developers/backward-compatibility/deprecations.md": []}, - {"docs/designers-developers/developers/backward-compatibility/meta-box.md": []} - ]}, - {"docs/designers-developers/developers/tutorials/readme.md": [ - {"docs/designers-developers/developers/tutorials/javascript/readme.md": [ - {"docs/designers-developers/developers/tutorials/javascript/plugins-background.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": []}, - {"docs/designers-developers/developers/tutorials/javascript/js-build-setup.md": []} - ]}, - {"docs/designers-developers/developers/tutorials/block-tutorial/readme.md" :[ - {"docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md" :[]}, - {"docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md" :[]}, - {"docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md" :[]}, - {"docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md" :[]}, - {"docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md" :[]}, - {"docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md" :[]} - ]}, - {"docs/designers-developers/developers/tutorials/metabox/readme.md": [ - {"docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md": []}, - {"docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md": []}, - {"docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md": []}, - {"docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md": []}, - {"docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md": []} - ]}, - {"docs/designers-developers/developers/tutorials/notices/README.md": []}, - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md": [ - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": []}, - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": []}, - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md": []}, - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md": []}, - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md": []}, - {"docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": []} - ]}, - {"docs/designers-developers/developers/tutorials/format-api/README.md": [ - {"docs/designers-developers/developers/tutorials/format-api/1-register-format.md": []}, - {"docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md": []}, - {"docs/designers-developers/developers/tutorials/format-api/3-apply-format.md": []} - ]} - ]} - ]}, - {"docs/designers-developers/designers/README.md": [ - {"docs/designers-developers/designers/block-design.md": []}, - {"docs/designers-developers/designers/design-patterns.md": []}, - {"docs/designers-developers/designers/design-resources.md": []}, - {"docs/designers-developers/designers/animation.md": []} - ]} - ]}, - {"docs/contributors/readme.md": [ - {"docs/contributors/principles.md": []}, - {"docs/contributors/design.md": [ - {"docs/contributors/principles/the-block.md": []}, - {"docs/contributors/reference.md": []} - ]}, - {"docs/contributors/develop.md": [ - {"docs/contributors/getting-started.md": []}, - {"docs/contributors/git-workflow.md": []}, - {"docs/contributors/coding-guidelines.md": []}, - {"docs/contributors/testing-overview.md": []}, - {"docs/contributors/grammar.md": []}, - {"docs/contributors/scripts.md": []}, - {"docs/contributors/managing-packages.md": []}, - {"docs/contributors/release.md": []}, - {"docs/contributors/localizing.md": []} - ]}, - {"docs/contributors/document.md": [ - {"docs/contributors/copy-guide.md": []} - ]}, - {"docs/contributors/history.md": []}, - {"docs/designers-developers/glossary.md": []}, - {"docs/designers-developers/faq.md": []}, - {"docs/contributors/repository-management.md": []}, - {"docs/contributors/outreach.md": []} - ]} + { "docs/readme.md": [] }, + { "docs/designers-developers/key-concepts.md": [] }, + { "docs/designers-developers/developers/README.md": [ + { "docs/designers-developers/developers/block-api/README.md": [ + { "docs/designers-developers/developers/block-api/block-registration.md": [] }, + { "docs/designers-developers/developers/block-api/block-edit-save.md": [] }, + { "docs/designers-developers/developers/block-api/block-attributes.md": [] }, + {"docs/designers-developers/developers/block-api/block-deprecation.md": [] }, + { "docs/designers-developers/developers/block-api/block-templates.md": [] }, + { "docs/designers-developers/developers/block-api/block-annotations.md": [] } + ] }, + { "docs/designers-developers/developers/filters/README.md": [ + { "docs/designers-developers/developers/filters/block-filters.md": [] }, + { "docs/designers-developers/developers/filters/editor-filters.md": [] }, + { "docs/designers-developers/developers/filters/parser-filters.md": [] }, + { "docs/designers-developers/developers/filters/autocomplete-filters.md": [] } + ] }, + { "docs/designers-developers/developers/internationalization.md": [] }, + { "docs/designers-developers/developers/accessibility.md": [] }, + { "docs/designers-developers/developers/feature-flags.md": [] }, + { "docs/designers-developers/developers/themes/README.md": [ + { "docs/designers-developers/developers/themes/theme-support.md": [] } + ] }, + { "docs/designers-developers/developers/backward-compatibility/README.md": [ + { "docs/designers-developers/developers/backward-compatibility/deprecations.md": [] }, + { "docs/designers-developers/developers/backward-compatibility/meta-box.md": [] } + ] } + ] }, + { "docs/designers-developers/designers/README.md": [ + { "docs/designers-developers/designers/block-design.md": [] }, + { "docs/designers-developers/designers/design-patterns.md": [] }, + { "docs/designers-developers/designers/design-resources.md": [] }, + { "docs/designers-developers/designers/animation.md": [] } + ] }, + { "docs/contributors/readme.md": [ + { "docs/contributors/principles.md": [] }, + { "docs/contributors/design.md": [ + { "docs/contributors/principles/the-block.md": [] }, + { "docs/contributors/reference.md": [] } + ] }, + { "docs/contributors/develop.md": [ + { "docs/contributors/getting-started.md": [] }, + { "docs/contributors/git-workflow.md": [] }, + { "docs/contributors/coding-guidelines.md": [] }, + { "docs/contributors/testing-overview.md": [] }, + { "docs/contributors/grammar.md": [] }, + { "docs/contributors/scripts.md": [] }, + { "docs/contributors/managing-packages.md": [] }, + { "docs/contributors/release.md": [] }, + { "docs/contributors/localizing.md": [] } + ] }, + { "docs/contributors/document.md": [ + { "docs/contributors/copy-guide.md": [] } + ] }, + { "docs/contributors/history.md": [] }, + { "docs/designers-developers/glossary.md": [] }, + { "docs/designers-developers/faq.md": [] }, + { "docs/contributors/repository-management.md": [] }, + { "docs/contributors/outreach.md": [] } + ] }, + { "docs/designers-developers/developers/tutorials/readme.md": [ + { "docs/designers-developers/developers/tutorials/javascript/readme.md": [ + { "docs/designers-developers/developers/tutorials/javascript/plugins-background.md": [] }, + { "docs/designers-developers/developers/tutorials/javascript/loading-javascript.md": [] }, + { "docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md": [] }, + { "docs/designers-developers/developers/tutorials/javascript/troubleshooting.md": [] }, + { "docs/designers-developers/developers/tutorials/javascript/versions-and-building.md": [] }, + { "docs/designers-developers/developers/tutorials/javascript/scope-your-code.md": [] }, + { "docs/designers-developers/developers/tutorials/javascript/js-build-setup.md": [] } + ] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/readme.md": [ + { "docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md": [] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md": [] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md": [] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md": [] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md": [] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md": [] } + ] }, + { "docs/designers-developers/developers/tutorials/metabox/readme.md": [ + { "docs/designers-developers/developers/tutorials/metabox/meta-block-1-intro.md": [] }, + { "docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md": [] }, + { "docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md": [] }, + { "docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md": [] }, + { "docs/designers-developers/developers/tutorials/metabox/meta-block-5-finishing.md": [] } + ] }, + { "docs/designers-developers/developers/tutorials/notices/README.md": [] }, + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-0.md": [ + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-1-up-and-running.md": [] }, + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-2-styles-and-controls.md": [] }, + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md": [] }, + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-4-initialize-input.md": [] }, + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-5-update-meta.md": [] }, + { "docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-6-finishing-touches.md": [] } + ] }, + { "docs/designers-developers/developers/tutorials/format-api/README.md": [ + { "docs/designers-developers/developers/tutorials/format-api/1-register-format.md": [] }, + { "docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md": [] }, + { "docs/designers-developers/developers/tutorials/format-api/3-apply-format.md": [] } + ] } + ] }, + { "packages/components/README.md": "{{components}}" }, + { "docs/designers-developers/developers/data/README.md": [ + { "docs/designers-developers/developers/data/data-core.md": []}, + { "docs/designers-developers/developers/data/data-core-annotations.md": [] }, + { "docs/designers-developers/developers/data/data-core-blocks.md": [] }, + { "docs/designers-developers/developers/data/data-core-block-editor.md": [] }, + { "docs/designers-developers/developers/data/data-core-editor.md": [] }, + { "docs/designers-developers/developers/data/data-core-edit-post.md": [] }, + { "docs/designers-developers/developers/data/data-core-notices.md": [] }, + { "docs/designers-developers/developers/data/data-core-nux.md": [] }, + { "docs/designers-developers/developers/data/data-core-viewport.md": [] } + ] }, + { "docs/designers-developers/developers/packages.md": "{{packages}}" } ] diff --git a/docs/tool/index.js b/docs/tool/index.js index c75767b3d85d96..bb1ffdc599b104 100644 --- a/docs/tool/index.js +++ b/docs/tool/index.js @@ -12,7 +12,7 @@ const path = require( 'path' ); const { getRootManifest } = require( './manifest' ); const tocFileInput = path.resolve( __dirname, '../toc.json' ); -const manifestOutput = path.resolve( __dirname, '../manifest.json' ); +const manifestOutput = path.resolve( __dirname, '../manifest-devhub.json' ); // Update data files from code execSync( join( __dirname, 'update-data.js' ) ); diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index cb5ae4deaf17c5..80537983bf0112 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -5,7 +5,7 @@ const { camelCase, nth, upperFirst } = require( 'lodash' ); const fs = require( 'fs' ); const glob = require( 'glob' ).sync; -const baseRepoUrl = `https://raw.githubusercontent.com/WordPress/gutenberg/master`; +const baseRepoUrl = '..'; const componentPaths = glob( 'packages/components/src/*/**/README.md' ); const packagePaths = glob( 'packages/*/package.json' ).map( ( fileName ) => fileName.split( '/' )[ 1 ] diff --git a/packages/components/README.md b/packages/components/README.md index b9dd9874a5cab1..fb82b7f526f860 100644 --- a/packages/components/README.md +++ b/packages/components/README.md @@ -1,4 +1,4 @@ -# Components +# Component Reference This packages includes a library of generic WordPress components to be used for creating common UI elements shared between screens and features of the WordPress dashboard. From bbb37b16ced8827b6a3dedd9136c86a43528f17f Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <nicolai.vadim@gmail.com> Date: Tue, 14 May 2019 13:06:56 +0300 Subject: [PATCH 089/664] Accessibility: Fixed links as buttons focus state in:not(Chrome) browsers. (#15601) --- packages/components/src/button/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 5eeb1fa0f8a559..1d44f3bb9cfcb2 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -173,7 +173,7 @@ opacity: 0.3; } - &:focus:enabled { + &:focus:not(:disabled) { @include button-style__focus-active; } From 5963776099b0659b79ca62d94dd89ffd74ab7b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Tue, 14 May 2019 12:14:21 +0200 Subject: [PATCH 090/664] Input Interaction: set caret correctly on merge forward (Delete) (#15599) * Input Interaction: set caret correctly on merge forward (Delete) * Reset selected block attrs --- packages/block-editor/src/store/effects.js | 79 +++++++++++-------- .../__snapshots__/writing-flow.test.js.snap | 6 ++ packages/e2e-tests/specs/writing-flow.test.js | 12 +++ 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js index 0f5d359b89f9f0..7cd87ee731114c 100644 --- a/packages/block-editor/src/store/effects.js +++ b/packages/block-editor/src/store/effects.js @@ -70,8 +70,8 @@ export default { MERGE_BLOCKS( action, store ) { const { dispatch } = store; const state = store.getState(); - const [ firstBlockClientId, secondBlockClientId ] = action.blocks; - const blockA = getBlock( state, firstBlockClientId ); + const [ clientIdA, clientIdB ] = action.blocks; + const blockA = getBlock( state, clientIdA ); const blockAType = getBlockType( blockA.name ); // Only focus the previous block if it's not mergeable @@ -80,27 +80,33 @@ export default { return; } - // We can only merge blocks with similar types - // thus, we transform the block to merge first - const blockB = getBlock( state, secondBlockClientId ); + const blockB = getBlock( state, clientIdB ); const blockBType = getBlockType( blockB.name ); + const { clientId, attributeKey, offset } = getSelectionStart( state ); + const hasSelection = clientId === clientIdA || clientId === clientIdB; + const selectedBlock = clientId === clientIdA ? blockA : blockB; + const html = selectedBlock.attributes[ attributeKey ]; // A robust way to retain selection position through various transforms // is to insert a special character at the position and then recover it. const START_OF_SELECTED_AREA = '\u0086'; - const { attributeKey, offset } = getSelectionStart( state ); - const html = blockB.attributes[ attributeKey ]; - const multilineTagB = blockBType.attributes[ attributeKey ].multiline; - const value = insert( create( { - html, - multilineTag: multilineTagB, - } ), START_OF_SELECTED_AREA, offset, offset ); - - blockB.attributes[ attributeKey ] = toHTMLString( { - value, - multilineTag: multilineTagB, - } ); + if ( hasSelection ) { + const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; + const multilineTag = selectedBlockType.attributes[ attributeKey ].multiline; + const value = insert( create( { + html, + multilineTag, + } ), START_OF_SELECTED_AREA, offset, offset ); + + selectedBlock.attributes[ attributeKey ] = toHTMLString( { + value, + multilineTag, + } ); + } + + // We can only merge blocks with similar types + // thus, we transform the block to merge first const blocksWithTheSameType = blockA.name === blockB.name ? [ blockB ] : switchToBlockType( blockB, blockA.name ); @@ -116,24 +122,27 @@ export default { blocksWithTheSameType[ 0 ].attributes ); - const newAttributeKey = findKey( updatedAttributes, ( v ) => - typeof v === 'string' && v.indexOf( START_OF_SELECTED_AREA ) !== -1 - ); - const convertedHtml = updatedAttributes[ newAttributeKey ]; - const multilineTagA = blockAType.attributes[ newAttributeKey ].multiline; - const convertedValue = create( { html: convertedHtml, multilineTag: multilineTagA } ); - const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); - const newValue = remove( convertedValue, newOffset, newOffset + 1 ); - const newHtml = toHTMLString( { value: newValue, multilineTag: multilineTagA } ); - - updatedAttributes[ newAttributeKey ] = newHtml; - - dispatch( selectionChange( - blockA.clientId, - newAttributeKey, - newOffset, - newOffset - ) ); + if ( hasSelection ) { + const newAttributeKey = findKey( updatedAttributes, ( v ) => + typeof v === 'string' && v.indexOf( START_OF_SELECTED_AREA ) !== -1 + ); + const convertedHtml = updatedAttributes[ newAttributeKey ]; + const multilineTag = blockAType.attributes[ newAttributeKey ].multiline; + const convertedValue = create( { html: convertedHtml, multilineTag } ); + const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); + const newValue = remove( convertedValue, newOffset, newOffset + 1 ); + const newHtml = toHTMLString( { value: newValue, multilineTag } ); + + updatedAttributes[ newAttributeKey ] = newHtml; + selectedBlock.attributes[ attributeKey ] = html; + + dispatch( selectionChange( + blockA.clientId, + newAttributeKey, + newOffset, + newOffset + ) ); + } dispatch( replaceBlocks( [ blockA.clientId, blockB.clientId ], diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 0e41868f3cffa7..27806248a2a8a3 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -100,6 +100,12 @@ exports[`adding blocks should insert line break mid text 1`] = ` <!-- /wp:paragraph -->" `; +exports[`adding blocks should merge forwards 1`] = ` +"<!-- wp:paragraph --> +<p>123</p> +<!-- /wp:paragraph -->" +`; + exports[`adding blocks should navigate around inline boundaries 1`] = ` "<!-- wp:paragraph --> <p>FirstAfter</p> diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index fe801a3617284d..532e50d4e677db 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -355,4 +355,16 @@ describe( 'adding blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should merge forwards', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '3' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'Delete' ); + await page.keyboard.type( '2' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From c64faf4902dfeda9eb33301b12b4214da854d101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Tue, 14 May 2019 13:13:31 +0200 Subject: [PATCH 091/664] RichText: fix RTL keyboard interactions (#15496) --- .../src/components/rich-text/index.js | 163 +++++++++++------- .../src/components/writing-flow/index.js | 13 +- packages/dom/src/dom.js | 8 +- .../__snapshots__/rich-text.test.js.snap | 6 + .../specs/__snapshots__/rtl.test.js.snap | 63 +++++++ packages/e2e-tests/specs/rich-text.test.js | 15 ++ packages/e2e-tests/specs/rtl.test.js | 122 +++++++++++++ .../rich-text/src/register-format-type.js | 17 +- 8 files changed, 329 insertions(+), 78 deletions(-) create mode 100644 packages/e2e-tests/specs/__snapshots__/rtl.test.js.snap create mode 100644 packages/e2e-tests/specs/rtl.test.js diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 90b10613f86229..541ac81ec7a1fb 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -8,7 +8,6 @@ import { omit, pickBy, } from 'lodash'; -import memize from 'memize'; /** * WordPress dependencies @@ -88,9 +87,9 @@ const globalStyle = document.createElement( 'style' ); document.head.appendChild( globalStyle ); -function createPrepareEditableTree( props ) { +function createPrepareEditableTree( props, prefix ) { const fns = Object.keys( props ).reduce( ( accumulator, key ) => { - if ( key.startsWith( 'format_prepare_functions' ) ) { + if ( key.startsWith( prefix ) ) { accumulator.push( props[ key ] ); } @@ -145,11 +144,6 @@ export class RichText extends Component { this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); this.onPointerDown = this.onPointerDown.bind( this ); - this.formatToValue = memize( - this.formatToValue.bind( this ), - { maxSize: 1 } - ); - this.patterns = getPatterns( { onReplace, valueToFormat: this.valueToFormat, @@ -162,10 +156,11 @@ export class RichText extends Component { this.usedDeprecatedChildrenSource = Array.isArray( value ); this.lastHistoryValue = value; - // Internal values that are update synchronously, unlike props. + // Internal values are updated synchronously, unlike props and state. this.value = value; - this.selectionStart = selectionStart; - this.selectionEnd = selectionEnd; + this.record = this.formatToValue( value ); + this.record.start = selectionStart; + this.record.end = selectionEnd; } componentWillUnmount() { @@ -195,11 +190,7 @@ export class RichText extends Component { * @return {Object} The current record (value and selection). */ getRecord() { - const { value, selectionStart: start, selectionEnd: end } = this.props; - const { formats, replacements, text } = this.formatToValue( value ); - const { activeFormats } = this.state; - - return { formats, replacements, text, start, end, activeFormats }; + return this.record; } createRecord() { @@ -211,7 +202,6 @@ export class RichText extends Component { range, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - prepareEditableTree: createPrepareEditableTree( this.props ), __unstableIsEditableTree: true, } ); } @@ -222,13 +212,13 @@ export class RichText extends Component { current: this.editableRef, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, - prepareEditableTree: createPrepareEditableTree( this.props ), + prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), __unstableDomOnly: domOnly, } ); } isEmpty() { - return isEmpty( this.formatToValue( this.props.value ) ); + return isEmpty( this.record ); } /** @@ -378,7 +368,20 @@ export class RichText extends Component { } this.recalculateBoundaryStyle(); - this.onSelectionChange(); + + // We know for certain that on focus, the old selection is invalid. It + // will be recalculated on `selectionchange`. + const index = undefined; + const activeFormats = undefined; + + this.record = { + ...this.record, + start: index, + end: index, + activeFormats, + }; + this.props.onSelectionChange( index, index ); + this.setState( { activeFormats } ); document.addEventListener( 'selectionchange', this.onSelectionChange ); } @@ -419,8 +422,7 @@ export class RichText extends Component { } const value = this.createRecord(); - const { activeFormats = [] } = this.state; - const start = this.selectionStart; + const { start, activeFormats = [] } = this.record; // Update the formats between the last and new caret position. const change = updateFormats( { @@ -459,12 +461,23 @@ export class RichText extends Component { * Handles the `selectionchange` event: sync the selection to local state. */ onSelectionChange() { - const value = this.createRecord(); - const { start, end } = value; + const { start, end } = this.createRecord(); + const value = this.getRecord(); - if ( start !== this.selectionStart || end !== this.selectionEnd ) { + if ( start !== value.start || end !== value.end ) { const { isCaretWithinFormattedText } = this.props; - const activeFormats = getActiveFormats( value ); + const newValue = { + ...value, + start, + end, + // Allow `getActiveFormats` to get new `activeFormats`. + activeFormats: undefined, + }; + + const activeFormats = getActiveFormats( newValue ); + + // Update the value with the new active formats. + newValue.activeFormats = activeFormats; if ( ! isCaretWithinFormattedText && activeFormats.length ) { this.props.onEnterFormattedText(); @@ -472,11 +485,12 @@ export class RichText extends Component { this.props.onExitFormattedText(); } - this.setState( { activeFormats } ); - this.applyRecord( { ...value, activeFormats }, { domOnly: true } ); + // It is important that the internal value is updated first, + // otherwise the value will be wrong on render! + this.record = newValue; + this.applyRecord( newValue, { domOnly: true } ); this.props.onSelectionChange( start, end ); - this.selectionStart = start; - this.selectionEnd = end; + this.setState( { activeFormats } ); if ( activeFormats.length > 0 ) { this.recalculateBoundaryStyle(); @@ -521,11 +535,10 @@ export class RichText extends Component { } ); this.value = this.valueToFormat( record ); + this.record = record; this.props.onChange( this.value ); - this.setState( { activeFormats } ); this.props.onSelectionChange( start, end ); - this.selectionStart = start; - this.selectionEnd = end; + this.setState( { activeFormats } ); if ( ! withoutHistory ) { this.onCreateUndoLevel(); @@ -739,11 +752,13 @@ export class RichText extends Component { * @param {SyntheticEvent} event A synthetic keyboard event. */ handleHorizontalNavigation( event ) { - const value = this.createRecord(); - const { formats, text, start, end } = value; - const { activeFormats = [] } = this.state; + const value = this.getRecord(); + const { text, formats, start, end, activeFormats = [] } = value; const collapsed = isCollapsed( value ); - const isReverse = event.keyCode === LEFT; + // To do: ideally, we should look at visual position instead. + const { direction } = getComputedStyle( this.editableRef ); + const reverseKey = direction === 'rtl' ? RIGHT : LEFT; + const isReverse = event.keyCode === reverseKey; // If the selection is collapsed and at the very start, do nothing if // navigating backward. @@ -804,24 +819,26 @@ export class RichText extends Component { if ( newActiveFormatsLength !== activeFormats.length ) { const newActiveFormats = source.slice( 0, newActiveFormatsLength ); - this.applyRecord( { ...value, activeFormats: newActiveFormats } ); + const newValue = { ...value, activeFormats: newActiveFormats }; + this.record = newValue; + this.applyRecord( newValue ); this.setState( { activeFormats: newActiveFormats } ); return; } const newPos = value.start + ( isReverse ? -1 : 1 ); - const newActiveFormats = isReverse ? formatsBefore.length : formatsAfter.length; - - this.setState( { selectedFormat: newActiveFormats } ); - this.props.onSelectionChange( newPos, newPos ); - this.selectionStart = newPos; - this.selectionEnd = newPos; - this.applyRecord( { + const newActiveFormats = isReverse ? formatsBefore : formatsAfter; + const newValue = { ...value, start: newPos, end: newPos, activeFormats: newActiveFormats, - } ); + }; + + this.record = newValue; + this.applyRecord( newValue ); + this.props.onSelectionChange( newPos, newPos ); + this.setState( { activeFormats: newActiveFormats } ); } /** @@ -898,8 +915,7 @@ export class RichText extends Component { } componentDidUpdate( prevProps ) { - const { tagName, value, isSelected } = this.props; - const record = this.getRecord(); + const { tagName, value, selectionStart, selectionEnd, isSelected } = this.props; // Check if the content changed. let shouldReapply = ( @@ -911,8 +927,8 @@ export class RichText extends Component { // Check if the selection changed. shouldReapply = shouldReapply || ( isSelected && ! prevProps.isSelected && ( - this.selectionStart !== record.start || - this.selectionEnd !== record.end + this.record.start !== selectionStart || + this.record.end !== selectionEnd ) ); @@ -925,18 +941,32 @@ export class RichText extends Component { shouldReapply = shouldReapply || ! isShallowEqual( prepareProps, prevPrepareProps ); + const { activeFormats = [] } = this.record; + if ( shouldReapply ) { - if ( ! isSelected ) { - delete record.start; - delete record.end; - } + this.value = value; + this.record = this.formatToValue( value ); + this.record.start = selectionStart; + this.record.end = selectionEnd; + + updateFormats( { + value: this.record, + start: this.record.start, + end: this.record.end, + formats: activeFormats, + } ); - this.applyRecord( record ); + this.applyRecord( this.record ); + } else if ( + this.record.start !== selectionStart || + this.record.end !== selectionEnd + ) { + this.record = { + ...this.record, + start: selectionStart, + end: selectionEnd, + }; } - - this.value = value; - this.selectionStart = record.start; - this.selectionEnd = record.end; } /** @@ -948,19 +978,20 @@ export class RichText extends Component { formatToValue( value ) { // Handle deprecated `children` and `node` sources. if ( Array.isArray( value ) ) { - return create( { - html: children.toHTML( value ), - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - } ); + value = children.toHTML( value ); } if ( this.props.format === 'string' ) { - return create( { + const prepare = createPrepareEditableTree( this.props, 'format_value_functions' ); + + value = create( { html: value, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, } ); + value.formats = prepare( value ); + + return value; } // Guard for blocks passing `null` in onSplit callbacks. May be removed @@ -976,7 +1007,7 @@ export class RichText extends Component { return toDom( { value, multilineTag: this.multilineTag, - prepareEditableTree: createPrepareEditableTree( this.props ), + prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), } ).body.innerHTML; } diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 1acb85b1695584..656ac5b4ed49e7 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -34,7 +34,7 @@ import { * Browser constants */ -const { getSelection } = window; +const { getSelection, getComputedStyle } = window; /** * Given an element, returns true if the element is a tabbable text field, or @@ -287,6 +287,11 @@ class WritingFlow extends Component { this.verticalRect = computeCaretRect(); } + // In the case of RTL scripts, right means previous and left means next, + // which is the exact reverse of LTR. + const { direction } = getComputedStyle( target ); + const isReverseDir = direction === 'rtl' ? ( ! isReverse ) : isReverse; + if ( isShift ) { if ( ( @@ -316,9 +321,9 @@ class WritingFlow extends Component { placeCaretAtVerticalEdge( closestTabbable, isReverse, this.verticalRect ); event.preventDefault(); } - } else if ( isHorizontal && getSelection().isCollapsed && isHorizontalEdge( target, isReverse ) ) { - const closestTabbable = this.getClosestTabbable( target, isReverse ); - placeCaretAtHorizontalEdge( closestTabbable, isReverse ); + } else if ( isHorizontal && getSelection().isCollapsed && isHorizontalEdge( target, isReverseDir ) ) { + const closestTabbable = this.getClosestTabbable( target, isReverseDir ); + placeCaretAtHorizontalEdge( closestTabbable, isReverseDir ); event.preventDefault(); } } diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index b676448ec4424f..64fb97534f60a2 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -143,12 +143,16 @@ function isEdge( container, isReverse, onlyVertical ) { return true; } + // In the case of RTL scripts, the horizontal edge is at the opposite side. + const { direction } = computedStyle; + const isReverseDir = direction === 'rtl' ? ( ! isReverse ) : isReverse; + // To calculate the horizontal position, we insert a test range and see if // this test range has the same horizontal position. This method proves to // be better than a DOM-based calculation, because it ignores empty text // nodes and a trailing line break element. In other words, we need to check // visual positioning, not DOM positioning. - const x = isReverse ? containerRect.left + 1 : containerRect.right - 1; + const x = isReverseDir ? containerRect.left + 1 : containerRect.right - 1; const y = isReverse ? containerRect.top + buffer : containerRect.bottom - buffer; const testRange = hiddenCaretRangeFromPoint( document, x, y, container ); @@ -156,7 +160,7 @@ function isEdge( container, isReverse, onlyVertical ) { return false; } - const side = isReverse ? 'left' : 'right'; + const side = isReverseDir ? 'left' : 'right'; const testRect = getRectangleFromRange( testRange ); return Math.round( testRect[ side ] ) === Math.round( rangeRect[ side ] ); diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap index bf5fefd31a7e32..8aab0dcd5b734b 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -24,6 +24,12 @@ exports[`RichText should apply multiple formats when selection is collapsed 1`] <!-- /wp:paragraph -->" `; +exports[`RichText should handle Home and End keys 1`] = ` +"<!-- wp:paragraph --> +<p>-<strong>12</strong>+</p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should handle change in tag name gracefully 1`] = ` "<!-- wp:heading {\\"level\\":3} --> <h3></h3> diff --git a/packages/e2e-tests/specs/__snapshots__/rtl.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rtl.test.js.snap new file mode 100644 index 00000000000000..19b4e8c305acc2 --- /dev/null +++ b/packages/e2e-tests/specs/__snapshots__/rtl.test.js.snap @@ -0,0 +1,63 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RTL should arrow navigate 1`] = ` +"<!-- wp:paragraph --> +<p>٠١٢</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should arrow navigate between blocks 1`] = ` +"<!-- wp:paragraph --> +<p>٠<br>١</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>٠<br>١<br>٢</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should merge backward 1`] = ` +"<!-- wp:paragraph --> +<p>٠١</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should merge forward 1`] = ` +"<!-- wp:paragraph --> +<p>٠١</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should navigate inline boundaries 1`] = ` +"<!-- wp:paragraph --> +<p><strong>١</strong>٠٢</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should navigate inline boundaries 2`] = ` +"<!-- wp:paragraph --> +<p><strong>١٠</strong>٢</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should navigate inline boundaries 3`] = ` +"<!-- wp:paragraph --> +<p><strong>٠١</strong>٢</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should navigate inline boundaries 4`] = ` +"<!-- wp:paragraph --> +<p>٠<strong>١</strong>٢</p> +<!-- /wp:paragraph -->" +`; + +exports[`RTL should split 1`] = ` +"<!-- wp:paragraph --> +<p>٠</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>١</p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index dcb1434abd89be..1bd6d79c1b10fc 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -208,4 +208,19 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should handle Home and End keys', async () => { + await page.keyboard.press( 'Enter' ); + + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '12' ); + await pressKeyWithModifier( 'primary', 'b' ); + + await page.keyboard.press( 'Home' ); + await page.keyboard.type( '-' ); + await page.keyboard.press( 'End' ); + await page.keyboard.type( '+' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/rtl.test.js b/packages/e2e-tests/specs/rtl.test.js new file mode 100644 index 00000000000000..76e6d15ff9aa30 --- /dev/null +++ b/packages/e2e-tests/specs/rtl.test.js @@ -0,0 +1,122 @@ +/** + * WordPress dependencies + */ +import { + createNewPost, + getEditedPostContent, + pressKeyWithModifier, +} from '@wordpress/e2e-test-utils'; + +// Avoid using three, as it looks too much like two with some fonts. +const ARABIC_ZERO = '٠'; +const ARABIC_ONE = '١'; +const ARABIC_TWO = '٢'; + +describe( 'RTL', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'should arrow navigate', async () => { + await page.evaluate( () => document.dir = 'rtl' ); + await page.keyboard.press( 'Enter' ); + + // We need at least three characters as arrow navigation *from* the + // edges might be handled differently. + await page.keyboard.type( ARABIC_ONE ); + await page.keyboard.type( ARABIC_TWO ); + await page.keyboard.press( 'ArrowRight' ); + // This is the important key press: arrow nav *from* the middle. + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( ARABIC_ZERO ); + + // Expect: ARABIC_ZERO + ARABIC_ONE + ARABIC_TWO (<p>٠١٢</p>). + // N.b.: HTML is LTR, so direction will be reversed! + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should split', async () => { + await page.evaluate( () => document.dir = 'rtl' ); + await page.keyboard.press( 'Enter' ); + + await page.keyboard.type( ARABIC_ZERO ); + await page.keyboard.type( ARABIC_ONE ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should merge backward', async () => { + await page.evaluate( () => document.dir = 'rtl' ); + await page.keyboard.press( 'Enter' ); + + await page.keyboard.type( ARABIC_ZERO ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( ARABIC_ONE ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should merge forward', async () => { + await page.evaluate( () => document.dir = 'rtl' ); + await page.keyboard.press( 'Enter' ); + + await page.keyboard.type( ARABIC_ZERO ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( ARABIC_ONE ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'Delete' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should arrow navigate between blocks', async () => { + await page.evaluate( () => document.dir = 'rtl' ); + await page.keyboard.press( 'Enter' ); + + await page.keyboard.type( ARABIC_ZERO ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( ARABIC_ONE ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( ARABIC_TWO ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'ArrowRight' ); + + // Move to the previous block with two lines in the current block. + await page.keyboard.press( 'ArrowRight' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( ARABIC_ONE ); + + // Move to the next block with two lines in the current block. + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.type( ARABIC_ZERO ); + await pressKeyWithModifier( 'shift', 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should navigate inline boundaries', async () => { + await page.evaluate( () => document.dir = 'rtl' ); + await page.keyboard.press( 'Enter' ); + + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( ARABIC_ONE ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( ARABIC_TWO ); + + // Insert a character at each boundary position. + for ( let i = 4; i > 0; i-- ) { + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( ARABIC_ZERO ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); + } + } ); +} ); diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js index eb6d48c9e56368..e6efc99f5fec97 100644 --- a/packages/rich-text/src/register-format-type.js +++ b/packages/rich-text/src/register-format-type.js @@ -146,18 +146,23 @@ export function registerFormatType( name, settings ) { blockClientId: props.clientId, }; - newProps[ `format_prepare_functions_(${ name })` ] = - settings.__experimentalCreatePrepareEditableTree( - propsByPrefix, - args - ); - if ( settings.__experimentalCreateOnChangeEditableValue ) { + newProps[ `format_value_functions_(${ name })` ] = + settings.__experimentalCreatePrepareEditableTree( + propsByPrefix, + args + ); newProps[ `format_on_change_functions_(${ name })` ] = settings.__experimentalCreateOnChangeEditableValue( propsByPrefix, args ); + } else { + newProps[ `format_prepare_functions_(${ name })` ] = + settings.__experimentalCreatePrepareEditableTree( + propsByPrefix, + args + ); } return <OriginalComponent { ...newProps } />; From 13a6b263d6dcc765152bf894533f9a0debcd0ca9 Mon Sep 17 00:00:00 2001 From: Thorsten Frommen <info@tfrommen.de> Date: Tue, 14 May 2019 13:45:34 +0200 Subject: [PATCH 092/664] Remove (non-special) comment nodes when pasting content (#15557) --- .../src/api/raw-handling/comment-remover.js | 21 +++++++++ .../src/api/raw-handling/paste-handler.js | 4 +- .../api/raw-handling/test/comment-remover.js | 43 +++++++++++++++++++ test/integration/blocks-raw-handling.spec.js | 2 + test/integration/fixtures/google-docs-in.html | 2 +- .../fixtures/google-docs-table-in.html | 2 +- .../google-docs-table-with-comments-in.html | 4 ++ .../google-docs-table-with-comments-out.html | 3 ++ .../google-docs-with-comments-in.html | 4 ++ .../google-docs-with-comments-out.html | 35 +++++++++++++++ 10 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 packages/blocks/src/api/raw-handling/comment-remover.js create mode 100644 packages/blocks/src/api/raw-handling/test/comment-remover.js create mode 100644 test/integration/fixtures/google-docs-table-with-comments-in.html create mode 100644 test/integration/fixtures/google-docs-table-with-comments-out.html create mode 100644 test/integration/fixtures/google-docs-with-comments-in.html create mode 100644 test/integration/fixtures/google-docs-with-comments-out.html diff --git a/packages/blocks/src/api/raw-handling/comment-remover.js b/packages/blocks/src/api/raw-handling/comment-remover.js new file mode 100644 index 00000000000000..fbfa319ccb8316 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/comment-remover.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { remove } from '@wordpress/dom'; + +/** + * Browser dependencies + */ +const { COMMENT_NODE } = window.Node; + +/** + * Looks for comments, and removes them. + * + * @param {Node} node The node to be processed. + * @return {void} + */ +export default function( node ) { + if ( node.nodeType === COMMENT_NODE ) { + remove( node ); + } +} diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index 1d67540c0b9225..c99b4ea5c5d0a2 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -11,6 +11,7 @@ import { getBlockContent } from '../serializer'; import { getBlockAttributes, parseWithGrammar } from '../parser'; import normaliseBlocks from './normalise-blocks'; import specialCommentConverter from './special-comment-converter'; +import commentRemover from './comment-remover'; import isInlineContent from './is-inline-content'; import phrasingContentReducer from './phrasing-content-reducer'; import headRemover from './head-remover'; @@ -44,7 +45,7 @@ const { console } = window; * @return {string} HTML only containing phrasing content. */ function filterInlineHTML( HTML ) { - HTML = deepFilterHTML( HTML, [ googleDocsUIDRemover, phrasingContentReducer ] ); + HTML = deepFilterHTML( HTML, [ googleDocsUIDRemover, phrasingContentReducer, commentRemover ] ); HTML = removeInvalidHTML( HTML, getPhrasingContentSchema(), { inline: true } ); // Allows us to ask for this information when we get a report. @@ -204,6 +205,7 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam imageCorrector, phrasingContentReducer, specialCommentConverter, + commentRemover, figureContentReducer, blockquoteNormaliser, ]; diff --git a/packages/blocks/src/api/raw-handling/test/comment-remover.js b/packages/blocks/src/api/raw-handling/test/comment-remover.js new file mode 100644 index 00000000000000..6721e20bec23b6 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/test/comment-remover.js @@ -0,0 +1,43 @@ +/** + * Internal dependencies + */ +import commentRemover from '../comment-remover'; +import { deepFilterHTML } from '../utils'; + +describe( 'commentRemover', () => { + it( 'should remove a single comment', () => { + expect( deepFilterHTML( + '<!-- Comment -->', + [ commentRemover ] + ) ).toEqual( + '' + ); + } ); + it( 'should remove multiple comments', () => { + expect( deepFilterHTML( + '<!-- First comment --><p>First paragraph.</p><!-- Second comment --><p>Second paragraph.</p><!-- Third comment -->', + [ commentRemover ] + ) ).toEqual( + '<p>First paragraph.</p><p>Second paragraph.</p>' + ); + } ); + it( 'should remove nested comments', () => { + expect( deepFilterHTML( + '<p>Paragraph.<!-- Comment --></p>', + [ commentRemover ] + ) ).toEqual( + '<p>Paragraph.</p>' + ); + } ); + it( 'should remove multi-line comments', () => { + expect( deepFilterHTML( + `<p>First paragraph.</p><!-- + Multi-line + comment + --><p>Second paragraph.</p>`, + [ commentRemover ] + ) ).toEqual( + '<p>First paragraph.</p><p>Second paragraph.</p>' + ); + } ); +} ); diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js index 83315ba0d38a68..fda576a042cbb3 100644 --- a/test/integration/blocks-raw-handling.spec.js +++ b/test/integration/blocks-raw-handling.spec.js @@ -236,6 +236,8 @@ describe( 'Blocks raw handling', () => { 'apple', 'google-docs', 'google-docs-table', + 'google-docs-table-with-comments', + 'google-docs-with-comments', 'ms-word', 'ms-word-styled', 'ms-word-online', diff --git a/test/integration/fixtures/google-docs-in.html b/test/integration/fixtures/google-docs-in.html index c667fddceab447..e70f79825bf305 100644 --- a/test/integration/fixtures/google-docs-in.html +++ b/test/integration/fixtures/google-docs-in.html @@ -1 +1 @@ -<b id="docs-internal-guid-23a0eab1-7fff-c4e2-3a94-7d2570556656" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; font-weight: normal;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 3pt;"><span style="font-size: 26pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is a </span><span style="font-size: 26pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">title</span></p><br><h2 dir="ltr" style="line-height: 1.38; margin-top: 18pt; margin-bottom: 4pt;"><span style="font-size: 17pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is a </span><span style="font-size: 17pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">heading</span></h2><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Formatting test: </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">bold</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">italic</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><a href="https://w.org/" style="text-decoration: none;"><span style="font-size: 11pt; font-family: Arial; color: rgb(17, 85, 204); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: underline; -webkit-text-decoration-skip: none; vertical-align: baseline; white-space: pre-wrap;">link</span></a><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: line-through; -webkit-text-decoration-skip: none; vertical-align: baseline; white-space: pre-wrap;">strikethrough</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 6.6pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: super; white-space: pre-wrap;">superscript</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 6.6pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: sub; white-space: pre-wrap;">subscript</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">nested</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">.</span></p><br><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A</span></p></li><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Bulleted</span></p></li><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: circle; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Indented</span></p></li></ul><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">List</span></p></li></ul><br><ol style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></li><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></li><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></li></ol><br><div dir="ltr" style="margin-left: 0pt;"><table style="border: none; border-collapse: collapse; width: 451.27559055118115pt;"><colgroup><col width="*"><col width="*"><col width="*"></colgroup><tbody><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></td></tr><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">1</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">2</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">3</span></p></td></tr><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">II</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">III</span></p></td></tr></tbody></table></div><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"></p><hr><p></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">An image:</span></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><img src="https://lh4.googleusercontent.com/ID" width="544" height="184" style="border: none; transform: rotate(0rad);"></span></p></b><br class="Apple-interchange-newline" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;"> \ No newline at end of file +<meta charset="utf-8"><b id="docs-internal-guid-23a0eab1-7fff-c4e2-3a94-7d2570556656" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; font-weight: normal;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 3pt;"><span style="font-size: 26pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is a </span><span style="font-size: 26pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">title</span></p><br><h2 dir="ltr" style="line-height: 1.38; margin-top: 18pt; margin-bottom: 4pt;"><span style="font-size: 17pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is a </span><span style="font-size: 17pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">heading</span></h2><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Formatting test: </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">bold</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">italic</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><a href="https://w.org/" style="text-decoration: none;"><span style="font-size: 11pt; font-family: Arial; color: rgb(17, 85, 204); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: underline; -webkit-text-decoration-skip: none; vertical-align: baseline; white-space: pre-wrap;">link</span></a><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: line-through; -webkit-text-decoration-skip: none; vertical-align: baseline; white-space: pre-wrap;">strikethrough</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 6.6pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: super; white-space: pre-wrap;">superscript</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 6.6pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: sub; white-space: pre-wrap;">subscript</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">nested</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">.</span></p><br><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A</span></p></li><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Bulleted</span></p></li><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: circle; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Indented</span></p></li></ul><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">List</span></p></li></ul><br><ol style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></li><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></li><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></li></ol><br><div dir="ltr" style="margin-left: 0pt;"><table style="border: none; border-collapse: collapse; width: 451.27559055118115pt;"><colgroup><col width="*"><col width="*"><col width="*"></colgroup><tbody><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></td></tr><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">1</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">2</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">3</span></p></td></tr><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">II</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">III</span></p></td></tr></tbody></table></div><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"></p><hr><p></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">An image:</span></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><img src="https://lh4.googleusercontent.com/ID" width="544" height="184" style="border: none; transform: rotate(0rad);"></span></p></b><br class="Apple-interchange-newline" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;"> \ No newline at end of file diff --git a/test/integration/fixtures/google-docs-table-in.html b/test/integration/fixtures/google-docs-table-in.html index 8a6b117fa6ed55..ad03a2dff05172 100644 --- a/test/integration/fixtures/google-docs-table-in.html +++ b/test/integration/fixtures/google-docs-table-in.html @@ -1 +1 @@ - <meta charset='utf-8'><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-7102d5c2-7fff-c8d1-1082-5abceee52545"><br /><div dir="ltr" style="margin-left:0pt;"><table style="border:none;border-collapse:collapse;width:451.27559055118115pt"><colgroup><col width="*" /><col width="*" /><col width="*" /></colgroup><tr style="height:3.75pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">One</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Two</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Three</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">I</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">II</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">III</span></p></td></tr></table></div></b> +<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-7102d5c2-7fff-c8d1-1082-5abceee52545"><br /><div dir="ltr" style="margin-left:0pt;"><table style="border:none;border-collapse:collapse;width:451.27559055118115pt"><colgroup><col width="*" /><col width="*" /><col width="*" /></colgroup><tr style="height:3.75pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">One</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Two</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Three</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">I</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">II</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">III</span></p></td></tr></table></div></b> \ No newline at end of file diff --git a/test/integration/fixtures/google-docs-table-with-comments-in.html b/test/integration/fixtures/google-docs-table-with-comments-in.html new file mode 100644 index 00000000000000..a23b54c17a5e33 --- /dev/null +++ b/test/integration/fixtures/google-docs-table-with-comments-in.html @@ -0,0 +1,4 @@ +<html><body> +<!--StartFragment--><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-7102d5c2-7fff-c8d1-1082-5abceee52545"><br /><div dir="ltr" style="margin-left:0pt;"><table style="border:none;border-collapse:collapse;width:451.27559055118115pt"><colgroup><col width="*" /><col width="*" /><col width="*" /></colgroup><tr style="height:3.75pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">One</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Two</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Three</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">1</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">2</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">3</span></p></td></tr><tr style="height:0pt"><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">I</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">II</span></p></td><td style="border-left:solid #000000 1pt;border-right:solid #000000 1pt;border-bottom:solid #000000 1pt;border-top:solid #000000 1pt;vertical-align:top;padding:5pt 5pt 5pt 5pt;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">III</span></p></td></tr></table></div></b> +</body> +</html> \ No newline at end of file diff --git a/test/integration/fixtures/google-docs-table-with-comments-out.html b/test/integration/fixtures/google-docs-table-with-comments-out.html new file mode 100644 index 00000000000000..697c2d41ea5cd9 --- /dev/null +++ b/test/integration/fixtures/google-docs-table-with-comments-out.html @@ -0,0 +1,3 @@ +<!-- wp:table --> +<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<!-- /wp:table --> diff --git a/test/integration/fixtures/google-docs-with-comments-in.html b/test/integration/fixtures/google-docs-with-comments-in.html new file mode 100644 index 00000000000000..e838e0198fe8f4 --- /dev/null +++ b/test/integration/fixtures/google-docs-with-comments-in.html @@ -0,0 +1,4 @@ +<html><body> +<!--StartFragment--><meta charset="utf-8"><b id="docs-internal-guid-23a0eab1-7fff-c4e2-3a94-7d2570556656" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; font-weight: normal;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 3pt;"><span style="font-size: 26pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is a </span><span style="font-size: 26pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">title</span></p><br><h2 dir="ltr" style="line-height: 1.38; margin-top: 18pt; margin-bottom: 4pt;"><span style="font-size: 17pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">This is a </span><span style="font-size: 17pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">heading</span></h2><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Formatting test: </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">bold</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">italic</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><a href="https://w.org/" style="text-decoration: none;"><span style="font-size: 11pt; font-family: Arial; color: rgb(17, 85, 204); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: underline; -webkit-text-decoration-skip: none; vertical-align: baseline; white-space: pre-wrap;">link</span></a><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: line-through; -webkit-text-decoration-skip: none; vertical-align: baseline; white-space: pre-wrap;">strikethrough</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 6.6pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: super; white-space: pre-wrap;">superscript</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 6.6pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: sub; white-space: pre-wrap;">subscript</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">, </span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 700; font-style: italic; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">nested</span><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">.</span></p><br><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">A</span></p></li><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Bulleted</span></p></li><ul style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: circle; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Indented</span></p></li></ul><li dir="ltr" style="list-style-type: disc; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">List</span></p></li></ul><br><ol style="margin-top: 0pt; margin-bottom: 0pt;"><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></li><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></li><li dir="ltr" style="list-style-type: decimal; font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></li></ol><br><div dir="ltr" style="margin-left: 0pt;"><table style="border: none; border-collapse: collapse; width: 451.27559055118115pt;"><colgroup><col width="*"><col width="*"><col width="*"></colgroup><tbody><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">One</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Two</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">Three</span></p></td></tr><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">1</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">2</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">3</span></p></td></tr><tr style="height: 0pt;"><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">I</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">II</span></p></td><td style="border: 1pt solid rgb(0, 0, 0); vertical-align: top; padding: 5pt;"><p dir="ltr" style="line-height: 1.2; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">III</span></p></td></tr></tbody></table></div><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"></p><hr><p></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">An image:</span></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;"><img src="https://lh4.googleusercontent.com/ID" width="544" height="184" style="border: none; transform: rotate(0rad);"></span></p></b><br class="Apple-interchange-newline" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -webkit-standard; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none;"> +</body> +</html> \ No newline at end of file diff --git a/test/integration/fixtures/google-docs-with-comments-out.html b/test/integration/fixtures/google-docs-with-comments-out.html new file mode 100644 index 00000000000000..7733ca660bdd02 --- /dev/null +++ b/test/integration/fixtures/google-docs-with-comments-out.html @@ -0,0 +1,35 @@ +<!-- wp:paragraph --> +<p>This is a <strong>title</strong><br></p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>This is a <em>heading</em></h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Formatting test: <strong>bold</strong>, <em>italic</em>, <a href="https://w.org/">link</a>, strikethrough, <sup>superscript</sup>, <sub>subscript</sub>, <strong><em>nested</em></strong>.<br></p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>A</li><li>Bulleted<ul><li>Indented</li></ul></li><li>List</li></ul> +<!-- /wp:list --> + +<!-- wp:list {"ordered":true} --> +<ol><li>One</li><li>Two</li><li>Three</li></ol> +<!-- /wp:list --> + +<!-- wp:table --> +<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<!-- /wp:table --> + +<!-- wp:separator --> +<hr class="wp-block-separator"/> +<!-- /wp:separator --> + +<!-- wp:paragraph --> +<p>An image:<br></p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="https://lh4.googleusercontent.com/ID" alt=""/></figure> +<!-- /wp:image --> From f9151c8e776f13366521e5b172777e9c455fae2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Tue, 14 May 2019 14:41:35 +0200 Subject: [PATCH 093/664] RichText: show boundary only with editable element focus (#15466) --- .../src/components/rich-text/editable.js | 15 +++++++++---- .../src/components/rich-text/index.js | 21 +++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/editable.js b/packages/block-editor/src/components/rich-text/editable.js index 58c7217e1944c4..c7c9a854fc0b8d 100644 --- a/packages/block-editor/src/components/rich-text/editable.js +++ b/packages/block-editor/src/components/rich-text/editable.js @@ -83,7 +83,10 @@ function applyInternetExplorerInputFix( editorNode ) { } const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; -const CLASS_NAME = 'editor-rich-text__editable block-editor-rich-text__editable'; + +const oldClassName = 'editor-rich-text__editable'; + +export const className = 'block-editor-rich-text__editable'; /** * Whether or not the user agent is Internet Explorer. @@ -116,7 +119,11 @@ export default class Editable extends Component { } if ( ! isEqual( this.props.className, nextProps.className ) ) { - this.editorNode.className = classnames( nextProps.className, CLASS_NAME ); + this.editorNode.className = classnames( + className, + oldClassName, + nextProps.className + ); } const { removedKeys, updatedKeys } = diffAriaProps( this.props, nextProps ); @@ -156,7 +163,7 @@ export default class Editable extends Component { style, record, valueToEditableHTML, - className, + className: additionalClassName, isPlaceholderVisible, ...remainingProps } = this.props; @@ -166,7 +173,7 @@ export default class Editable extends Component { return createElement( tagName, { role: 'textbox', 'aria-multiline': true, - className: classnames( className, CLASS_NAME ), + className: classnames( className, oldClassName, additionalClassName ), contentEditable: true, [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, ref: this.bindEditorNode, diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 541ac81ec7a1fb..e8dad655cbaada 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -52,7 +52,7 @@ import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; import FormatEdit from './format-edit'; import FormatToolbar from './format-toolbar'; -import Editable from './editable'; +import Editable, { className as editableClassName } from './editable'; import { pickAriaProps } from './aria'; import { getPatterns } from './patterns'; import { withBlockEditContext } from '../block-edit/context'; @@ -502,15 +502,18 @@ export class RichText extends Component { const boundarySelector = '*[data-rich-text-format-boundary]'; const element = this.editableRef.querySelector( boundarySelector ); - if ( element ) { - const computedStyle = getComputedStyle( element ); - const newColor = computedStyle.color - .replace( ')', ', 0.2)' ) - .replace( 'rgb', 'rgba' ); - - globalStyle.innerHTML = - `*:focus ${ boundarySelector }{background-color: ${ newColor }}`; + if ( ! element ) { + return; } + + const computedStyle = getComputedStyle( element ); + const newColor = computedStyle.color + .replace( ')', ', 0.2)' ) + .replace( 'rgb', 'rgba' ); + const selector = `.${ editableClassName }:focus ${ boundarySelector }`; + const rule = `background-color: ${ newColor }`; + + globalStyle.innerHTML = `${ selector } {${ rule }}`; } /** From 9683d000ab5aa25263e64feb5a0570820e72f8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 14 May 2019 15:01:51 +0200 Subject: [PATCH 094/664] Input Interaction: always reset initial vertical position rect (#15624) --- .../src/components/writing-flow/index.js | 18 ++++++++----- .../__snapshots__/writing-flow.test.js.snap | 20 ++++++++++++++ packages/e2e-tests/specs/writing-flow.test.js | 27 +++++++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 656ac5b4ed49e7..f4c4844f5bf722 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -241,6 +241,18 @@ class WritingFlow extends Component { const hasModifier = isShift || event.ctrlKey || event.altKey || event.metaKey; const isNavEdge = isVertical ? isVerticalEdge : isHorizontalEdge; + // When presing any key other than up or down, the initial vertical + // position must ALWAYS be reset. The vertical position is saved so it + // can be restored as well as possible on sebsequent vertical arrow key + // presses. It may not always be possible to restore the exact same + // position (such as at an empty line), so it wouldn't be good to + // compute the position right before any vertical arrow key press. + if ( ! isVertical ) { + this.verticalRect = null; + } else if ( ! this.verticalRect ) { + this.verticalRect = computeCaretRect(); + } + // This logic inside this condition needs to be checked before // the check for event.nativeEvent.defaultPrevented. // The logic handles meta+a keypress and this event is default prevented @@ -281,12 +293,6 @@ class WritingFlow extends Component { return; } - if ( ! isVertical ) { - this.verticalRect = null; - } else if ( ! this.verticalRect ) { - this.verticalRect = computeCaretRect(); - } - // In the case of RTL scripts, right means previous and left means next, // which is the exact reverse of LTR. const { direction } = getComputedStyle( target ); diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 27806248a2a8a3..7852dacfc4174f 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -203,3 +203,23 @@ exports[`adding blocks should not prematurely multi-select 1`] = ` <p>></p> <!-- /wp:paragraph -->" `; + +exports[`adding blocks should preserve horizontal position when navigating vertically between blocks 1`] = ` +"<!-- wp:paragraph --> +<p>abc</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>123</p> +<!-- /wp:paragraph -->" +`; + +exports[`adding blocks should remember initial vertical position 1`] = ` +"<!-- wp:paragraph --> +<p>1x</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><br>2</p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index 532e50d4e677db..5bcf9869c86469 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -367,4 +367,31 @@ describe( 'adding blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should preserve horizontal position when navigating vertically between blocks', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'abc' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '23' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowDown' ); + await page.keyboard.type( '1' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should remember initial vertical position', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.type( 'x' ); // Should be right after "1". + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 94d311cf0e51abc337eabd35cb8c74e1c6973e7e Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Tue, 14 May 2019 09:41:42 -0400 Subject: [PATCH 095/664] Replace nextpage icon with a Material version. (#15627) --- packages/block-library/src/nextpage/icon.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/nextpage/icon.js b/packages/block-library/src/nextpage/icon.js index c4abf194ec7596..a69aac47adf07c 100644 --- a/packages/block-library/src/nextpage/icon.js +++ b/packages/block-library/src/nextpage/icon.js @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { G, Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; export default ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><G><Path d="M9 12h6v-2H9zm-7 0h5v-2H2zm15 0h5v-2h-5zm3 2v2l-6 6H6a2 2 0 0 1-2-2v-6h2v6h6v-4a2 2 0 0 1 2-2h6zM4 8V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4h-2V4H6v4z" /></G></SVG> + <SVG xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24"><Path d="M0 0h24v24H0z" fill="none" /><Path d="M9 11h6v2H9zM2 11h5v2H2zM17 11h5v2h-5zM6 4h7v5h7V8l-6-6H6a2 2 0 0 0-2 2v5h2zM18 20H6v-5H4v5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5h-2z" /></SVG> ); From 8de2ba501b77bc36a535cf4dfbfc4bf1aa7dbc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 14 May 2019 16:58:11 +0200 Subject: [PATCH 096/664] Bump version number to 5.7.0-rc.1 (#15632) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 80d8252a4fed3d..6d49c7dba65ac6 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.6.1 + * Version: 5.7.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index e7af47a4f0dbdf..25bfdd2ef9e859 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.6.1", + "version": "5.7.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ef38c002ece9ca..67d2e21445635c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.6.1", + "version": "5.7.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 8bc45fa922bbdc10b4e60268ea65359de7e05ffc Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Tue, 14 May 2019 17:04:47 +0200 Subject: [PATCH 097/664] Content structure: Fix Semantics (#15474) * use list markup * remove extra margin that was added with <ul> usage * remove <li> margin * use h2 for heading * add focusable wrapper with note role, add list role to <ul> --- .../src/components/table-of-contents/panel.js | 61 +++++++++++-------- .../components/table-of-contents/style.scss | 12 ++-- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index 7c956651af3199..cd88216810d1ee 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -12,46 +12,57 @@ import DocumentOutline from '../document-outline'; function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled, onRequestClose } ) { return ( + /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ <> <div - className="table-of-contents__counts" + className="table-of-contents__wrapper" role="note" aria-label={ __( 'Document Statistics' ) } tabIndex="0" > - <div className="table-of-contents__count"> - { __( 'Words' ) } - <WordCount /> - </div> - <div className="table-of-contents__count"> - { __( 'Headings' ) } - <span className="table-of-contents__number"> - { headingCount } - </span> - </div> - <div className="table-of-contents__count"> - { __( 'Paragraphs' ) } - <span className="table-of-contents__number"> - { paragraphCount } - </span> - </div> - <div className="table-of-contents__count"> - { __( 'Blocks' ) } - <span className="table-of-contents__number"> - { numberOfBlocks } - </span> - </div> + <ul + role="list" + className="table-of-contents__counts" + > + <li className="table-of-contents__count"> + { __( 'Words' ) } + <WordCount /> + </li> + <li className="table-of-contents__count"> + { __( 'Headings' ) } + <span className="table-of-contents__number"> + { headingCount } + </span> + </li> + <li className="table-of-contents__count"> + { __( 'Paragraphs' ) } + <span className="table-of-contents__number"> + { paragraphCount } + </span> + </li> + <li className="table-of-contents__count"> + { __( 'Blocks' ) } + <span className="table-of-contents__number"> + { numberOfBlocks } + </span> + </li> + </ul> </div> { headingCount > 0 && ( <> <hr /> - <span className="table-of-contents__title"> + <h2 className="table-of-contents__title"> { __( 'Document Outline' ) } - </span> + </h2> <DocumentOutline onSelect={ onRequestClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> </> ) } </> + /* eslint-enable jsx-a11y/no-redundant-roles */ ); } diff --git a/packages/editor/src/components/table-of-contents/style.scss b/packages/editor/src/components/table-of-contents/style.scss index bd748416c6b8ab..2365aa4c534387 100644 --- a/packages/editor/src/components/table-of-contents/style.scss +++ b/packages/editor/src/components/table-of-contents/style.scss @@ -17,14 +17,15 @@ } } +.table-of-contents__wrapper:focus { + @include square-style__focus(); + outline-offset: $grid-size; +} + .table-of-contents__counts { display: flex; flex-wrap: wrap; - - &:focus { - @include square-style__focus(); - outline-offset: $grid-size; - } + margin: 0; } .table-of-contents__count { @@ -33,6 +34,7 @@ flex-direction: column; font-size: $default-font-size; color: $dark-gray-300; + margin-bottom: 0; } .table-of-contents__number, From 5bbda3656a530616a7a78c0a101d6ec2d8fa6a7a Mon Sep 17 00:00:00 2001 From: Suzen Fylke <codesue@users.noreply.github.com> Date: Tue, 14 May 2019 12:16:19 -0400 Subject: [PATCH 098/664] changed inline @link tag to block-level @see tag (#15616) --- .../src/components/rich-text/index.js | 4 +-- packages/blocks/src/api/parser.js | 4 +-- packages/blocks/src/api/validation.js | 8 +++--- packages/components/src/disabled/index.js | 2 +- .../src/test/fixtures/tags-function/code.js | 2 +- .../src/press-key-with-modifier.js | 4 +-- .../hooks/components/media-upload/index.js | 2 +- packages/escape-html/src/index.js | 12 ++++---- packages/redux-routine/src/is-generator.js | 2 +- packages/token-list/src/index.js | 28 +++++++++---------- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index e8dad655cbaada..a34e315adefac1 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -68,7 +68,7 @@ const { getSelection, getComputedStyle } = window; /** * All inserting input types that would insert HTML into the DOM. * - * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes + * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes * * @type {Set} */ @@ -563,7 +563,7 @@ export class RichText extends Component { * selection where caret is at directional edge: forward for a delete key, * reverse for a backspace key. * - * @link https://en.wikipedia.org/wiki/Caret_navigation + * @see https://en.wikipedia.org/wiki/Caret_navigation * * @param {KeyboardEvent} event Keydown event. */ diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 2132f6fa45f4f3..162c5cec83c508 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -120,7 +120,7 @@ export function isOfTypes( value, types ) { * Returns true if value is valid per the given block attribute schema type * definition, or false otherwise. * - * @link https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1 + * @see https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.1 * * @param {*} value Value to test. * @param {?(Array<string>|string)} type Block attribute schema type. @@ -135,7 +135,7 @@ export function isValidByType( value, type ) { * Returns true if value is valid per the given block attribute schema enum * definition, or false otherwise. * - * @link https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.2 + * @see https://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.2 * * @param {*} value Value to test. * @param {?Array} enumSet Block attribute schema enum. diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index 50692a7414eff6..7105b5bba1890f 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -170,8 +170,8 @@ const TEXT_NORMALIZATIONS = [ * references.every( ( reference ) => /^[\da-z]+$/i.test( reference ) ) * ``` * - * @link https://html.spec.whatwg.org/multipage/syntax.html#character-references - * @link https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references + * @see https://html.spec.whatwg.org/multipage/syntax.html#character-references + * @see https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references * * @type {RegExp} */ @@ -183,7 +183,7 @@ const REGEXP_NAMED_CHARACTER_REFERENCE = /^[\da-z]+$/i; * "The ampersand must be followed by a U+0023 NUMBER SIGN character (#), * followed by one or more ASCII digits, representing a base-ten integer" * - * @link https://html.spec.whatwg.org/multipage/syntax.html#character-references + * @see https://html.spec.whatwg.org/multipage/syntax.html#character-references * * @type {RegExp} */ @@ -197,7 +197,7 @@ const REGEXP_DECIMAL_CHARACTER_REFERENCE = /^#\d+$/; * U+0058 LATIN CAPITAL LETTER X character (X), which must then be followed by * one or more ASCII hex digits, representing a hexadecimal integer" * - * @link https://html.spec.whatwg.org/multipage/syntax.html#character-references + * @see https://html.spec.whatwg.org/multipage/syntax.html#character-references * * @type {RegExp} */ diff --git a/packages/components/src/disabled/index.js b/packages/components/src/disabled/index.js index d335c6553c036b..73c9ecc9349d81 100644 --- a/packages/components/src/disabled/index.js +++ b/packages/components/src/disabled/index.js @@ -17,7 +17,7 @@ const { Consumer, Provider } = createContext( false ); * * See WHATWG HTML Standard: 4.10.18.5: "Enabling and disabling form controls: the disabled attribute". * - * @link https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute + * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#enabling-and-disabling-form-controls:-the-disabled-attribute * * @type {string[]} */ diff --git a/packages/docgen/src/test/fixtures/tags-function/code.js b/packages/docgen/src/test/fixtures/tags-function/code.js index 8d4261e0f63289..cbde0e0b9b0456 100644 --- a/packages/docgen/src/test/fixtures/tags-function/code.js +++ b/packages/docgen/src/test/fixtures/tags-function/code.js @@ -5,7 +5,7 @@ * @since v2 * * @see addition - * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators * * @param {number} firstParam The first param to add. * @param {number} secondParam The second param to add. diff --git a/packages/e2e-test-utils/src/press-key-with-modifier.js b/packages/e2e-test-utils/src/press-key-with-modifier.js index 59f5c8330672ec..1989f0d7236ffd 100644 --- a/packages/e2e-test-utils/src/press-key-with-modifier.js +++ b/packages/e2e-test-utils/src/press-key-with-modifier.js @@ -16,8 +16,8 @@ import { modifiers, SHIFT, ALT, CTRL } from '@wordpress/keycodes'; * that any `Event#preventDefault` which would have normally occurred in the * application as a result of Ctrl+A is respected. * - * @link https://github.com/GoogleChrome/puppeteer/issues/1313 - * @link https://w3c.github.io/uievents/tools/key-event-viewer.html + * @see https://github.com/GoogleChrome/puppeteer/issues/1313 + * @see https://w3c.github.io/uievents/tools/key-event-viewer.html * * @return {Promise} Promise resolving once the SelectAll emulation completes. */ diff --git a/packages/edit-post/src/hooks/components/media-upload/index.js b/packages/edit-post/src/hooks/components/media-upload/index.js index eefb3450a6e1c4..f97b6b1f432985 100644 --- a/packages/edit-post/src/hooks/components/media-upload/index.js +++ b/packages/edit-post/src/hooks/components/media-upload/index.js @@ -16,7 +16,7 @@ const getGalleryDetailsMediaFrame = () => { /** * Custom gallery details frame. * - * @link https://github.com/xwp/wp-core-media-widgets/blob/905edbccfc2a623b73a93dac803c5335519d7837/wp-admin/js/widgets/media-gallery-widget.js + * @see https://github.com/xwp/wp-core-media-widgets/blob/905edbccfc2a623b73a93dac803c5335519d7837/wp-admin/js/widgets/media-gallery-widget.js * @class GalleryDetailsMediaFrame * @constructor */ diff --git a/packages/escape-html/src/index.js b/packages/escape-html/src/index.js index c29efb94bd2d04..d31f43f63f70bb 100644 --- a/packages/escape-html/src/index.js +++ b/packages/escape-html/src/index.js @@ -10,7 +10,7 @@ import __unstableEscapeGreaterThan from './escape-greater'; * U+0020 SPACE, U+0022 ("), U+0027 ('), U+003E (>), U+002F (/), U+003D (=), * and noncharacters." * - * @link https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + * @see https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 * * @type {RegExp} */ @@ -22,9 +22,9 @@ const REGEXP_INVALID_ATTRIBUTE_NAME = /[\u007F-\u009F "'>/="\uFDD0-\uFDEF]/; * named, decimal, or hexadecimal character references are escaped. Invalid * named references (i.e. ambiguous ampersand) are are still permitted. * - * @link https://w3c.github.io/html/syntax.html#character-references - * @link https://w3c.github.io/html/syntax.html#ambiguous-ampersand - * @link https://w3c.github.io/html/syntax.html#named-character-references + * @see https://w3c.github.io/html/syntax.html#character-references + * @see https://w3c.github.io/html/syntax.html#ambiguous-ampersand + * @see https://w3c.github.io/html/syntax.html#named-character-references * * @param {string} value Original string. * @@ -59,7 +59,7 @@ export function escapeLessThan( value ) { /** * Returns an escaped attribute value. * - * @link https://w3c.github.io/html/syntax.html#elements-attributes + * @see https://w3c.github.io/html/syntax.html#elements-attributes * * "[...] the text cannot contain an ambiguous ampersand [...] must not contain * any literal U+0022 QUOTATION MARK characters (")" @@ -83,7 +83,7 @@ export function escapeAttribute( value ) { /** * Returns an escaped HTML element value. * - * @link https://w3c.github.io/html/syntax.html#writing-html-documents-elements + * @see https://w3c.github.io/html/syntax.html#writing-html-documents-elements * * "the text must not contain the character U+003C LESS-THAN SIGN (<) or an * ambiguous ampersand." diff --git a/packages/redux-routine/src/is-generator.js b/packages/redux-routine/src/is-generator.js index 70ce6002f1a102..dcddb9d59fc665 100644 --- a/packages/redux-routine/src/is-generator.js +++ b/packages/redux-routine/src/is-generator.js @@ -1,7 +1,7 @@ /** * Returns true if the given object is a generator, or false otherwise. * - * @link https://www.ecma-international.org/ecma-262/6.0/#sec-generator-objects + * @see https://www.ecma-international.org/ecma-262/6.0/#sec-generator-objects * * @param {*} object Object to test. * diff --git a/packages/token-list/src/index.js b/packages/token-list/src/index.js index b959c331e79af7..acf8b79b68dacd 100644 --- a/packages/token-list/src/index.js +++ b/packages/token-list/src/index.js @@ -6,7 +6,7 @@ import { uniq, compact, without } from 'lodash'; /** * A set of tokens. * - * @link https://dom.spec.whatwg.org/#domtokenlist + * @see https://dom.spec.whatwg.org/#domtokenlist */ export default class TokenList { /** @@ -27,7 +27,7 @@ export default class TokenList { /** * Returns the associated set as string. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-value + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-value * * @return {string} Token set as string. */ @@ -38,7 +38,7 @@ export default class TokenList { /** * Replaces the associated set with a new string value. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-value + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-value * * @param {string} value New token set as string. */ @@ -51,7 +51,7 @@ export default class TokenList { /** * Returns the number of tokens. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-length + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-length * * @return {number} Number of tokens. */ @@ -62,8 +62,8 @@ export default class TokenList { /** * Returns the stringified form of the TokenList. * - * @link https://dom.spec.whatwg.org/#DOMTokenList-stringification-behavior - * @link https://www.ecma-international.org/ecma-262/9.0/index.html#sec-tostring + * @see https://dom.spec.whatwg.org/#DOMTokenList-stringification-behavior + * @see https://www.ecma-international.org/ecma-262/9.0/index.html#sec-tostring * * @return {string} Token set as string. */ @@ -74,7 +74,7 @@ export default class TokenList { /** * Returns an iterator for the TokenList, iterating items of the set. * - * @link https://dom.spec.whatwg.org/#domtokenlist + * @see https://dom.spec.whatwg.org/#domtokenlist * * @return {Generator} TokenList iterator. */ @@ -85,7 +85,7 @@ export default class TokenList { /** * Returns the token with index `index`. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-item + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-item * * @param {number} index Index at which to return token. * @@ -98,7 +98,7 @@ export default class TokenList { /** * Returns true if `token` is present, and false otherwise. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-contains + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-contains * * @param {string} item Token to test. * @@ -111,7 +111,7 @@ export default class TokenList { /** * Adds all arguments passed, except those already present. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-add + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-add * * @param {...string} items Items to add. */ @@ -122,7 +122,7 @@ export default class TokenList { /** * Removes arguments passed, if they are present. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-remove + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-remove * * @param {...string} items Items to remove. */ @@ -136,7 +136,7 @@ export default class TokenList { * as add()). If force is false, removes token (same as remove()). Returns * true if `token` is now present, and false otherwise. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-toggle + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-toggle * * @param {string} token Token to toggle. * @param {?boolean} force Presence to force. @@ -161,7 +161,7 @@ export default class TokenList { * Replaces `token` with `newToken`. Returns true if `token` was replaced * with `newToken`, and false otherwise. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-replace + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-replace * * @param {string} token Token to replace with `newToken`. * @param {string} newToken Token to use in place of `token`. @@ -185,7 +185,7 @@ export default class TokenList { * * Always returns `true` in this implementation. * - * @link https://dom.spec.whatwg.org/#dom-domtokenlist-supports + * @see https://dom.spec.whatwg.org/#dom-domtokenlist-supports * * @return {boolean} Whether token is supported. */ From 761543e9f092e04fcdbb32c1d99afd8fb3d11624 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Wed, 15 May 2019 12:55:12 +0200 Subject: [PATCH 099/664] [RN Mobile] Fix crash when merging block (#15392) Upgrade RichText on mobile native to use the latest changes from the web, mainly those made in #14640 It also removes the customization we made to the rich-text library since writing in multiple formats at once is now stable on the web. * Adding `onSelectionChange` from store to RichText * First experiment fixing selection change handling in rich text * Update readme change postcommit * Fix sequence of events on split and selection change * Remove commented onTextUpdate call on enter pressed * Do not reset activeFormats when onSelectionChange is emitted while typing * Update value from props * Pinpoint conditions for the case where onSelectionChange is emitted without changing selection * Improve isTyping detection * Fix applying link format * Do not try to update text from the paste handler * onChange should update the state * Selection change events always fire after text already changed * Fix issue where the list block was not being selected on focus. * Add isSelected prop from context to RichText * Fix move caret to the end when merging. This avoid moving the caret to the end when two blocks, both with content, are merged. * Caret position from props, don't force to end * Fix lint errors * WIP: more fixes * WIP: Fix list external Enter key on Android * No stray onSelectionChanged events from AztecAndroid * Only block html elements will be trimmed by AztecAndroid * Mirror caret state on text change * Fix issue on iOS where the format buttons were not working properly. Pressing on the buttons didn't change the format of the selected text. * Restore iOS autoscroll while editing. * Cleanup * Try a new approach to detect if the selection change event is manual or not * Fix iOS issue with formats, where pressing the format buttons sometimes won't change the format of the selected text --- .../src/components/rich-text/index.native.js | 417 ++++++++++-------- .../block-library/src/heading/edit.native.js | 68 +-- .../src/paragraph/edit.native.js | 1 + .../format-library/src/link/modal.native.js | 7 +- packages/rich-text/src/apply-format.native.js | 83 ---- .../rich-text/src/get-active-format.native.js | 44 -- .../rich-text/src/remove-format.native.js | 70 --- 7 files changed, 258 insertions(+), 432 deletions(-) delete mode 100644 packages/rich-text/src/apply-format.native.js delete mode 100644 packages/rich-text/src/get-active-format.native.js delete mode 100644 packages/rich-text/src/remove-format.native.js diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index aca01b3897556f..007a612c948264 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -5,6 +5,10 @@ */ import RCTAztecView from 'react-native-aztec'; import { View, Platform } from 'react-native'; +import { + pickBy, +} from 'lodash'; +import memize from 'memize'; /** * WordPress dependencies @@ -12,10 +16,11 @@ import { View, Platform } from 'react-native'; import { Component, RawHTML } from '@wordpress/element'; import { withInstanceId, compose } from '@wordpress/compose'; import { BlockFormatControls } from '@wordpress/block-editor'; -import { withSelect } from '@wordpress/data'; +import { withSelect, withDispatch } from '@wordpress/data'; import { applyFormat, getActiveFormat, + __unstableGetActiveFormats as getActiveFormats, isEmpty, create, split, @@ -24,7 +29,6 @@ import { __unstableInsertLineSeparator as insertLineSeparator, __unstableIsEmptyLine as isEmptyLine, isCollapsed, - getTextContent, } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { BACKSPACE } from '@wordpress/keycodes'; @@ -77,7 +81,7 @@ const gutenbergFormatNamesToAztec = { }; export class RichText extends Component { - constructor( { multiline } ) { + constructor( { value, multiline, selectionStart, selectionEnd } ) { super( ...arguments ); this.isMultiline = false; @@ -97,28 +101,43 @@ export class RichText extends Component { } this.isIOS = Platform.OS === 'ios'; + this.createRecord = this.createRecord.bind( this ); this.onChange = this.onChange.bind( this ); this.onEnter = this.onEnter.bind( this ); this.onBackspace = this.onBackspace.bind( this ); this.onPaste = this.onPaste.bind( this ); this.onFocus = this.onFocus.bind( this ); this.onBlur = this.onBlur.bind( this ); + this.onTextUpdate = this.onTextUpdate.bind( this ); this.onContentSizeChange = this.onContentSizeChange.bind( this ); this.onFormatChangeForceChild = this.onFormatChangeForceChild.bind( this ); this.onFormatChange = this.onFormatChange.bind( this ); + this.formatToValue = memize( + this.formatToValue.bind( this ), + { maxSize: 1 } + ); + // This prevents a bug in Aztec which triggers onSelectionChange twice on format change this.onSelectionChange = this.onSelectionChange.bind( this ); + this.onSelectionChangeFromAztec = this.onSelectionChangeFromAztec.bind( this ); this.valueToFormat = this.valueToFormat.bind( this ); this.willTrimSpaces = this.willTrimSpaces.bind( this ); this.state = { - start: 0, - end: 0, - formatPlaceholder: null, + activeFormats: [], + selectedFormat: null, height: 0, }; this.needsSelectionUpdate = false; this.savedContent = ''; this.isTouched = false; + this.lastAztecEventType = null; + + this.lastHistoryValue = value; + + // Internal values that are update synchronously, unlike props. + this.value = value; + this.selectionStart = selectionStart; + this.selectionEnd = selectionEnd; } /** @@ -127,9 +146,8 @@ export class RichText extends Component { * @return {Object} The current record (value and selection). */ getRecord() { - const { formatPlaceholder, start, end } = this.state; - - let value = this.props.value === undefined ? null : this.props.value; + const { selectionStart: start, selectionEnd: end } = this.props; + let { value } = this.props; // Since we get the text selection from Aztec we need to be in sync with the HTML `value` // Removing leading white spaces using `trim()` should make sure this is the case. @@ -138,8 +156,34 @@ export class RichText extends Component { } const { formats, replacements, text } = this.formatToValue( value ); + const { activeFormats } = this.state; - return { formats, replacements, formatPlaceholder, text, start, end }; + return { formats, replacements, text, start, end, activeFormats }; + } + + /** + * Creates a RichText value "record" from native content and selection + * information + * + * @param {string} currentContent The content (usually an HTML string) from + * the native component. + * @param {number} selectionStart The start of the selection. + * @param {number} selectionEnd The end of the selection (same as start if + * cursor instead of selection). + * + * @return {Object} A RichText value with formats and selection. + */ + createRecord() { + return { + start: this.selectionStart, + end: this.selectionEnd, + ...create( { + html: this.value, + range: null, + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + } ), + }; } /* @@ -215,41 +259,42 @@ export class RichText extends Component { this.onFormatChange( record, true ); } - onFormatChange( record, doUpdateChild ) { - let newContent; - // valueToFormat might throw when converting the record to a tree structure - // let's ignore the event for now and force a render update so we're still in sync - try { - newContent = this.valueToFormat( record ); - } catch ( error ) { - // eslint-disable-next-line no-console - console.log( error ); - } - this.setState( { - formatPlaceholder: record.formatPlaceholder, + onFormatChange( record ) { + const { start, end, activeFormats = [] } = record; + const changeHandlers = pickBy( this.props, ( v, key ) => + key.startsWith( 'format_on_change_functions_' ) + ); + + Object.values( changeHandlers ).forEach( ( changeHandler ) => { + changeHandler( record.formats, record.text ); } ); - if ( newContent && newContent !== this.props.value ) { - this.props.onChange( newContent ); - if ( record.needsSelectionUpdate && record.start && record.end && doUpdateChild ) { - this.forceSelectionUpdate( record.start, record.end ); - } - } else { - if ( doUpdateChild ) { - this.lastEventCount = undefined; - } else { - // make sure the component rerenders without refreshing the text on gutenberg - // (this can trigger other events that might update the active formats on aztec) - this.lastEventCount = 0; - } - this.forceUpdate(); + + this.value = this.valueToFormat( record ); + this.props.onChange( this.value ); + this.setState( { activeFormats } ); + this.props.onSelectionChange( start, end ); + this.selectionStart = start; + this.selectionEnd = end; + + this.onCreateUndoLevel(); + + this.lastAztecEventType = 'format change'; + } + + onCreateUndoLevel() { + // If the content is the same, no level needs to be created. + if ( this.lastHistoryValue === this.value ) { + return; } + + this.props.onCreateUndoLevel(); + this.lastHistoryValue = this.value; } /* * Cleans up any root tags produced by aztec. * TODO: This should be removed on a later version when aztec doesn't return the top tag of the text being edited */ - removeRootTagsProduceByAztec( html ) { let result = this.removeRootTag( this.props.tagName, html ); // Temporary workaround for https://github.com/WordPress/gutenberg/pull/13763 @@ -271,21 +316,37 @@ export class RichText extends Component { * Handles any case where the content of the AztecRN instance has changed */ onChange( event ) { - this.lastEventCount = event.nativeEvent.eventCount; const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); - this.lastContent = contentWithoutRootTag; + // On iOS, onChange can be triggered after selection changes, even though there are no content changes. + if ( contentWithoutRootTag === this.value ) { + return; + } + this.lastEventCount = event.nativeEvent.eventCount; this.comesFromAztec = true; this.firedAfterTextChanged = true; // the onChange event always fires after the fact - this.props.onChange( this.lastContent ); + this.onTextUpdate( event ); + this.lastAztecEventType = 'input'; } - /** + onTextUpdate( event ) { + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + + const refresh = this.value !== contentWithoutRootTag; + this.value = contentWithoutRootTag; + + // we don't want to refresh if our goal is just to create a record + if ( refresh ) { + this.props.onChange( contentWithoutRootTag ); + } + } + + /* * Handles any case where the content of the AztecRN instance has changed in size */ - onContentSizeChange( contentSize ) { const contentHeight = contentSize.height; this.setState( { height: contentHeight } ); + this.lastAztecEventType = 'content size change'; } // eslint-disable-next-line no-unused-vars @@ -294,30 +355,28 @@ export class RichText extends Component { this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; - const currentRecord = this.createRecord( { - ...event.nativeEvent, - currentContent: unescapeSpaces( event.nativeEvent.text ), - } ); + const currentRecord = this.createRecord(); if ( this.multilineTag ) { if ( event.shiftKey ) { - const insertedLineBreak = { needsSelectionUpdate: true, ...insert( currentRecord, '\n' ) }; + this.needsSelectionUpdate = true; + const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; this.onFormatChangeForceChild( insertedLineBreak ); } else if ( this.onSplit && isEmptyLine( currentRecord ) ) { - this.setState( { - needsSelectionUpdate: false, - } ); this.splitContent( currentRecord ); } else { - const insertedLineSeparator = { needsSelectionUpdate: true, ...insertLineSeparator( currentRecord ) }; + this.needsSelectionUpdate = true; + const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; this.onFormatChange( insertedLineSeparator, ! this.firedAfterTextChanged ); } } else if ( event.shiftKey || ! this.onSplit ) { - const insertedLineBreak = { needsSelectionUpdate: true, ...insert( currentRecord, '\n' ) }; + this.needsSelectionUpdate = true; + const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; this.onFormatChangeForceChild( insertedLineBreak ); } else { this.splitContent( currentRecord ); } + this.lastAztecEventType = 'input'; } // eslint-disable-next-line no-unused-vars @@ -343,6 +402,9 @@ export class RichText extends Component { if ( onRemove && empty && isReverse ) { onRemove( ! isReverse ); } + + event.preventDefault(); + this.lastAztecEventType = 'input'; } /** @@ -355,7 +417,7 @@ export class RichText extends Component { const { onSplit } = this.props; const { pastedText, pastedHtml, files } = event.nativeEvent; - const currentRecord = this.createRecord( event.nativeEvent ); + const currentRecord = this.createRecord(); event.preventDefault(); @@ -396,8 +458,8 @@ export class RichText extends Component { href: decodeEntities( trimmedText ), }, } ); - this.lastContent = this.valueToFormat( linkedRecord ); - this.props.onChange( this.lastContent ); + this.value = this.valueToFormat( linkedRecord ); + this.props.onChange( this.value ); // Allows us to ask for this information when we get a report. window.console.log( 'Created link:\n\n', trimmedText ); @@ -428,12 +490,14 @@ export class RichText extends Component { const recordToInsert = create( { html: pastedContent } ); const insertedContent = insert( currentRecord, recordToInsert ); const newContent = this.valueToFormat( insertedContent ); - this.lastContent = newContent; + + this.lastEventCount = undefined; + this.value = newContent; // explicitly set selection after inline paste this.forceSelectionUpdate( insertedContent.start, insertedContent.end ); - this.props.onChange( this.lastContent ); + this.props.onChange( this.value ); } else if ( onSplit ) { if ( ! pastedContent.length ) { return; @@ -447,12 +511,14 @@ export class RichText extends Component { } } - onFocus( event ) { + onFocus() { this.isTouched = true; if ( this.props.onFocus ) { - this.props.onFocus( event ); + this.props.onFocus(); } + + this.lastAztecEventType = 'focus'; } onBlur( event ) { @@ -461,76 +527,61 @@ export class RichText extends Component { if ( this.props.onBlur ) { this.props.onBlur( event ); } + + this.lastAztecEventType = 'blur'; + } + + onSelectionChange( start, end ) { + const hasChanged = this.selectionStart !== start || this.selectionEnd !== end; + + this.selectionStart = start; + this.selectionEnd = end; + + // This is a manual selection change event if onChange was not triggered just before + // and we did not just trigger a text update + // `onChange` could be the last event and could have been triggered a long time ago so + // this approach is not perfectly reliable + const isManual = this.lastAztecEventType !== 'input' && this.props.value === this.value; + if ( hasChanged && isManual ) { + const value = this.createRecord(); + const activeFormats = getActiveFormats( value ); + this.setState( { activeFormats } ); + } + + this.props.onSelectionChange( start, end ); } - onSelectionChange( start, end, text, event ) { + onSelectionChangeFromAztec( start, end, text, event ) { // `end` can be less than `start` on iOS // Let's fix that here so `rich-text/slice` can work properly const realStart = Math.min( start, end ); const realEnd = Math.max( start, end ); - const noChange = this.state.start === start && this.state.end === end; - const isTyping = this.state.start + 1 === realStart; - const shouldKeepFormats = noChange || isTyping; - // update format placeholder to continue writing in the current format - // or set it to null if user jumped to another part in the text - const formatPlaceholder = shouldKeepFormats && this.state.formatPlaceholder ? { - ...this.state.formatPlaceholder, - index: realStart, - } : null; - this.setState( { - start: realStart, - end: realEnd, - formatPlaceholder, - } ); - this.lastEventCount = event.nativeEvent.eventCount; - // Make sure there are changes made to the content before upgrading it upward - const newContent = this.removeRootTagsProduceByAztec( unescapeSpaces( text ) ); - if ( this.lastContent !== newContent ) { - // we don't want to refresh aztec native as no content can have changed from this event - // let's update lastContent to prevent that in shouldComponentUpdate - this.lastContent = newContent; - this.comesFromAztec = true; - this.firedAfterTextChanged = true; // Selection change event always fires after the fact - this.props.onChange( this.lastContent ); + // check and dicsard stray event, where the text and selection is equal to the ones already cached + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + if ( contentWithoutRootTag === this.value && realStart === this.selectionStart && realEnd === this.selectionEnd ) { + return; } + + this.comesFromAztec = true; + this.firedAfterTextChanged = true; // Selection change event always fires after the fact + + // update text before updating selection + // Make sure there are changes made to the content before upgrading it upward + this.onTextUpdate( event ); + + this.onSelectionChange( realStart, realEnd ); + + // Update lastEventCount to prevent Aztec from re-rendering the content it just sent + this.lastEventCount = event.nativeEvent.eventCount; + + this.lastAztecEventType = 'selection change'; } isEmpty() { return isEmpty( this.formatToValue( this.props.value ) ); } - /** - * Creates a RichText value "record" from native content and selection - * information - * - * @param {string} currentContent The content (usually an HTML string) from - * the native component. - * @param {number} selectionStart The start of the selection. - * @param {number} selectionEnd The end of the selection (same as start if - * cursor instead of selection). - * - * @return {Object} A RichText value with formats and selection. - */ - createRecord( { currentContent, selectionStart, selectionEnd } ) { - // strip outer <p> tags - const innerContent = this.removeRootTagsProduceByAztec( currentContent ); - - // create record (with selection) from current contents - const currentRecord = { - start: selectionStart, - end: selectionEnd, - ...create( { - html: innerContent, - range: null, - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - } ), - }; - - return currentRecord; - } - formatToValue( value ) { // Handle deprecated `children` and `node` sources. if ( Array.isArray( value ) ) { @@ -559,46 +610,25 @@ export class RichText extends Component { } componentWillReceiveProps( nextProps ) { - this.moveCaretToTheEndIfNeeded( nextProps ); - } - - moveCaretToTheEndIfNeeded( nextProps ) { - const nextRecord = this.formatToValue( nextProps.value ); - const nextTextContent = getTextContent( nextRecord ); - - if ( this.isTouched || ! nextProps.isSelected ) { - this.savedContent = nextTextContent; - return; - } - - if ( nextTextContent === '' && this.savedContent === '' ) { - return; - } - - // This logic will handle the selection when two blocks are merged or when block is split - // into two blocks - if ( nextTextContent.startsWith( this.savedContent ) && this.savedContent && this.savedContent.length > 0 ) { - let length = this.savedContent.length; - if ( length === 0 && nextTextContent !== this.props.value ) { - length = this.props.value.length; - } - - this.forceSelectionUpdate( length, length ); - this.savedContent = nextTextContent; + if ( nextProps.value !== this.value ) { + this.value = this.props.value; + this.lastEventCount = undefined; } } forceSelectionUpdate( start, end ) { if ( ! this.needsSelectionUpdate ) { this.needsSelectionUpdate = true; - this.setState( { start, end } ); + this.selectionStart = start; + this.selectionEnd = end; + this.forceUpdate(); } } shouldComponentUpdate( nextProps ) { if ( nextProps.tagName !== this.props.tagName ) { this.lastEventCount = undefined; - this.lastContent = undefined; + this.value = undefined; return true; } @@ -608,21 +638,28 @@ export class RichText extends Component { // If the component is changed React side (undo/redo/merging/splitting/custom text actions) // we need to make sure the native is updated as well. - const previousValueToCheck = Platform.OS === 'android' ? this.props.value : this.lastContent; - // Also, don't trust the "this.lastContent" as on Android, incomplete text events arrive // with only some of the text, while the virtual keyboard's suggestion system does its magic. // ** compare with this.lastContent for optimizing performance by not forcing Aztec with text it already has // , but compare with props.value to not lose "half word" text because of Android virtual keyb autosuggestion behavior if ( ( typeof nextProps.value !== 'undefined' ) && ( typeof this.props.value !== 'undefined' ) && - ( Platform.OS === 'ios' || ( Platform.OS === 'android' && ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) ) ) && - nextProps.value !== previousValueToCheck ) { + ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) && + nextProps.value !== this.props.value ) { + // Gutenberg seems to try to mirror the caret state even on events that only change the content so, + // let's force caret update if state has selection set. + if ( typeof nextProps.selectionStart !== 'undefined' && typeof nextProps.selectionEnd !== 'undefined' ) { + this.needsSelectionUpdate = true; + } + this.lastEventCount = undefined; // force a refresh on the native side } - if ( Platform.OS === 'android' && this.comesFromAztec === false ) { - if ( this.needsSelectionUpdate ) { + if ( ! this.comesFromAztec ) { + if ( nextProps.selectionStart !== this.props.selectionStart && + nextProps.selectionStart !== this.selectionStart && + nextProps.isSelected ) { + this.needsSelectionUpdate = true; this.lastEventCount = undefined; // force a refresh on the native side } } @@ -633,6 +670,7 @@ export class RichText extends Component { componentDidMount() { if ( this.props.isSelected ) { this._editor.focus(); + this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } } @@ -645,15 +683,19 @@ export class RichText extends Component { componentDidUpdate( prevProps ) { if ( this.props.isSelected && ! prevProps.isSelected ) { this._editor.focus(); + // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange + // if its internal value hasn't change. When created, default value is 0, 0 + this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } else if ( ! this.props.isSelected && prevProps.isSelected && this.isIOS ) { this._editor.blur(); } } willTrimSpaces( html ) { - // regex for detecting spaces around html tags - const trailingSpaces = /(\s+)<.+?>|<.+?>(\s+)/g; - const matches = html.match( trailingSpaces ); + // regex for detecting spaces around block element html tags + const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; + const leadingOrTrailingSpaces = new RegExp( `(\\s+)<\/?${ blockHtmlElements }>|<\/?${ blockHtmlElements }>(\\s+)`, 'g' ); + const matches = html.match( leadingOrTrailingSpaces ); if ( matches && matches.length > 0 ) { return true; } @@ -697,7 +739,7 @@ export class RichText extends Component { // So, skip forcing it, let Aztec just do its best and just log the fact. console.warn( 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' ); } else { - selection = { start: this.state.start, end: this.state.end }; + selection = { start: this.props.selectionStart, end: this.props.selectionEnd }; } } @@ -746,7 +788,7 @@ export class RichText extends Component { activeFormats={ this.getActiveFormatNames( record ) } onContentSizeChange={ this.onContentSizeChange } onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } - onSelectionChange={ this.onSelectionChange } + onSelectionChange={ this.onSelectionChangeFromAztec } isSelected={ isSelected } blockType={ { tag: tagName } } color={ 'black' } @@ -771,35 +813,58 @@ RichText.defaultProps = { const RichTextContainer = compose( [ withInstanceId, - withSelect( ( select ) => { + withBlockEditContext( ( { clientId, onFocus, isSelected, onCaretVerticalPositionChange }, ownProps ) => { + return { + clientId, + isSelected, + onFocus: onFocus || ownProps.onFocus, + onCaretVerticalPositionChange, + }; + } ), + withSelect( ( select, { + clientId, + instanceId, + identifier = instanceId, + isSelected, + } ) => { const { getFormatTypes } = select( 'core/rich-text' ); + const { + getSelectionStart, + getSelectionEnd, + } = select( 'core/block-editor' ); + + const selectionStart = getSelectionStart(); + const selectionEnd = getSelectionEnd(); + + if ( isSelected === undefined ) { + isSelected = ( + selectionStart.clientId === clientId && + selectionStart.attributeKey === identifier + ); + } return { formatTypes: getFormatTypes(), + selectionStart: isSelected ? selectionStart.offset : undefined, + selectionEnd: isSelected ? selectionEnd.offset : undefined, + isSelected, }; } ), - withBlockEditContext( ( context, ownProps ) => { - // When explicitly set as not selected, do nothing. - if ( ownProps.isSelected === false ) { - return { - clientId: context.clientId, - }; - } - // When explicitly set as selected, use the value stored in the context instead. - if ( ownProps.isSelected === true ) { - return { - isSelected: context.isSelected, - clientId: context.clientId, - onCaretVerticalPositionChange: context.onCaretVerticalPositionChange, - }; - } - - // Ensures that only one RichText component can be focused. + withDispatch( ( dispatch, { + clientId, + instanceId, + identifier = instanceId, + } ) => { + const { + __unstableMarkLastChangeAsPersistent, + selectionChange, + } = dispatch( 'core/block-editor' ); + return { - clientId: context.clientId, - isSelected: context.isSelected, - onFocus: context.onFocus || ownProps.onFocus, - onCaretVerticalPositionChange: context.onCaretVerticalPositionChange, + onCreateUndoLevel: __unstableMarkLastChangeAsPersistent, + onSelectionChange( start, end ) { + selectionChange( clientId, identifier, start, end ); + }, }; } ), ] )( RichText ); diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index 59cc25d8bd4c3e..ded76af565b632 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -19,61 +19,7 @@ import { RichText, BlockControls } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { create } from '@wordpress/rich-text'; -const name = 'core/heading'; - class HeadingEdit extends Component { - constructor( props ) { - super( props ); - - this.splitBlock = this.splitBlock.bind( this ); - } - - /** - * Split handler for RichText value, namely when content is pasted or the - * user presses the Enter key. - * - * @param {?Array} before Optional before value, to be used as content - * in place of what exists currently for the - * block. If undefined, the block is deleted. - * @param {?Array} after Optional after value, to be appended in a new - * paragraph block to the set of blocks passed - * as spread. - * @param {...WPBlock} blocks Optional blocks inserted between the before - * and after value blocks. - */ - splitBlock( before, after, ...blocks ) { - const { - attributes, - insertBlocksAfter, - setAttributes, - onReplace, - } = this.props; - - if ( after ) { - // Append "After" content as a new heading block to the end of - // any other blocks being inserted after the current heading. - const newBlock = createBlock( name, { content: after } ); - blocks.push( newBlock ); - } else { - const newBlock = createBlock( 'core/paragraph', { content: after } ); - blocks.push( newBlock ); - } - - if ( blocks.length && insertBlocksAfter ) { - insertBlocksAfter( blocks ); - } - - const { content } = attributes; - if ( before === null ) { - onReplace( [] ); - } else if ( content !== before ) { - // Only update content if it has in-fact changed. In case that user - // has created a new paragraph at end of an existing one, the value - // of before will be strictly equal to the current content. - setAttributes( { content: before } ); - } - } - plainTextContent( html ) { const result = create( { html } ); if ( result ) { @@ -85,6 +31,7 @@ class HeadingEdit extends Component { render() { const { attributes, + insertBlocksAfter, setAttributes, mergeBlocks, style, @@ -121,6 +68,7 @@ class HeadingEdit extends Component { <HeadingToolbar minLevel={ 2 } maxLevel={ 5 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> </BlockControls> <RichText + identifier="content" tagName={ tagName } value={ content } isSelected={ this.props.isSelected } @@ -132,7 +80,17 @@ class HeadingEdit extends Component { onBlur={ this.props.onBlur } // always assign onBlur as a props onChange={ ( value ) => setAttributes( { content: value } ) } onMerge={ mergeBlocks } - onSplit={ this.splitBlock } + unstableOnSplit={ + insertBlocksAfter ? + ( before, after, ...blocks ) => { + setAttributes( { content: before } ); + insertBlocksAfter( [ + ...blocks, + createBlock( 'core/paragraph', { content: after } ), + ] ); + } : + undefined + } placeholder={ placeholder || __( 'Write heading…' ) } /> </View> diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index e7eafbac1797c6..b16761ace72f84 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -120,6 +120,7 @@ class ParagraphEdit extends Component { onAccessibilityTap={ this.props.onFocus } > <RichText + identifier="content" tagName="p" value={ content } isSelected={ this.props.isSelected } diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index b11929c821b8bb..294d3e9c371d3f 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -82,18 +82,17 @@ class ModalLinkUI extends Component { opensInNewWindow, text: linkText, } ); - const placeholderFormats = ( value.formatPlaceholder && value.formatPlaceholder.formats ) || []; if ( isCollapsed( value ) && ! isActive ) { // insert link - const toInsert = applyFormat( create( { text: linkText } ), [ ...placeholderFormats, format ], 0, linkText.length ); + const toInsert = applyFormat( create( { text: linkText } ), format, 0, linkText.length ); const newAttributes = insert( value, toInsert ); onChange( { ...newAttributes, needsSelectionUpdate: true } ); } else if ( text !== getTextContent( slice( value ) ) ) { // edit text in selected link - const toInsert = applyFormat( create( { text } ), [ ...placeholderFormats, format ], 0, text.length ); + const toInsert = applyFormat( create( { text } ), format, 0, text.length ); const newAttributes = insert( value, toInsert, value.start, value.end ); onChange( { ...newAttributes, needsSelectionUpdate: true } ); } else { // transform selected text into link - const newAttributes = applyFormat( value, [ ...placeholderFormats, format ] ); + const newAttributes = applyFormat( value, format ); onChange( { ...newAttributes, needsSelectionUpdate: true } ); } diff --git a/packages/rich-text/src/apply-format.native.js b/packages/rich-text/src/apply-format.native.js deleted file mode 100644 index ef641cbaa224d9..00000000000000 --- a/packages/rich-text/src/apply-format.native.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * External dependencies - */ - -import { cloneDeep } from 'lodash'; - -/** - * Internal dependencies - */ - -import { normaliseFormats } from './normalise-formats'; - -/** - * Apply a format object to a Rich Text value from the given `startIndex` to the - * given `endIndex`. Indices are retrieved from the selection if none are - * provided. - * - * @param {Object} value Value to modify. - * @param {Object} formats Formats to apply. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. - * - * @return {Object} A new value with the format applied. - */ -export function applyFormat( - value, - formats, - startIndex = value.start, - endIndex = value.end -) { - const { formats: currentFormats, formatPlaceholder, start } = value; - - if ( ! Array.isArray( formats ) ) { - formats = [ formats ]; - } - - // The selection is collpased, insert a placeholder with the format so new input appears - // with the format applied. - if ( startIndex === endIndex ) { - const previousFormats = currentFormats[ startIndex - 1 ] || []; - const placeholderFormats = formatPlaceholder && formatPlaceholder.index === start && formatPlaceholder.formats; - // Follow the same logic as in getActiveFormat: placeholderFormats has priority over previousFormats - const activeFormats = ( placeholderFormats ? placeholderFormats : previousFormats ) || []; - return { - ...value, - formats: currentFormats, - formatPlaceholder: { - index: start, - formats: mergeFormats( activeFormats, formats ), - }, - }; - } - - const newFormats = currentFormats.slice( 0 ); - - for ( let index = startIndex; index < endIndex; index++ ) { - applyFormats( newFormats, index, formats ); - } - - return normaliseFormats( { ...value, formats: newFormats } ); -} - -function mergeFormats( formats1, formats2 ) { - const formatsOut = cloneDeep( formats1 ); - formats2.forEach( ( format2 ) => { - const format1In2 = formatsOut.find( ( format1 ) => format1.type === format2.type ); - // update properties while keeping the formats ordered - if ( format1In2 ) { - Object.assign( format1In2, format2 ); - } else { - formatsOut.push( cloneDeep( format2 ) ); - } - } ); - return formatsOut; -} - -function applyFormats( formats, index, newFormats ) { - if ( formats[ index ] ) { - formats[ index ] = mergeFormats( formats[ index ], newFormats ); - } else { - formats[ index ] = cloneDeep( newFormats ); - } -} diff --git a/packages/rich-text/src/get-active-format.native.js b/packages/rich-text/src/get-active-format.native.js deleted file mode 100644 index 152070fedf278c..00000000000000 --- a/packages/rich-text/src/get-active-format.native.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * External dependencies - */ - -import { find } from 'lodash'; - -/** - * Gets the format object by type at the start of the selection. This can be - * used to get e.g. the URL of a link format at the current selection, but also - * to check if a format is active at the selection. Returns undefined if there - * is no format at the selection. - * - * @param {Object} value Value to inspect. - * @param {string} formatType Format type to look for. - * - * @return {?Object} Active format object of the specified type, or undefined. - */ -export function getActiveFormat( { formats, formatPlaceholder, start, end }, formatType ) { - if ( start === undefined ) { - return; - } - - // if selection is not empty, get the first character format - if ( start !== end ) { - return find( formats[ start ], { type: formatType } ); - } - - // if user picked (or unpicked) formats but didn't write anything in those formats yet return this format - if ( formatPlaceholder && formatPlaceholder.index === start ) { - return find( formatPlaceholder.formats, { type: formatType } ); - } - - // if we're at the start of text, use the first char to pick up the formats - const startPos = start === 0 ? 0 : start - 1; - - // otherwise get the previous character format - const previousLetterFormat = find( formats[ startPos ], { type: formatType } ); - - if ( previousLetterFormat ) { - return previousLetterFormat; - } - - return undefined; -} diff --git a/packages/rich-text/src/remove-format.native.js b/packages/rich-text/src/remove-format.native.js deleted file mode 100644 index 5c20260728b719..00000000000000 --- a/packages/rich-text/src/remove-format.native.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * External dependencies - */ - -import { cloneDeep } from 'lodash'; - -/** - * Internal dependencies - */ - -import { normaliseFormats } from './normalise-formats'; - -/** - * Remove any format object from a Rich Text value by type from the given - * `startIndex` to the given `endIndex`. Indices are retrieved from the - * selection if none are provided. - * - * @param {Object} value Value to modify. - * @param {string} formatType Format type to remove. - * @param {number} startIndex Start index. - * @param {number} endIndex End index. - * - * @return {Object} A new value with the format applied. - */ -export function removeFormat( - value, - formatType, - startIndex = value.start, - endIndex = value.end -) { - const { formats, formatPlaceholder, start, end } = value; - const newFormats = formats.slice( 0 ); - let newFormatPlaceholder = null; - - if ( start === end ) { - if ( formatPlaceholder && formatPlaceholder.index === start ) { - const placeholderFormats = ( formatPlaceholder.formats || [] ).slice( 0 ); - newFormatPlaceholder = { - ...formatPlaceholder, - // make sure we do not reuse the formats reference in our placeholder `formats` array - formats: cloneDeep( placeholderFormats.filter( ( { type } ) => type !== formatType ) ), - }; - } else if ( ! formatPlaceholder ) { - const previousFormat = ( start > 0 ? formats[ start - 1 ] : formats[ 0 ] ) || []; - newFormatPlaceholder = { - index: start, - formats: cloneDeep( previousFormat.filter( ( { type } ) => type !== formatType ) ), - }; - } - } - - // Do not remove format if selection is empty - for ( let i = startIndex; i < endIndex; i++ ) { - if ( newFormats[ i ] ) { - filterFormats( newFormats, i, formatType ); - } - } - - return normaliseFormats( { ...value, formats: newFormats, formatPlaceholder: newFormatPlaceholder } ); -} - -function filterFormats( formats, index, formatType ) { - const newFormats = formats[ index ].filter( ( { type } ) => type !== formatType ); - - if ( newFormats.length ) { - formats[ index ] = newFormats; - } else { - delete formats[ index ]; - } -} From 761ec3fc1a668f90f0fcd15b50f688d13daaa802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 15 May 2019 14:28:07 +0200 Subject: [PATCH 100/664] Bump version to 5.7.0 (#15648) --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 6d49c7dba65ac6..a03313638fe7b6 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.7.0-rc.1 + * Version: 5.7.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 25bfdd2ef9e859..dfb7ac301bef49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.7.0-rc.1", + "version": "5.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 67d2e21445635c..50040eb7d399bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.7.0-rc.1", + "version": "5.7.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From fc76329a1e114fdf7820f62b99cb7382ceb701fe Mon Sep 17 00:00:00 2001 From: Timothy Jacobs <timothy@ironbounddesigns.com> Date: Wed, 15 May 2019 08:41:22 -0400 Subject: [PATCH 101/664] Support replacing multiple custom template tags in one output path. (#15596) * Support replacing multiple custom template tags in one output path. Fixes #15567 * Custom Templated Path Webpack Plugin: Add test case for multiple tags fix * Custom Templated Path Webpack Plugin: Add CHANGELOG entry for multi-tag fix --- packages/custom-templated-path-webpack-plugin/CHANGELOG.md | 6 ++++++ packages/custom-templated-path-webpack-plugin/src/index.js | 2 +- .../test/fixtures/webpack.config.js | 5 ++++- packages/custom-templated-path-webpack-plugin/test/index.js | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/custom-templated-path-webpack-plugin/CHANGELOG.md b/packages/custom-templated-path-webpack-plugin/CHANGELOG.md index 9ee57a0904c4b9..3d97245e0fca25 100644 --- a/packages/custom-templated-path-webpack-plugin/CHANGELOG.md +++ b/packages/custom-templated-path-webpack-plugin/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### Bug Fixes + +- Resolved an issue where only the value of the first matched tag would be produced. + ## 1.1.0 (2018-07-12) ### Internal diff --git a/packages/custom-templated-path-webpack-plugin/src/index.js b/packages/custom-templated-path-webpack-plugin/src/index.js index 5216fd8c7b7aed..b802335e0309b1 100644 --- a/packages/custom-templated-path-webpack-plugin/src/index.js +++ b/packages/custom-templated-path-webpack-plugin/src/index.js @@ -39,7 +39,7 @@ class CustomTemplatedPathPlugin { for ( let i = 0; i < this.handlers.length; i++ ) { const [ regexp, handler ] = this.handlers[ i ]; if ( regexp.test( path ) ) { - return path.replace( regexp, handler( path, data ) ); + path = path.replace( regexp, handler( path, data ) ); } } diff --git a/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js b/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js index 0df06b26621340..a050642f040895 100644 --- a/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js +++ b/packages/custom-templated-path-webpack-plugin/test/fixtures/webpack.config.js @@ -15,7 +15,7 @@ module.exports = { context: __dirname, entry: './entry', output: { - filename: 'build/[basename].js', + filename: 'build/[basename]-[theanswertolife].js', path: __dirname, }, plugins: [ @@ -32,6 +32,9 @@ module.exports = { return path; }, + theanswertolife() { + return 42; + }, } ), ], }; diff --git a/packages/custom-templated-path-webpack-plugin/test/index.js b/packages/custom-templated-path-webpack-plugin/test/index.js index 7173c8f9bec412..f6e441fb666067 100644 --- a/packages/custom-templated-path-webpack-plugin/test/index.js +++ b/packages/custom-templated-path-webpack-plugin/test/index.js @@ -19,7 +19,7 @@ const unlinkAsync = promisify( unlink ); const webpackAsync = promisify( webpack ); describe( 'CustomTemplatedPathPlugin', () => { - const outputFile = path.join( __dirname, '/fixtures/build/entry.js' ); + const outputFile = path.join( __dirname, '/fixtures/build/entry-42.js' ); beforeAll( async () => { // Remove output file so as not to report false positive from previous From a61a3fb812af283968a34359eb758bb113434908 Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <nicolai.vadim@gmail.com> Date: Wed, 15 May 2019 16:01:19 +0300 Subject: [PATCH 102/664] Accessibility: Fixed reading order of keyboard shortcuts. (#15631) * Accessibility: Fixed reading order of keyboard shortcuts. * Accessibility: added role list to unordered list and reset li bottom margin. --- .../keyboard-shortcut-help-modal/index.js | 23 +++++++++++-------- .../keyboard-shortcut-help-modal/style.scss | 3 +-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index 810519ccd3a493..a077cc6fe6fe95 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -40,23 +40,28 @@ const mapKeyCombination = ( keyCombination ) => keyCombination.map( ( character, } ); const ShortcutList = ( { shortcuts } ) => ( - <dl className="edit-post-keyboard-shortcut-help__shortcut-list"> + /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ + <ul className="edit-post-keyboard-shortcut-help__shortcut-list" role="list"> { shortcuts.map( ( { keyCombination, description, ariaLabel }, index ) => ( - <div + <li className="edit-post-keyboard-shortcut-help__shortcut" key={ index } > - <dt className="edit-post-keyboard-shortcut-help__shortcut-term"> + <div className="edit-post-keyboard-shortcut-help__shortcut-description"> + { description } + </div> + <div className="edit-post-keyboard-shortcut-help__shortcut-term"> <kbd className="edit-post-keyboard-shortcut-help__shortcut-key-combination" aria-label={ ariaLabel }> { mapKeyCombination( castArray( keyCombination ) ) } </kbd> - </dt> - <dd className="edit-post-keyboard-shortcut-help__shortcut-description"> - { description } - </dd> - </div> + </div> + </li> ) ) } - </dl> + </ul> ); const ShortcutSection = ( { title, shortcuts } ) => ( diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss index da56dc87045828..e6bcc5f43137e9 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss @@ -15,6 +15,7 @@ align-items: center; padding: 0.6rem 0; border-top: 1px solid $light-gray-500; + margin-bottom: 0; &:last-child { border-bottom: 1px solid $light-gray-500; @@ -22,14 +23,12 @@ } &__shortcut-term { - order: 1; font-weight: 600; margin: 0 0 0 1rem; } &__shortcut-description { flex: 1; - order: 0; margin: 0; // IE 11 flex item fix - ensure the item does not collapse From 6e6a9331bc5f93a77ac8922a3a91edd50e40fb60 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 15 May 2019 06:59:53 -0700 Subject: [PATCH 103/664] Rename and link to Block Editor Handbook (#15656) - Rename references from Gutenberg Handbook to Block Editor Handbook - Update links for new handbook location --- CONTRIBUTING.md | 4 ++-- docs/contributors/document.md | 2 +- packages/i18n/README.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24994336cbeb46..521f17e5b90798 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thank you for thinking about contributing to WordPress' Gutenberg project! If yo As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our [Code of Conduct](/CODE_OF_CONDUCT.md). -Before contributing, we encourage you to review the [Contributor Handbook](https://wordpress.org/gutenberg/handbook/contributors/). If you have any questions, please ask, either in Slack or open an issue in GitHub so we can help clarify. +Before contributing, we encourage you to review the [Contributor Handbook](https://developer.wordpress.org/block-editor/contributors/). If you have any questions, please ask, either in Slack or open an issue in GitHub so we can help clarify. All WordPress projects are [licensed under the GPLv2+](/LICENSE.md), and all contributions to Gutenberg will be released under the GPLv2+ license. You maintain copyright over any contribution you make, and by submitting a pull request, you are agreeing to release that contribution under the GPLv2+ license. @@ -22,7 +22,7 @@ If you'd like to contribute to the design or front-end, feel free to contribute Please see the [Documentation section](/docs/contributors/document.md) of the Contributor Handbook. -Documentation is automatically synced from `master` to the [Gutenberg Handbook](https://wordpress.org/gutenberg/handbook/) every 15 minutes. +Documentation is automatically synced from `master` to the [Block Editor Handbook](https://developer.wordpress.org/block-editor/) every 15 minutes. ### `@wordpress/component` diff --git a/docs/contributors/document.md b/docs/contributors/document.md index 31f372bc865cfd..43efc49c3db171 100644 --- a/docs/contributors/document.md +++ b/docs/contributors/document.md @@ -1,6 +1,6 @@ # Documentation Contributions -Documentation for Gutenberg is maintained in the `/docs/` directory in the same Gutenberg Github repository. The docs are published every 15 minutes to the [Gutenberg Handbook site](https://wordpress.org/gutenberg/handbook/). +Documentation for Gutenberg is maintained in the `/docs/` directory in the same Gutenberg Github repository. The docs are published every 15 minutes to the [Block Editor Handbook site](https://developer.wordpress.org/block-editor/). ## New Document diff --git a/packages/i18n/README.md b/packages/i18n/README.md index d20f94cb1425de..519a2ad6006c1b 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -21,7 +21,7 @@ sprintf( _n( '%d hat', '%d hats', 4, 'text-domain' ), 4 ); // 4 hats ``` -For a complete example, see the [Internationalization section of the Gutenberg Handbook](https://wordpress.org/gutenberg/handbook/designers-developers/developers/internationalization/). +For a complete example, see the [Internationalization section of the Block Editor Handbook](https://developer.wordpress.org/block-editor/developers/internationalization/). ## API From aab71fcf6ad56ed2c7d02879666910f1e2fcf4d9 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Wed, 15 May 2019 07:02:30 -0700 Subject: [PATCH 104/664] Improve label for multiple additional css classes (#15634) * Improve label for multiple additional css classes Add clarity around additional css classes so users know that multiple classes are supported and the proper separator between classes. * Add "(es)" so new label is "Additional CSS Class(es)" * Add help attribute with help message: "Space separate multiple classes." * Update copy per review --- packages/block-editor/src/hooks/custom-class-name.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index b8c242f05a9267..d7147a3693c30a 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -60,13 +60,14 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => <BlockEdit { ...props } /> <InspectorAdvancedControls> <TextControl - label={ __( 'Additional CSS Class' ) } + label={ __( 'Additional CSS Class(es)' ) } value={ props.attributes.className || '' } onChange={ ( nextValue ) => { props.setAttributes( { className: nextValue !== '' ? nextValue : undefined, } ); } } + help={ __( 'Separate multiple classes with spaces.' ) } /> </InspectorAdvancedControls> </> From f34057788ddac6b133a077bc56d8a3f7095f75da Mon Sep 17 00:00:00 2001 From: Jon Desrosiers <desrosj@users.noreply.github.com> Date: Wed, 15 May 2019 10:34:46 -0400 Subject: [PATCH 105/664] Update node-sass to version 4.12 to support the latest version of NodeJS (12.2.0). (#15613) --- package-lock.json | 47 ++++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index dfb7ac301bef49..7211447a4e733c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13784,12 +13784,6 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true - }, "lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", @@ -13844,12 +13838,6 @@ "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", "dev": true }, - "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", - "dev": true - }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -14732,7 +14720,8 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true + "dev": true, + "optional": true }, "nanomatch": { "version": "1.2.13", @@ -14923,9 +14912,9 @@ } }, "node-sass": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", - "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", + "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -14935,12 +14924,10 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash.assign": "^4.2.0", - "lodash.clonedeep": "^4.3.2", - "lodash.mergewith": "^4.6.0", + "lodash": "^4.17.11", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.10.0", + "nan": "^2.13.2", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", @@ -15080,18 +15067,18 @@ } }, "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", "dev": true }, "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "dev": true, "requires": { - "mime-db": "~1.38.0" + "mime-db": "1.40.0" } }, "minimist": { @@ -15100,6 +15087,12 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "dev": true + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", diff --git a/package.json b/package.json index 50040eb7d399bd..f2eaee420b4dfe 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "lint-staged": "8.1.5", "lodash": "4.17.11", "mkdirp": "0.5.1", - "node-sass": "4.11.0", + "node-sass": "4.12.0", "node-watch": "0.6.0", "parcel-bundler": "1.12.3", "pegjs": "0.10.0", From 34d24ed9d98f738c682c20b500634d85319cd33c Mon Sep 17 00:00:00 2001 From: James Stine <leon.blade@gmail.com> Date: Wed, 15 May 2019 11:47:46 -0400 Subject: [PATCH 106/664] Include icon in documentation for MediaPlaceholder (#15585) * Include icon in documentation for MediaPlaceholder * Slight changes to media-placeholder docs. --- .../src/components/media-placeholder/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/block-editor/src/components/media-placeholder/README.md b/packages/block-editor/src/components/media-placeholder/README.md index 9dc983ab7a8d8c..59795b5469c8cc 100644 --- a/packages/block-editor/src/components/media-placeholder/README.md +++ b/packages/block-editor/src/components/media-placeholder/README.md @@ -65,6 +65,13 @@ Class name added to the placeholder. - Type: `String` - Required: No +### icon + +Icon to display left of the title. When passed as a `String`, the icon will be resolved as a [Dashicon](https://developer.wordpress.org/resource/dashicons/). Alternatively, you can pass in a `WPComponent` such as `BlockIcon`to render instead. + +- Type: `String|WPComponent` +- Required: No + ### isAppender If true, the property changes the look of the placeholder to be adequate to scenarios where new files are added to an already existing set of files, e.g., adding files to a gallery. From 3aa603aebe92a83c8ef631a746af1635af5ec07b Mon Sep 17 00:00:00 2001 From: Ryan Kienstra <kienstraryan@gmail.com> Date: Thu, 16 May 2019 03:12:49 -0500 Subject: [PATCH 107/664] Address console warning for Image block inside Column block (#15239) --- packages/block-library/src/image/edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 91eeb5a40d6baf..645248bc702414 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -482,7 +482,7 @@ class ImageEdit extends Component { type="number" className="block-library-image__dimensions__width" label={ __( 'Width' ) } - value={ width !== undefined ? width : imageWidth } + value={ width || imageWidth || '' } min={ 1 } onChange={ this.updateWidth } /> @@ -490,7 +490,7 @@ class ImageEdit extends Component { type="number" className="block-library-image__dimensions__height" label={ __( 'Height' ) } - value={ height !== undefined ? height : imageHeight } + value={ height || imageHeight || '' } min={ 1 } onChange={ this.updateHeight } /> From fb276434f0b94b4c5594a7adbf490c923a4f38fb Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 16 May 2019 09:24:38 +0100 Subject: [PATCH 108/664] Add widget-areas endpoint (#15015) --- ...-experimental-wp-widget-blocks-manager.php | 274 ++++++++++++++++++ lib/class-wp-rest-widget-areas-controller.php | 219 ++++++++++++++ lib/load.php | 4 + lib/rest-api.php | 11 + 4 files changed, 508 insertions(+) create mode 100644 lib/class-experimental-wp-widget-blocks-manager.php create mode 100644 lib/class-wp-rest-widget-areas-controller.php diff --git a/lib/class-experimental-wp-widget-blocks-manager.php b/lib/class-experimental-wp-widget-blocks-manager.php new file mode 100644 index 00000000000000..bb1ac9fa089f76 --- /dev/null +++ b/lib/class-experimental-wp-widget-blocks-manager.php @@ -0,0 +1,274 @@ +<?php +/** + * Start: Include for phase 2 + * + * @package gutenberg + * @since 5.7.0 + */ + +/** + * Class that provides a set of static abstractions to deal with widgets. + * Itended to be used by WP_REST_Widget_Areas_Controller. + * + * @since 5.7.0 + */ +class Experimental_WP_Widget_Blocks_Manager { + /** + * Returns the $wp_registered_widgets global. + * + * @since 5.7.0 + * + * @return array $wp_registered_widgets global. + */ + private static function get_wp_registered_widgets() { + global $wp_registered_widgets; + return $wp_registered_widgets; + } + + /** + * Returns the $wp_registered_sidebars global. + * + * @since 5.7.0 + * + * @return array $wp_registered_sidebars global. + */ + private static function get_wp_registered_sidebars() { + global $wp_registered_sidebars; + return $wp_registered_sidebars; + } + + /** + * Given a sidebar_id returns the sidebar object in $wp_registered_sidebars for tha sidebar_id. + * + * @since 5.7.0 + * + * @param string $sidebar_id Indentifier of the sidebar. + * @return array Sidebar structure. + */ + public static function get_wp_registered_sidebars_sidebar( $sidebar_id ) { + $wp_registered_sidebars = self::get_wp_registered_sidebars(); + return $wp_registered_sidebars[ $sidebar_id ]; + } + + /** + * Returns a post id being referenced in a sidebar area. + * + * @since 5.7.0 + * + * @param string $sidebar_id Indentifier of the sidebar. + * @return integer Post id. + */ + public static function get_post_id_referenced_in_sidebar( $sidebar_id ) { + $sidebars = wp_get_sidebars_widgets(); + $sidebar = $sidebars[ $sidebar_id ]; + return is_numeric( $sidebar ) ? $sidebar : 0; + } + + /** + * Updates a sidebar structure to reference a $post_id, and makes the widgets being referenced inactive. + * + * @since 5.7.0 + * + * @param string $sidebar_id Indentifier of the sidebar. + * @param integer $post_id Post id. + */ + public static function reference_post_id_in_sidebar( $sidebar_id, $post_id ) { + $sidebars = wp_get_sidebars_widgets(); + $sidebar = $sidebars[ $sidebar_id ]; + wp_set_sidebars_widgets( + array_merge( + $sidebars, + array( + $sidebar_id => $post_id, + 'wp_inactive_widgets' => array_merge( + $sidebars['wp_inactive_widgets'], + $sidebar + ), + ) + ) + ); + } + + /** + * Returns a sidebar as an array of legacy widget blocks. + * + * @since 5.7.0 + * + * @param string $sidebar_id Indentifier of the sidebar. + * @return array $post_id Post id. + */ + public static function get_sidebar_as_blocks( $sidebar_id ) { + $blocks = array(); + + $sidebars_items = wp_get_sidebars_widgets(); + + foreach ( $sidebars_items[ $sidebar_id ] as $item ) { + $widget_class = self::get_widget_class( $item ); + $blocks[] = array( + 'blockName' => 'core/legacy-widget', + 'attrs' => array( + 'class' => $widget_class, + 'identifier' => $item, + 'instance' => self::get_sidebar_widget_instance( $sidebar, $item ), + ), + 'innerHTML' => '', + ); + } + return $blocks; + } + + /** + * Verifies if a sidabar id is valid or not. + * + * @since 5.7.0 + * + * @param string $sidebar_id Indentifier of the sidebar. + * @return boolean True if the $sidebar_id value is valid and false otherwise. + */ + public static function is_valid_sidabar_id( $sidebar_id ) { + $wp_registered_sidebars = self::get_wp_registered_sidebars(); + return isset( $wp_registered_sidebars[ $sidebar_id ] ); + } + + + /** + * Given a widget id returns the name of the class the represents the widget. + * + * @since 5.7.0 + * + * @param string $widget_id Indentifier of the widget. + * @return string|null Name of the class that represents the widget or null if the widget is not represented by a class. + */ + private static function get_widget_class( $widget_id ) { + $wp_registered_widgets = self::get_wp_registered_widgets(); + if ( + isset( $wp_registered_widgets[ $widget_id ]['callback'][0] ) && + $wp_registered_widgets[ $widget_id ]['callback'][0] instanceof WP_Widget + ) { + return get_class( $wp_registered_widgets[ $widget_id ]['callback'][0] ); + } + return null; + } + + /** + * Retrieves a widget instance. + * + * @since 5.7.0 + * + * @param array $sidebar sidebar data available at $wp_registered_sidebars. + * @param string $id Idenfitier of the widget instance. + * @return array Array containing the widget instance. + */ + private static function get_sidebar_widget_instance( $sidebar, $id ) { + list( $object, $number, $name ) = self::get_widget_info( $id ); + if ( ! $object ) { + return array(); + } + + $object->_set( $number ); + + $instances = $object->get_settings(); + $instance = $instances[ $number ]; + + $args = array_merge( + $sidebar, + array( + 'widget_id' => $id, + 'widget_name' => $name, + ) + ); + + /** + * Filters the settings for a particular widget instance. + * + * Returning false will effectively short-circuit display of the widget. + * + * @since 2.8.0 + * + * @param array $instance The current widget instance's settings. + * @param WP_Widget $this The current widget instance. + * @param array $args An array of default widget arguments. + */ + $instance = apply_filters( 'widget_display_callback', $instance, $object, $args ); + + if ( false === $instance ) { + return array(); + } + + return $instance; + } + + /** + * Given a widget id returns an array containing information about the widget. + * + * @since 5.7.0 + * + * @param string $widget_id Indentifier of the widget. + * @return array Array containing the the wiget object, the number, and the name. + */ + private static function get_widget_info( $widget_id ) { + $wp_registered_widgets = self::get_wp_registered_widgets(); + + if ( + ! isset( $wp_registered_widgets[ $widget_id ]['callback'][0] ) || + ! isset( $wp_registered_widgets[ $widget_id ]['params'][0]['number'] ) || + ! isset( $wp_registered_widgets[ $widget_id ]['name'] ) || + ! ( $wp_registered_widgets[ $widget_id ]['callback'][0] instanceof WP_Widget ) + ) { + return array( null, null, null ); + } + + $object = $wp_registered_widgets[ $widget_id ]['callback'][0]; + $number = $wp_registered_widgets[ $widget_id ]['params'][0]['number']; + $name = $wp_registered_widgets[ $widget_id ]['name']; + return array( $object, $number, $name ); + } + + /** + * Serializes an array of blocks. + * + * @since 5.7.0 + * + * @param array $blocks Post Array of block objects. + * @return string String representing the blocks. + */ + public static function serialize_blocks( $blocks ) { + return implode( array_map( 'self::serialize_block', $blocks ) ); + } + + /** + * Serializes a block. + * + * @since 5.7.0 + * + * @param array $block Block object. + * @return string String representing the block. + */ + public static function serialize_block( $block ) { + $name = $block['blockName']; + if ( 0 === strpos( $name, 'core/' ) ) { + $name = substr( $name, strlen( 'core/' ) ); + } + + if ( empty( $block['attrs'] ) ) { + $opening_tag_suffix = ''; + } else { + $opening_tag_suffix = ' ' . json_encode( $block['attrs'] ); + } + + if ( empty( $block['innerHTML'] ) ) { + return sprintf( + '<!-- wp:%s%s /-->', + $name, + $opening_tag_suffix + ); + } else { + return sprintf( + '<!-- wp:%1$s%2$s -->%3$s<!-- /wp:%1$s -->', + $name, + $opening_tag_suffix, + $block['innerHTML'] + ); + } + } +} diff --git a/lib/class-wp-rest-widget-areas-controller.php b/lib/class-wp-rest-widget-areas-controller.php new file mode 100644 index 00000000000000..6d333efa96c145 --- /dev/null +++ b/lib/class-wp-rest-widget-areas-controller.php @@ -0,0 +1,219 @@ +<?php +/** + * Start: Include for phase 2 + * Widget Areas REST API: WP_REST_Widget_Areas_Controller class + * + * @package gutenberg + * @since 5.7.0 + */ + +/** + * Controller which provides REST endpoint for the widget areas. + * + * @since 5.2.0 + * + * @see WP_REST_Controller + */ +class WP_REST_Widget_Areas_Controller extends WP_REST_Controller { + + /** + * Constructs the controller. + * + * @access public + */ + public function __construct() { + $this->namespace = '__experimental'; + $this->rest_base = 'widget-areas'; + } + + /** + * Registers the necessary REST API routes. + * + * @access public + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + $id_argument = array( + 'description' => __( 'The sidebar’s ID.', 'gutenberg' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'is_valid_sidabar_id' ), + ); + + $content_argument = array( + 'description' => __( 'Sidebar content.', 'gutenberg' ), + 'type' => 'string', + 'required' => true, + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P<id>.+)', + array( + 'args' => array( + 'id' => $id_argument, + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => array( + 'id' => $id_argument, + 'content' => $content_argument, + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Checks whether a given request has permission to read widget areas. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_user_cannot_view', + __( 'Sorry, you are not allowed to read sidebars.', 'gutenberg' ) + ); + } + + return true; + } + + /** + * Retrieves all widget areas. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + global $wp_registered_sidebars; + + $data = array(); + + foreach ( array_keys( $wp_registered_sidebars ) as $sidebar_id ) { + $data[ $sidebar_id ] = $this->get_sidebar_data( $sidebar_id ); + } + + return rest_ensure_response( $data ); + } + + /** + * Retrieves a specific widget area. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + return rest_ensure_response( $this->get_sidebar_data( $request['id'] ) ); + } + + /** + * Checks if a given REST request has access to update a widget area. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|bool True if the request has access to update the item, error object otherwise. + */ + public function update_item_permissions_check( $request ) { + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'rest_user_cannot_edit', + __( 'Sorry, you are not allowed to edit sidebars.', 'gutenberg' ) + ); + } + + return true; + } + + /** + * Updates a single widget area. + * + * @since 5.7.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function update_item( $request ) { + $sidebar_id = $request->get_param( 'id' ); + $sidebar_content = $request->get_param( 'content' ); + + $id_referenced_in_sidebar = Experimental_WP_Widget_Blocks_Manager::get_post_id_referenced_in_sidebar( $sidebar_id ); + + $post_id = wp_insert_post( + array( + 'ID' => $id_referenced_in_sidebar, + 'post_content' => $sidebar_content, + 'post_type' => 'wp_area', + ) + ); + + if ( 0 === $id_referenced_in_sidebar ) { + Experimental_WP_Widget_Blocks_Manager::reference_post_id_in_sidebar( $sidebar_id, $post_id ); + } + + return rest_ensure_response( $this->get_sidebar_data( $request['id'] ) ); + } + + /** + * Returns the sidebar data together with a content array containing the blocks present in the sidebar. + * The bocks may be legacy widget blocks representing the widgets currently present in the sidebar, or the content of a wp_area post that the sidebar references. + * + * @since 5.7.0 + * + * @param string $sidebar_id Indentifier of the sidebar. + * @return object Sidebar data with a content array. + */ + protected function get_sidebar_data( $sidebar_id ) { + $content_string = ''; + + $post_id_referenced_in_sidebar = Experimental_WP_Widget_Blocks_Manager::get_post_id_referenced_in_sidebar( $sidebar_id ); + + if ( 0 !== $post_id_referenced_in_sidebar ) { + $post = get_post( $post_id_referenced_in_sidebar ); + $content_string = $post->post_content; + } else { + $blocks = Experimental_WP_Widget_Blocks_Manager::get_sidebar_as_blocks( $sidebar_id ); + $content_string = Experimental_WP_Widget_Blocks_Manager::serialize_blocks( $blocks ); + } + + return array_merge( + Experimental_WP_Widget_Blocks_Manager::get_wp_registered_sidebars_sidebar( $sidebar_id ), + array( + 'content' => array( + 'raw' => $content_string, + 'rendered' => apply_filters( 'the_content', $content_string ), + 'block_version' => block_version( $content_string ), + ), + ) + ); + } +} diff --git a/lib/load.php b/lib/load.php index 9f845d95a8bf9d..d93b9dc1f9f0ea 100644 --- a/lib/load.php +++ b/lib/load.php @@ -18,6 +18,10 @@ if ( ! class_exists( 'WP_REST_Widget_Updater_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-widget-updater-controller.php'; } + if ( ! class_exists( 'WP_REST_Widget_Areas_Controller' ) ) { + require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php'; + require dirname( __FILE__ ) . '/class-wp-rest-widget-areas-controller.php'; + } /** * End: Include for phase 2 */ diff --git a/lib/rest-api.php b/lib/rest-api.php index eaccc09c61978d..d9671557bfcb8b 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -67,6 +67,17 @@ function gutenberg_register_rest_widget_updater_routes() { $widgets_controller->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); + +/** + * Registers the widget area REST API routes. + * + * @since 5.7.0 + */ +function gutenberg_register_rest_widget_areas() { + $widget_areas_controller = new WP_REST_Widget_Areas_Controller(); + $widget_areas_controller->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_widget_areas' ); /** * End: Include for phase 2 */ From f07e7560456dc364e306a01d5e6ba6bc47523649 Mon Sep 17 00:00:00 2001 From: Adam Silverstein <adam@10up.com> Date: Thu, 16 May 2019 03:11:04 -0600 Subject: [PATCH 109/664] wp.hooks - enable the 'all' hook (#12038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * All action: Shim doAction and applyFilters to trigger the all listener for every hook. * add all hook handling wrapped in comment blocks for production removal * Add some unit tests for the ‘all’ hook * Avoid hook recursion when adding all hook * Conditionally add all hook support based on process.env.NODE_ENV * use negative production check * Add a changelog entry describing the all hook * Document the `all` hook * Update CHANGELOG.md --- packages/hooks/CHANGELOG.md | 6 ++++ packages/hooks/README.md | 4 +++ packages/hooks/src/createRunHook.js | 8 +++++ packages/hooks/src/test/index.test.js | 45 +++++++++++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 4e0dbdce40e7d5..0b070a6d3c0dbc 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Feature + +- Enable support for the 'all' hook in non production environments. + ## 2.0.4 (2019-01-03) ## 2.0.0 (2018-09-05) diff --git a/packages/hooks/README.md b/packages/hooks/README.md index be0148f3747a07..952931c393bd15 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -52,4 +52,8 @@ Whenever an action or filter is added or removed, a matching `hookAdded` or `hoo * `hookAdded` action is triggered when `addFilter()` or `addAction()` method is called, passing values for `hookName`, `functionName`, `callback` and `priority`. * `hookRemoved` action is triggered when `removeFilter()` or `removeAction()` method is called, passing values for `hookName` and `functionName`. +### The `all` hook + +In non-minified builds developers can register a filter or action that will be called on *all* hooks, for example: `addAction( 'all', 'namespace', callbackFunction );`. Useful for debugging, the code supporting the `all` hook is stripped from the production code for performance reasons. + <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/packages/hooks/src/createRunHook.js b/packages/hooks/src/createRunHook.js index 66baaaa3e208ca..d9c909b734af7d 100644 --- a/packages/hooks/src/createRunHook.js +++ b/packages/hooks/src/createRunHook.js @@ -30,6 +30,14 @@ function createRunHook( hooks, returnFirstArg ) { const handlers = hooks[ hookName ].handlers; + // The following code is stripped from production builds. + if ( 'production' !== process.env.NODE_ENV ) { + // Handle any 'all' hooks registered. + if ( 'hookAdded' !== hookName && hooks.all ) { + handlers.push( ...hooks.all.handlers ); + } + } + if ( ! handlers || ! handlers.length ) { return returnFirstArg ? args[ 0 ] : diff --git a/packages/hooks/src/test/index.test.js b/packages/hooks/src/test/index.test.js index a31a0d2d113ced..20f27a30b9ae34 100644 --- a/packages/hooks/src/test/index.test.js +++ b/packages/hooks/src/test/index.test.js @@ -77,6 +77,7 @@ beforeEach( () => { delete hooks[ k ]; } + delete hooks.all; } ); } ); @@ -697,3 +698,47 @@ test( 'removing a filter triggers a hookRemoved action passing all callback deta 'my_callback3' ); } ); + +test( 'add an all filter and run it any hook to trigger it', () => { + addFilter( 'all', 'my_callback', filterA ); + expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testa' ); + expect( applyFilters( 'test.filter-anything', 'test' ) ).toBe( 'testa' ); +} ); + +test( 'add an all action and run it any hook to trigger it', () => { + addAction( 'all', 'my_callback', actionA ); + addAction( 'test.action', 'my_callback', actionA );// Doesn't get triggered. + doAction( 'test.action-anything' ); + expect( window.actionValue ).toBe( 'a' ); +} ); + +test( 'add multiple all filters and run it any hook to trigger them', () => { + addFilter( 'all', 'my_callback', filterA ); + addFilter( 'all', 'my_callback', filterB ); + expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testab' ); + expect( applyFilters( 'test.filter-anything', 'test' ) ).toBe( 'testab' ); +} ); + +test( 'add multiple all actions and run it any hook to trigger them', () => { + addAction( 'all', 'my_callback', actionA ); + addAction( 'all', 'my_callback', actionB ); + addAction( 'test.action', 'my_callback', actionA ); // Doesn't get triggered. + doAction( 'test.action-anything' ); + expect( window.actionValue ).toBe( 'ab' ); +} ); + +test( 'add multiple all filters and run it any hook to trigger them by priority', () => { + addFilter( 'all', 'my_callback', filterA, 11 ); + addFilter( 'all', 'my_callback', filterB, 10 ); + expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testba' ); + expect( applyFilters( 'test.filter-anything', 'test' ) ).toBe( 'testba' ); +} ); + +test( 'add multiple all actions and run it any hook to trigger them by priority', () => { + addAction( 'all', 'my_callback', actionA, 11 ); + addAction( 'all', 'my_callback', actionB, 10 ); + addAction( 'test.action', 'my_callback', actionA ); // Doesn't get triggered. + doAction( 'test.action-anything' ); + expect( window.actionValue ).toBe( 'ba' ); +} ); + From f257ed207fa7cb1dc28e4b1f277af000240fe992 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Thu, 16 May 2019 11:13:21 +0200 Subject: [PATCH 110/664] [Mobile] Rich Image Caption feature branch (#15571) * [Mobile]: Add Image Caption Styling (#14883) * Add ability to style image caption Adds ability to style image caption with bold, italic, links, and strikethrough (wordpress-mobile/gutenberg-mobile#574). Adds ability for image captions to wrap to multiple lines instead of getting cut off (wordpress-mobile/gutenberg-mobile#590). * Address style errors from linter * Hide image toolbar controls if caption selected * Update state in componentDidMount The previous approach of setting the child component's `isSelected` prop at the time the child was rendered based on `this.props.isSelected && this.state.isCaptionSelected` did not work when: (a) the child was selected (so its `isSelected` prop was true); (b) the child component had no text; and (c) the parent's `isSelected` prop was changed to false (i.e., another block was selected). Because the child component is not rendered when both the parent's `isSelected` prop is false and the caption does not contain any text, the child component's `onBlur` prop function (the `onCaptionBlur function) would not be called and update the `isCaptionSelected` state to be false. This bug shows when a user selects an empty caption, then selects a different block (thereby hiding the caption since it is empty), and then re-selects the image with the empty caption. In that scenario, upon re-selecting the image with the empty caption, the empty caption would appear and immediately be selected instead of just appearing. This bug would not appear if there was any text in the caption, because then the caption would always render and its `onBlur` prop function would be called. * Add comment explaining isSelected guard * Update image caption tagname from p to figcaption This change brings mobile's handling of image captinos in line with the web. It also addresses a crash that was occurring when the enter key was tapped to enter a new line in an image caption. * Remove `onBlurCaption` function On iOS the display of the link UI modal was causing the `onBlurCaption` function to be called, which would update the image caption's `isSelected` prop to false. That would, in turn, immediately remove the just-displayed modal. Restructuring the logic to not like this to not use the image caption's `onBlur` function avoids that issue. * Remove tagName from caption RichText component Explicitly setting the tagName to figcaption caused an invalid block to be saved if a caption ended with an empty line. * iOS: Remove p tags from image caption paragraphs (#15366) * Add center alignment to image caption on mobile (#15257) * Adding missing style to video/style.native.scss This style was fetched from the Image block, and it was removed after replacing the TextInput with RichText on captions --- .../src/components/rich-text/index.native.js | 24 +++++--- .../block-library/src/image/edit.native.js | 58 +++++++++++++++---- .../src/image/styles.native.scss | 4 -- .../block-library/src/video/edit.native.js | 3 +- .../block-library/src/video/style.native.scss | 4 ++ 5 files changed, 67 insertions(+), 26 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 007a612c948264..9b9af9c49eee86 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -122,6 +122,7 @@ export class RichText extends Component { this.onSelectionChangeFromAztec = this.onSelectionChangeFromAztec.bind( this ); this.valueToFormat = this.valueToFormat.bind( this ); this.willTrimSpaces = this.willTrimSpaces.bind( this ); + this.getHtmlToRender = this.getHtmlToRender.bind( this ); this.state = { activeFormats: [], selectedFormat: null, @@ -703,6 +704,19 @@ export class RichText extends Component { return false; } + getHtmlToRender( record, tagName ) { + // Save back to HTML from React tree + const value = this.valueToFormat( record ); + + if ( value === undefined || value === '' ) { + this.lastEventCount = undefined; // force a refresh on the native side + return ''; + } else if ( tagName ) { + return `<${ tagName }>${ value }</${ tagName }>`; + } + return value; + } + render() { const { tagName, @@ -713,14 +727,7 @@ export class RichText extends Component { } = this.props; const record = this.getRecord(); - // Save back to HTML from React tree - const value = this.valueToFormat( record ); - let html = `<${ tagName }>${ value }</${ tagName }>`; - // We need to check if the value is undefined or empty, and then assign it properly otherwise the placeholder is not visible - if ( value === undefined || value === '' ) { - html = ''; - this.lastEventCount = undefined; // force a refresh on the native side - } + const html = this.getHtmlToRender( record, tagName ); let minHeight = styles[ 'block-editor-rich-text' ].minHeight; if ( style && style.minHeight ) { @@ -799,6 +806,7 @@ export class RichText extends Component { fontStyle={ this.props.fontStyle } disableEditingMenu={ this.props.disableEditingMenu } isMultiline={ this.isMultiline } + textAlign={ this.props.textAlign } /> { isSelected && <FormatEdit value={ record } onChange={ this.onFormatChange } /> } </View> diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 93e21246f5e74a..d3677add3e33f9 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,7 +2,7 @@ * External dependencies */ import React from 'react'; -import { View, TextInput, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; +import { View, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; import { requestMediaImport, mediaUploadSync, @@ -47,6 +47,7 @@ class ImageEdit extends React.Component { this.state = { showSettings: false, + isCaptionSelected: false, }; this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); @@ -59,6 +60,7 @@ class ImageEdit extends React.Component { this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); this.onImagePressed = this.onImagePressed.bind( this ); this.onClearSettings = this.onClearSettings.bind( this ); + this.onFocusCaption = this.onFocusCaption.bind( this ); } componentDidMount() { @@ -83,6 +85,14 @@ class ImageEdit extends React.Component { } } + componentWillReceiveProps( nextProps ) { + // Avoid a UI flicker in the toolbar by insuring that isCaptionSelected + // is updated immediately any time the isSelected prop becomes false + this.setState( ( state ) => ( { + isCaptionSelected: nextProps.isSelected && state.isCaptionSelected, + } ) ); + } + onImagePressed() { const { attributes } = this.props; @@ -91,6 +101,11 @@ class ImageEdit extends React.Component { } else if ( attributes.id && ! isURL( attributes.url ) ) { requestImageFailedRetryDialog( attributes.id ); } + + this._caption.blur(); + this.setState( { + isCaptionSelected: false, + } ); } updateMediaProgress( payload ) { @@ -153,6 +168,17 @@ class ImageEdit extends React.Component { setAttributes( { url: mediaUrl, id: mediaId } ); } + onFocusCaption() { + if ( this.props.onFocus ) { + this.props.onFocus(); + } + if ( ! this.state.isCaptionSelected ) { + this.setState( { + isCaptionSelected: true, + } ); + } + } + render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width, alt, href, id } = attributes; @@ -242,9 +268,11 @@ class ImageEdit extends React.Component { > <View style={ { flex: 1 } }> { getInspectorControls() } - <BlockControls> - { toolbarEditButton } - </BlockControls> + { ( ! this.state.isCaptionSelected ) && + <BlockControls> + { toolbarEditButton } + </BlockControls> + } <InspectorControls> <ToolbarButton title={ __( 'Image Settings' ) } @@ -288,8 +316,7 @@ class ImageEdit extends React.Component { } } /> { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( - <View - style={ { padding: 12, flex: 1 } } + <View style={ { padding: 12, flex: 1 } } accessible={ true } accessibilityLabel={ isEmpty( caption ) ? @@ -303,13 +330,20 @@ class ImageEdit extends React.Component { } accessibilityRole={ 'button' } > - <TextInput - style={ { textAlign: 'center' } } - fontFamily={ this.props.fontFamily || ( styles[ 'caption-text' ].fontFamily ) } - underlineColorAndroid="transparent" - value={ caption } + <RichText + setRef={ ( ref ) => { + this._caption = ref; + } } + rootTagsToEliminate={ [ 'p' ] } placeholder={ __( 'Write caption…' ) } - onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + value={ caption } + onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + onFocus={ this.onFocusCaption } + onBlur={ this.props.onBlur } // always assign onBlur as props + isSelected={ this.state.isCaptionSelected } + fontSize={ 14 } + underlineColorAndroid="transparent" + textAlign={ 'center' } /> </View> ) } diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index b187d12aa32feb..fc4e82aded534b 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -18,10 +18,6 @@ align-items: center; } -.caption-text { - font-family: $default-regular-font; -} - .clearSettingsButton { color: $alert-red; } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 4f32732ed61bec..cb1309b24f9890 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -36,7 +36,6 @@ import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies */ -import styles from '../image/styles.scss'; import MediaUploadProgress from '../image/media-upload-progress'; import style from './style.scss'; @@ -215,7 +214,7 @@ class VideoEdit extends React.Component { <View style={ { padding: 12, flex: 1 } }> <TextInput style={ { textAlign: 'center' } } - fontFamily={ this.props.fontFamily || ( styles[ 'caption-text' ].fontFamily ) } + fontFamily={ this.props.fontFamily || ( style[ 'caption-text' ].fontFamily ) } underlineColorAndroid="transparent" value={ caption } placeholder={ __( 'Write caption…' ) } diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss index 4d0c557203f15f..cff1b9e31c80e9 100644 --- a/packages/block-library/src/video/style.native.scss +++ b/packages/block-library/src/video/style.native.scss @@ -23,3 +23,7 @@ font-size: 14; margin-top: 5; } + +.caption-text { + font-family: $default-regular-font; +} From f48a325f576dd322b2e625b255e84d92d2f3b158 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 16 May 2019 11:03:30 +0100 Subject: [PATCH 111/664] Fix previews for blocks using InnerBlocks (#15561) --- .../src/components/block-preview/index.js | 31 ++++++++++--------- .../src/components/block-preview/style.scss | 11 +++++++ .../src/components/block-styles/index.js | 3 ++ .../src/components/block-switcher/index.js | 1 + .../block-vertical-alignment-toolbar/index.js | 4 +-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 97e9a9ebef40c3..cc4c846a1cff3e 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -1,19 +1,16 @@ -/** - * External dependencies - */ -import { noop } from 'lodash'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { createBlock } from '@wordpress/blocks'; import { Disabled } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; /** * Internal dependencies */ -import BlockEdit from '../block-edit'; +import BlockEditorProvider from '../provider'; +import BlockList from '../block-list'; /** * Block Preview Component: It renders a preview given a block name and attributes. @@ -31,18 +28,22 @@ function BlockPreview( props ) { ); } -export function BlockPreviewContent( { name, attributes } ) { - const block = createBlock( name, attributes ); +export function BlockPreviewContent( { name, attributes, innerBlocks, settings } ) { + const block = createBlock( name, attributes, innerBlocks ); return ( <Disabled className="editor-block-preview__content block-editor-block-preview__content editor-styles-wrapper" aria-hidden> - <BlockEdit - name={ name } - focus={ false } - attributes={ block.attributes } - setAttributes={ noop } - /> + <BlockEditorProvider + value={ [ block ] } + settings={ settings } + > + <BlockList /> + </BlockEditorProvider> </Disabled> ); } -export default BlockPreview; +export default withSelect( ( select ) => { + return { + settings: select( 'core/block-editor' ).getSettings(), + }; +} )( BlockPreview ); diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index ca518cf8b50917..b96e181aa67d36 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -29,6 +29,17 @@ } } +.block-editor-block-preview__content { + // Resetting the block editor paddings and margins + .block-editor-block-list__layout, + .block-editor-block-list__block { + padding: 0; + } + .editor-block-list__block-edit [data-block] { + margin-top: 0; + } +} + .block-editor-block-preview__title { margin-bottom: 10px; color: $dark-gray-300; diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index 215d9e545b69fb..93c522eb227f51 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -71,6 +71,7 @@ function BlockStyles( { name, attributes, type, + block, onSwitch = noop, onHoverClassName = noop, } ) { @@ -129,6 +130,7 @@ function BlockStyles( { ...attributes, className: styleClassName, } } + innerBlocks={ block.innerBlocks } /> </div> <div className="editor-block-styles__item-label block-editor-block-styles__item-label"> @@ -149,6 +151,7 @@ export default compose( [ const blockType = getBlockType( block.name ); return { + block, name: block.name, attributes: block.attributes, className: block.attributes.className || '', diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 4d6964cc0e1109..66292f21faf4a6 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -164,6 +164,7 @@ export class BlockSwitcher extends Component { <BlockPreview name={ blocks[ 0 ].name } attributes={ { ...blocks[ 0 ].attributes, className: hoveredClassName } } + innerBlocks={ blocks[ 0 ].innerBlocks } /> } </> diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js index e7d0014cdecbcd..d3d94a0218b098 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js @@ -68,10 +68,10 @@ export default compose( } ), withViewportMatch( { isLargeViewport: 'medium' } ), withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getEditorSettings } = select( 'core/editor' ); + const { getBlockRootClientId, getSettings } = select( 'core/block-editor' ); return { isCollapsed: isCollapsed || ! isLargeViewport || ( - ! getEditorSettings().hasFixedToolbar && + ! getSettings().hasFixedToolbar && getBlockRootClientId( clientId ) ), }; From e2d5432fb759d0a3e0a5221d3b123468a92e321c Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 16 May 2019 11:04:50 +0100 Subject: [PATCH 112/664] Adding a note about the Github teams to the repository management docs (#15595) Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> --- docs/contributors/repository-management.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index 7eaa1cb28661e0..b1d84c31155ddb 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -150,6 +150,16 @@ If you’d like a template to follow: > > For more details, please see ____ and ____. +## Teams + +Two GitHub teams are used in the project. + +* [Gutenberg Core](https://github.com/orgs/WordPress/teams/gutenberg-core): A team composed of people that are actively involved in the project: attending meetings regularly, participating in triage sessions, performing reviews regularly, working on features and bug fixes and performing plugin and npm releases. + +* [Gutenberg](https://github.com/orgs/WordPress/teams/gutenberg): A team composed of contributors with at least 2–3 meaningful contributions to the project. + +If you meet this criteria of several meaningful contributions having been accepted into the repository and would like to be added to the Gutenberg team, feel free to ask in the [#core-editor Slack channel](https://make.wordpress.org/chat/). + ## Projects We use [GitHub projects](https://github.com/WordPress/gutenberg/projects) to keep track of details that aren't immediately actionable, but that we want to keep around for future reference. From bd2bdc6870a9300946eebfd60ac07074d86efdfa Mon Sep 17 00:00:00 2001 From: Matthew Kevins <mkevins@users.noreply.github.com> Date: Thu, 16 May 2019 20:36:48 +1000 Subject: [PATCH 113/664] Add console.warn and comments to help track WordPress-Android issue #9768 (#15658) --- packages/block-library/src/image/edit.native.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index d3677add3e33f9..ddb9477c76bccf 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -66,6 +66,14 @@ class ImageEdit extends React.Component { componentDidMount() { const { attributes, setAttributes } = this.props; + // This will warn when we have `id` defined, while `url` is undefined. + // This may help track this issue: https://github.com/wordpress-mobile/WordPress-Android/issues/9768 + // where a cancelled image upload was resulting in a subsequent crash. + if ( attributes.id && ! attributes.url ) { + // eslint-disable-next-line no-console + console.warn( 'Attributes has id with no url.' ); + } + if ( attributes.id && attributes.url && ! isURL( attributes.url ) ) { if ( attributes.url.indexOf( 'file:' ) === 0 ) { requestMediaImport( attributes.url, ( mediaId, mediaUri ) => { From b2629cefaa3eb136342ea99d57abd885bb230a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Thu, 16 May 2019 13:42:53 +0200 Subject: [PATCH 114/664] Fix: Disabled block switcher icons are blurry (#15643) --- .../src/components/block-switcher/index.js | 5 ++--- .../block-switcher/test/__snapshots__/index.js.snap | 13 +++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 66292f21faf4a6..eae64277471b2e 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -73,9 +73,8 @@ export class BlockSwitcher extends Component { disabled className="editor-block-switcher__no-switcher-icon block-editor-block-switcher__no-switcher-icon" label={ __( 'Block icon' ) } - > - <BlockIcon icon={ icon } showColors /> - </IconButton> + icon={ <BlockIcon icon={ icon } showColors /> } + /> </Toolbar> ); } diff --git a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap index 4680cbc0b0b18d..09a84bb9fbef01 100644 --- a/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-switcher/test/__snapshots__/index.js.snap @@ -5,13 +5,14 @@ exports[`BlockSwitcher should render disabled block switcher with multi block of <ForwardRef(IconButton) className="editor-block-switcher__no-switcher-icon block-editor-block-switcher__no-switcher-icon" disabled={true} + icon={ + <BlockIcon + icon="layout" + showColors={true} + /> + } label="Block icon" - > - <BlockIcon - icon="layout" - showColors={true} - /> - </ForwardRef(IconButton)> + /> </Toolbar> `; From b6dcb2092a5837c06f489ac8db352aafda8031e7 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Thu, 16 May 2019 08:32:26 -0400 Subject: [PATCH 115/664] Update Calendar block icon for better alignment with the Archives block icon (#15628) * Update Calendar block icon to match Archives block * Remove unnecessary fill=none * Update icons Use the old Archives icon for the Calendar block, add a new icon to the Archives block. --- packages/block-library/src/archives/icon.js | 4 ++-- packages/block-library/src/calendar/icon.js | 8 ++++++++ packages/block-library/src/calendar/index.js | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 packages/block-library/src/calendar/icon.js diff --git a/packages/block-library/src/archives/icon.js b/packages/block-library/src/archives/icon.js index 023a1902171d04..074a16c79458fc 100644 --- a/packages/block-library/src/archives/icon.js +++ b/packages/block-library/src/archives/icon.js @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { G, Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M7 11h2v2H7v-2zm14-5v14c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2l.01-14c0-1.1.88-2 1.99-2h1V2h2v2h8V2h2v2h1c1.1 0 2 .9 2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /></G></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M21 6V20C21 21.1 20.1 22 19 22H5C3.89 22 3 21.1 3 20L3.01 6C3.01 4.9 3.89 4 5 4H6V2H8V4H16V2H18V4H19C20.1 4 21 4.9 21 6ZM5 8H19V6H5V8ZM19 20V10H5V20H19ZM11 12H17V14H11V12ZM17 16H11V18H17V16ZM7 12H9V14H7V12ZM9 18V16H7V18H9Z" /></SVG> ); diff --git a/packages/block-library/src/calendar/icon.js b/packages/block-library/src/calendar/icon.js new file mode 100644 index 00000000000000..023a1902171d04 --- /dev/null +++ b/packages/block-library/src/calendar/icon.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { G, Path, SVG } from '@wordpress/components'; + +export default ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M7 11h2v2H7v-2zm14-5v14c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2l.01-14c0-1.1.88-2 1.99-2h1V2h2v2h8V2h2v2h1c1.1 0 2 .9 2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /></G></SVG> +); diff --git a/packages/block-library/src/calendar/index.js b/packages/block-library/src/calendar/index.js index c64cb010ca82a7..f43f5c288f446f 100644 --- a/packages/block-library/src/calendar/index.js +++ b/packages/block-library/src/calendar/index.js @@ -7,13 +7,14 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import edit from './edit'; +import icon from './icon'; export const name = 'core/calendar'; export const settings = { title: __( 'Calendar' ), description: __( 'A calendar of your site’s posts.' ), - icon: 'calendar', + icon, category: 'widgets', keywords: [ __( 'posts' ), __( 'archive' ) ], supports: { From 1f52970c0779b65c21bab9ed906e055e394a44f6 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Thu, 16 May 2019 09:16:33 -0400 Subject: [PATCH 116/664] @wordpress/data: Expose `hasResolver` property on returned selectors (#15436) * expose `hasResolver` property on returned selectors * account for custom stores that may not have resolvers defined in the store config --- packages/data/CHANGELOG.md | 4 +++ packages/data/src/namespace-store/index.js | 15 ++++++++--- .../data/src/namespace-store/test/index.js | 27 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 91362f4e1d5c4c..79cce308253eed 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -4,6 +4,10 @@ - Restore functionality of action-generators returning a Promise. Clarify intent and behaviour for `wp.data.dispatch` behaviour. Dispatch actions now always return a promise ([#14830](https://github.com/WordPress/gutenberg/pull/14830) + +### Enhancements + +- Expose `hasResolver` property on returned selectors indicating whether the selector has a corresponding resolver. ## 4.3.0 (2019-03-06) diff --git a/packages/data/src/namespace-store/index.js b/packages/data/src/namespace-store/index.js index 73e6560baf8e9a..6c8d7d94094eba 100644 --- a/packages/data/src/namespace-store/index.js +++ b/packages/data/src/namespace-store/index.js @@ -155,9 +155,11 @@ function createReduxStore( key, options, registry ) { */ function mapSelectors( selectors, store, registry ) { const createStateSelector = ( registeredSelector ) => { - const selector = registeredSelector.isRegistrySelector ? registeredSelector( registry.select ) : registeredSelector; + const registrySelector = registeredSelector.isRegistrySelector ? + registeredSelector( registry.select ) : + registeredSelector; - return function runSelector() { + const selector = function runSelector() { // This function is an optimized implementation of: // // selector( store.getState(), ...arguments ) @@ -172,8 +174,10 @@ function mapSelectors( selectors, store, registry ) { args[ i + 1 ] = arguments[ i ]; } - return selector( ...args ); + return registrySelector( ...args ); }; + selector.hasResolver = false; + return selector; }; return mapValues( selectors, createStateSelector ); @@ -213,10 +217,11 @@ function mapResolvers( resolvers, selectors, store ) { const mapSelector = ( selector, selectorName ) => { const resolver = resolvers[ selectorName ]; if ( ! resolver ) { + selector.hasResolver = false; return selector; } - return ( ...args ) => { + const selectorResolver = ( ...args ) => { async function fulfillSelector() { const state = store.getState(); if ( typeof resolver.isFulfilled === 'function' && resolver.isFulfilled( state, ...args ) ) { @@ -236,6 +241,8 @@ function mapResolvers( resolvers, selectors, store ) { fulfillSelector( ...args ); return selector( ...args ); }; + selectorResolver.hasResolver = true; + return selectorResolver; }; return { diff --git a/packages/data/src/namespace-store/test/index.js b/packages/data/src/namespace-store/test/index.js index 34a1092d44d1b4..2a9f14ae821f64 100644 --- a/packages/data/src/namespace-store/test/index.js +++ b/packages/data/src/namespace-store/test/index.js @@ -80,6 +80,33 @@ describe( 'controls', () => { registry.select( 'store' ).getItems(); } ); + describe( 'selectors have expected value for the `hasResolver` property', () => { + it( 'when custom store has resolvers defined', () => { + registry.registerStore( 'store', { + reducer: jest.fn(), + selectors: { + getItems: ( state ) => state, + getItem: ( state ) => state, + }, + resolvers: { + * getItems() { + yield 'foo'; + }, + }, + } ); + expect( registry.select( 'store' ).getItems.hasResolver ).toBe( true ); + expect( registry.select( 'store' ).getItem.hasResolver ).toBe( false ); + } ); + it( 'when custom store does not have resolvers defined', () => { + registry.registerStore( 'store', { + reducer: jest.fn(), + selectors: { + getItems: ( state ) => state, + }, + } ); + expect( registry.select( 'store' ).getItems.hasResolver ).toBe( false ); + } ); + } ); describe( 'various action types have expected response and resolve as ' + 'expected with controls middleware', () => { const actions = { From baef1c2e397a7156645d1172c6dcbf52bb5c44dd Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Thu, 16 May 2019 16:13:04 +0200 Subject: [PATCH 117/664] [Mobile]: Improve screen reader support on BottomSheet's cells. (#15213) * Improve accessibility on BottomSheet cell * Improve screen reader support for BottomSheet cells with switch * Clean up code. * Fix lint issues * Add proper accessibility labels to Image Settings bottom sheet cells. * Handle switch cell state localization internaly. * Accessibility labels on bottom-sheet cells are handled internally. * Switch cells handle accessibility labels internally. * Fix lint issues * Remove unnecessary accessibility label from image settings cell --- .../mobile/bottom-sheet/cell.native.js | 37 +++++++++++- .../mobile/bottom-sheet/index.native.js | 2 + .../mobile/bottom-sheet/switch-cell.native.js | 57 +++++++++++++++++++ .../block-library/src/image/edit.native.js | 1 + .../format-library/src/link/modal.native.js | 44 +++++++------- 5 files changed, 117 insertions(+), 24 deletions(-) create mode 100644 packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js index 9acc94bbfb3bbd..689aeeb38dbec7 100644 --- a/packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js @@ -2,12 +2,14 @@ * External dependencies */ import { TouchableOpacity, Text, View, TextInput, I18nManager } from 'react-native'; +import { isEmpty } from 'lodash'; /** * WordPress dependencies */ import { Dashicon } from '@wordpress/components'; import { Component } from '@wordpress/element'; +import { __, _x, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -31,6 +33,9 @@ export default class Cell extends Component { render() { const { + accessibilityLabel, + accessibilityHint, + accessibilityRole, onPress, label, value, @@ -118,8 +123,38 @@ export default class Cell extends Component { ); }; + const getAccessibilityLabel = () => { + if ( accessibilityLabel || ! showValue ) { + return accessibilityLabel || label; + } + return isEmpty( value ) ? + sprintf( + /* translators: accessibility text. Empty state of a inline textinput cell. %s: The cell's title */ + _x( '%s. Empty', 'inline textinput cell' ), + label + ) : + // Separating by ',' is necessary to make a pause on urls (non-capitalized text) + sprintf( + /* translators: accessibility text. Inline textinput title and value.%1: Cell title, %2: cell value. */ + _x( '%1$s, %2$s', 'inline textinput cell' ), + label, + value + ); + }; + return ( - <TouchableOpacity onPress={ onCellPress } style={ { ...styles.clipToBounds, ...style } } > + <TouchableOpacity + accessible={ ! this.state.isEditingValue } + accessibilityLabel={ getAccessibilityLabel() } + accessibilityRole={ accessibilityRole || 'button' } + accessibilityHint={ isValueEditable ? + /* translators: accessibility text */ + __( 'Double tap to edit this value' ) : + accessibilityHint + } + onPress={ onCellPress } + style={ { ...styles.clipToBounds, ...style } } + > <View style={ styles.cellContainer }> <View style={ styles.cellRowContainer }> { icon && ( diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/index.native.js b/packages/block-editor/src/components/mobile/bottom-sheet/index.native.js index fbda22cc7aa66a..6caed0b1da4854 100644 --- a/packages/block-editor/src/components/mobile/bottom-sheet/index.native.js +++ b/packages/block-editor/src/components/mobile/bottom-sheet/index.native.js @@ -17,6 +17,7 @@ import styles from './styles.scss'; import Button from './button'; import Cell from './cell'; import PickerCell from './picker-cell'; +import SwitchCell from './switch-cell'; import KeyboardAvoidingView from './keyboard-avoiding-view'; class BottomSheet extends Component { @@ -162,5 +163,6 @@ BottomSheet.getWidth = getWidth; BottomSheet.Button = Button; BottomSheet.Cell = Cell; BottomSheet.PickerCell = PickerCell; +BottomSheet.SwitchCell = SwitchCell; export default BottomSheet; diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js b/packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js new file mode 100644 index 00000000000000..2682b82668c60b --- /dev/null +++ b/packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js @@ -0,0 +1,57 @@ + +/** + * External dependencies + */ +import { Switch } from 'react-native'; +/** + * WordPress dependencies + */ +import { __, _x, sprintf } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import Cell from './cell'; + +export default function SwitchCell( props ) { + const { + value, + onValueChange, + ...cellProps + } = props; + + const onPress = () => { + onValueChange( ! value ); + }; + + const accessibilityLabel = value ? + sprintf( + /* translators: accessibility text. Switch setting ON state. %s: Switch title. */ + _x( '%s. On', 'switch control' ), + cellProps.label + ) : + sprintf( + /* translators: accessibility text. Switch setting OFF state. %s: Switch title. */ + _x( '%s. Off', 'switch control' ), + cellProps.label + ); + + return ( + <Cell + { ...cellProps } + accessibilityLabel={ accessibilityLabel } + accessibilityRole={ 'none' } + accessibilityHint={ + /* translators: accessibility text (hint for switches) */ + __( 'Double tap to toggle setting' ) + } + onPress={ onPress } + editable={ false } + value={ '' } + > + <Switch + value={ value } + onValueChange={ onValueChange } + /> + </Cell> + ); +} diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index ddb9477c76bccf..cf1872a0a2fe48 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -231,6 +231,7 @@ class ImageEdit extends React.Component { onChangeValue={ this.onSetLinkDestination } autoCapitalize="none" autoCorrect={ false } + keyboardType="url" /> <BottomSheet.Cell icon={ 'editor-textcolor' } diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index 294d3e9c371d3f..954bd6f1da5d7a 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -2,7 +2,7 @@ * External dependencies */ import React from 'react'; -import { Switch, Platform } from 'react-native'; +import { Platform } from 'react-native'; /** * WordPress dependencies @@ -122,6 +122,7 @@ class ModalLinkUI extends Component { render() { const { isVisible } = this.props; + const { text } = this.state; return ( <BottomSheet @@ -129,36 +130,33 @@ class ModalLinkUI extends Component { onClose={ this.onDismiss } hideHeader > - { /* eslint-disable jsx-a11y/no-autofocus */ } - <BottomSheet.Cell - icon={ 'admin-links' } - label={ __( 'URL' ) } - value={ this.state.inputValue } - placeholder={ __( 'Add URL' ) } - autoCapitalize="none" - autoCorrect={ false } - keyboardType="url" - onChangeValue={ this.onChangeInputValue } - autoFocus={ Platform.OS === 'ios' } - /> - { /* eslint-enable jsx-a11y/no-autofocus */ } + { /* eslint-disable jsx-a11y/no-autofocus */ + <BottomSheet.Cell + icon={ 'admin-links' } + label={ __( 'URL' ) } + value={ this.state.inputValue } + placeholder={ __( 'Add URL' ) } + autoCapitalize="none" + autoCorrect={ false } + keyboardType="url" + onChangeValue={ this.onChangeInputValue } + autoFocus={ Platform.OS === 'ios' } + /> + /* eslint-enable jsx-a11y/no-autofocus */ } <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Link Text' ) } - value={ this.state.text } + value={ text } placeholder={ __( 'Add Link Text' ) } onChangeValue={ this.onChangeText } /> - <BottomSheet.Cell + <BottomSheet.SwitchCell icon={ 'external' } label={ __( 'Open in New Tab' ) } - value={ '' } - > - <Switch - value={ this.state.opensInNewWindow } - onValueChange={ this.onChangeOpensInNewWindow } - /> - </BottomSheet.Cell> + value={ this.state.opensInNewWindow } + onValueChange={ this.onChangeOpensInNewWindow } + separatorType={ 'fullWidth' } + /> <BottomSheet.Cell label={ __( 'Remove Link' ) } labelStyle={ styles.clearLinkButton } From 304b4909df95befdcc7e4d6e68cb798e9399a24f Mon Sep 17 00:00:00 2001 From: Jorge Bernal <jbernal@gmail.com> Date: Thu, 16 May 2019 16:34:10 +0200 Subject: [PATCH 118/664] Add @daniloercoli and @sergioestevao as RichText CODEOWNERS (#15681) As mentioned on the last editor chat, let's make sure we have one representative of each mobile platform keeping notified of changes to RichText. https://make.wordpress.org/core/2019/05/15/editor-chat-summary-may-15/ --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 22ec9d8c002b3d..5b6279ebafdc75 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -77,9 +77,9 @@ /packages/plugins @youknowriad @gziolo @aduth @adamsilverstein # Rich Text -/packages/format-library @youknowriad @aduth @ellatrix @jorgefilipecosta -/packages/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta -/packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta +/packages/format-library @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao +/packages/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao +/packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao # PHP /lib @youknowriad @gziolo @aduth From ed630e88c8c988f5fea5a99b5b3515d0f8b701e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Thu, 16 May 2019 19:20:10 +0200 Subject: [PATCH 119/664] Fixes update-readme script (#15679) This PR does two things: - Fixes #15626 and #15680 by making the script synchronous. In #15200 we missed the fact that to fill tokens within the same file we need to execute docgen synchronously, otherwise, the last token process will overwrite the first. - Updates our espree dependency to 4.0.0. When error reporting was back, we uncovered that introducing short Fragment syntax in #15120 caused docgen to fail. The reason is that the espree version we used didn't support that. This fixes it by upgrading it to one that does. A couple of restrictions: - espree uses acorn-jsx to power JSX parsing. - acorn-jsx@4.1.0 added support for JSX fragment short syntax (patched in 4.1.1). - espree@4.0.0 added acorn-jsx@4.1.1. We should use this at a minimum. - espree@4.1.0 added acorn@6 and acorn-jsx@5 for parsing. This caused an error I couldn't identify the source. - espree@5.0.0 removed support for the attachComment. We use this for collocating the JSDoc comment with the proper export statement. Without this, we can't migrate to espree@5.0.0. --- bin/update-readmes.js | 19 +++++++++---------- package-lock.json | 26 +++++++++----------------- package.json | 2 +- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 1aca8699f9c07b..63d041e7ab5098 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -1,8 +1,7 @@ #!/usr/bin/env node const { join } = require( 'path' ); -const { promisify } = require( 'util' ); -const spawn = promisify( require( 'child_process' ).spawn ); +const spawnSync = require( 'child_process' ).spawnSync; const packages = [ 'a11y', @@ -39,15 +38,17 @@ const packages = [ 'wordcount', ]; -Promise.all( packages.map( ( entry ) => { +packages.forEach( ( entry ) => { if ( ! Array.isArray( entry ) ) { entry = [ entry, { 'Autogenerated API docs': 'src/index.js' } ]; } const [ packageName, targets ] = entry; - return Promise.all( Object.entries( targets ).map( async ( [ token, path ] ) => { - const { status, stderr } = await spawn( + Object.entries( targets ).forEach( ( [ token, path ] ) => { + // Each target operates over the same file, so it needs to be processed synchronously, + // as to make sure the processes don't overwrite each other. + const { status, stderr } = spawnSync( join( __dirname, '..', 'node_modules', '.bin', 'docgen' ), [ join( 'packages', packageName, path ), @@ -60,10 +61,8 @@ Promise.all( packages.map( ( entry ) => { ); if ( status !== 0 ) { - throw stderr.toString(); + process.stderr.write( `${ packageName } ${ stderr.toString() }\n` ); + process.exit( 1 ); } - } ) ); -} ) ).catch( ( error ) => { - process.stderr.write( `${ error }\n` ); - process.exit( 1 ); + } ); } ); diff --git a/package-lock.json b/package-lock.json index 7211447a4e733c..5f830dff76a576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3673,20 +3673,12 @@ } }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", "dev": true, "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } + "acorn": "^5.0.3" } }, "agent-base": { @@ -8770,13 +8762,13 @@ "dev": true }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" } }, "esprima": { diff --git a/package.json b/package.json index f2eaee420b4dfe..7986263f49cfe0 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "doctrine": "2.1.0", "enzyme": "3.9.0", "eslint-plugin-jest": "21.5.0", - "espree": "3.5.4", + "espree": "4.0.0", "fbjs": "0.8.17", "glob": "7.1.2", "husky": "0.14.3", From a95d5de3bcb6afaa9b50a672f89cae4802575571 Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Thu, 16 May 2019 21:42:11 +0200 Subject: [PATCH 120/664] Media & Text: Add width constraints (#14951) * add width constraints * make sure the text wraps the same accross browsers (fixes FF bug) * use constant for width constraint --- packages/block-library/src/media-text/edit.js | 7 +++++-- packages/block-library/src/media-text/editor.scss | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index e4f843bcc24bf4..f6594b7a71a591 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -37,6 +37,9 @@ const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/ const TEMPLATE = [ [ 'core/paragraph', { fontSize: 'large', placeholder: _x( 'Content…', 'content placeholder' ) } ], ]; +// this limits the resize to a safe zone to avoid making broken layouts +const WIDTH_CONSTRAINT_PERCENTAGE = 15; +const applyWidthConstraints = ( width ) => Math.max( WIDTH_CONSTRAINT_PERCENTAGE, Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE ) ); class MediaTextEdit extends Component { constructor() { @@ -85,7 +88,7 @@ class MediaTextEdit extends Component { onWidthChange( width ) { this.setState( { - mediaWidth: width, + mediaWidth: applyWidthConstraints( width ), } ); } @@ -93,7 +96,7 @@ class MediaTextEdit extends Component { const { setAttributes } = this.props; setAttributes( { - mediaWidth: width, + mediaWidth: applyWidthConstraints( width ), } ); this.setState( { mediaWidth: null, diff --git a/packages/block-library/src/media-text/editor.scss b/packages/block-library/src/media-text/editor.scss index 3d0ad0361bd947..fe6a552526971b 100644 --- a/packages/block-library/src/media-text/editor.scss +++ b/packages/block-library/src/media-text/editor.scss @@ -3,6 +3,7 @@ "media-text-media media-text-content" "resizer resizer"; align-items: center; + word-break: break-all; } .wp-block-media-text.has-media-on-the-right { From b193b09c536485a9d06c98a208b3bf76935c9562 Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Thu, 16 May 2019 23:01:31 +0200 Subject: [PATCH 121/664] FormTokenField: Add Help Text (#15469) * add visual help text * updates based on review * add periods after help msgs --- packages/components/src/form-token-field/index.js | 9 ++++++--- packages/components/src/form-token-field/style.scss | 6 +++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/components/src/form-token-field/index.js b/packages/components/src/form-token-field/index.js index 18c05f4f0ffa45..fa44e49494ff38 100644 --- a/packages/components/src/form-token-field/index.js +++ b/packages/components/src/form-token-field/index.js @@ -583,9 +583,12 @@ class FormTokenField extends Component { /> ) } </div> - <div id={ `components-form-token-suggestions-howto-${ instanceId }` } className="screen-reader-text"> - { __( 'Separate with commas' ) } - </div> + <p id={ `components-form-token-suggestions-howto-${ instanceId }` } className="components-form-token-field__help"> + { this.props.tokenizeOnSpace ? + __( 'Separate with commas, spaces, or the Enter key.' ) : + __( 'Separate with commas or the Enter key.' ) + } + </p> </div> ); /* eslint-enable jsx-a11y/no-static-element-interactions */ diff --git a/packages/components/src/form-token-field/style.scss b/packages/components/src/form-token-field/style.scss index 99fbc607c819aa..5480cdef635409 100644 --- a/packages/components/src/form-token-field/style.scss +++ b/packages/components/src/form-token-field/style.scss @@ -3,7 +3,7 @@ flex-wrap: wrap; align-items: flex-start; width: 100%; - margin: 0; + margin: 0 0 $grid-size 0; padding: $grid-size-small; background-color: $white; border: $border-width solid $light-gray-700; @@ -50,6 +50,10 @@ margin-bottom: $grid-size-small; } +.components-form-token-field__help { + font-style: italic; +} + // Tokens .components-form-token-field__token { font-size: $default-font-size; From 84a021ca2ebb3fe262687fd38664ab68f75506c4 Mon Sep 17 00:00:00 2001 From: "Michael P. Pfeiffer" <frontdevde@users.noreply.github.com> Date: Fri, 17 May 2019 12:25:37 +0200 Subject: [PATCH 122/664] A11y: update icon for heading block (#15462) --- packages/block-library/src/heading/icon.js | 8 -------- packages/block-library/src/heading/index.js | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 packages/block-library/src/heading/icon.js diff --git a/packages/block-library/src/heading/icon.js b/packages/block-library/src/heading/icon.js deleted file mode 100644 index 9bff6fbd085a4b..00000000000000 --- a/packages/block-library/src/heading/icon.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * WordPress dependencies - */ -import { Path, SVG } from '@wordpress/components'; - -export default ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M5 4v3h5.5v12h3V7H19V4z" /><Path fill="none" d="M0 0h24v24H0V0z" /></SVG> -); diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 4dd2f16e086cee..6c0d58b4e240bd 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -13,7 +13,6 @@ import { RichText } from '@wordpress/block-editor'; * Internal dependencies */ import edit from './edit'; -import icon from './icon'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; @@ -31,7 +30,7 @@ const supports = { export const settings = { title: __( 'Heading' ), description: __( 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.' ), - icon, + icon: 'heading', keywords: [ __( 'title' ), __( 'subtitle' ) ], supports, transforms, From 2bd498ddfac559c8cc1b78a0ff20db7bf50ebddf Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 17 May 2019 08:55:24 -0400 Subject: [PATCH 123/664] Add @melchoyce @sarahmonster and @kjellr to CONTRIBUTORS.md (#15700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just noticed that the three of us aren't there. 🙂 --- CONTRIBUTORS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2256d669c7dddb..cc8690aaeacebc 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -128,3 +128,6 @@ This list is manually curated to include valuable contributions by volunteers th | @gutendev | @gutendev | | @drdogbot7 | @drdogbot7 | | @m-e-h | @m-e-h | +| @melchoyce | @melchoyce | +| @sarahmonster | @tinkerbelly | +| @kjellr | @kjellr | From afd2fbeb46ddfb812c9e8d8a0c5995f666f006e1 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 17 May 2019 07:01:16 -0700 Subject: [PATCH 124/664] Reword design review for clarity (#15611) --- docs/contributors/repository-management.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index b1d84c31155ddb..a9d22c8b4e59d1 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -110,9 +110,9 @@ Every pull request goes through a manual code review, in addition to automated t ### Design Review -If your pull request impacts the design, you can ask for a designer to review. Most pull requests that have an impact on design are reviewed. However, you can request a design review on something by adding the [Needs Design Feedback](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback) label. As a guide, this could be: +If your pull request impacts the design, you should ask for a design review. To request a design review add the [Needs Design Feedback](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback) label to your PR. As a guide, changes that should be reviewed: -- Something based on a previous design, to check is as that design. +- A change based on a previous design, to confirm the design is still valid with the change. - Anything that changes something visually. - If you just want design feedback on an idea or exploration. From a15cfa0494911f5597f14fdef8a5940ea01ab4a3 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 17 May 2019 18:02:10 +0100 Subject: [PATCH 125/664] Refactor the popover component using React Hooks (#15053) --- .../test/__snapshots__/index.js.snap | 3 - .../src/dropdown-menu/test/index.js | 3 +- .../components/src/dropdown/test/index.js | 6 +- packages/components/src/popover/index.js | 641 +++++++++--------- .../popover/test/__snapshots__/index.js.snap | 4 +- packages/components/src/popover/test/index.js | 135 ++-- 6 files changed, 385 insertions(+), 407 deletions(-) diff --git a/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap index 9613a40af346ac..c835e54540c257 100644 --- a/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap @@ -4,7 +4,6 @@ exports[`URLPopover matches the snapshot in its default state 1`] = ` <Popover className="editor-url-popover block-editor-url-popover" focusOnMount="firstElement" - noArrow={false} position="bottom center" > <div @@ -28,7 +27,6 @@ exports[`URLPopover matches the snapshot when the settings are toggled open 1`] <Popover className="editor-url-popover block-editor-url-popover" focusOnMount="firstElement" - noArrow={false} position="bottom center" > <div @@ -59,7 +57,6 @@ exports[`URLPopover matches the snapshot when there are no settings 1`] = ` <Popover className="editor-url-popover block-editor-url-popover" focusOnMount="firstElement" - noArrow={false} position="bottom center" > <div diff --git a/packages/components/src/dropdown-menu/test/index.js b/packages/components/src/dropdown-menu/test/index.js index fb3037ed7ed980..ef8e900de24f9f 100644 --- a/packages/components/src/dropdown-menu/test/index.js +++ b/packages/components/src/dropdown-menu/test/index.js @@ -14,7 +14,6 @@ import { Component } from '@wordpress/element'; * Internal dependencies */ import DropdownMenu from '../'; -import Popover from '../../popover'; describe( 'DropdownMenu', () => { let controls; @@ -79,7 +78,7 @@ describe( 'DropdownMenu', () => { } ); - expect( TestUtils.scryRenderedComponentsWithType( wrapper, Popover ) ).toHaveLength( 1 ); + expect( TestUtils.scryRenderedDOMComponentsWithClass( wrapper, 'components-popover' ) ).toHaveLength( 1 ); } ); } ); } ); diff --git a/packages/components/src/dropdown/test/index.js b/packages/components/src/dropdown/test/index.js index 7b1016a14d60e2..3de873a76787b8 100644 --- a/packages/components/src/dropdown/test/index.js +++ b/packages/components/src/dropdown/test/index.js @@ -7,12 +7,11 @@ import TestUtils from 'react-dom/test-utils'; * Internal dependencies */ import Dropdown from '../'; -import Popover from '../../popover'; describe( 'Dropdown', () => { const expectPopoverVisible = ( wrapper, visible ) => { expect( - TestUtils.scryRenderedComponentsWithType( wrapper, Popover ) ) + TestUtils.scryRenderedDOMComponentsWithClass( wrapper, 'components-popover' ) ) .toHaveLength( visible ? 1 : 0 ); }; const buttonElement = ( wrapper ) => TestUtils.findRenderedDOMComponentWithTag( @@ -31,13 +30,14 @@ describe( 'Dropdown', () => { buttonElement( wrapper ).getAttribute( 'aria-expanded' ) ).toBe( expanded.toString() ); }; + const wrapper = TestUtils.renderIntoDocument( <Dropdown className="container" contentClassName="content" renderToggle={ ( { isOpen, onToggle } ) => ( <button aria-expanded={ isOpen } onClick={ onToggle }>Toggleee</button> ) } - renderContent={ () => null } + renderContent={ () => <span>test</span> } /> ); expectButtonExpanded( wrapper, false ); diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 4515cd2facfe78..c303b1c75acb07 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component, createRef } from '@wordpress/element'; +import { useRef, useState, useEffect } from '@wordpress/element'; import { focus } from '@wordpress/dom'; import { ESCAPE } from '@wordpress/keycodes'; import isShallowEqual from '@wordpress/is-shallow-equal'; @@ -33,226 +33,269 @@ const FocusManaged = withConstrainedTabbing( withFocusReturn( ( { children } ) = */ const SLOT_NAME = 'Popover'; -class Popover extends Component { - constructor() { - super( ...arguments ); - - this.getAnchorRect = this.getAnchorRect.bind( this ); - this.computePopoverPosition = this.computePopoverPosition.bind( this ); - this.maybeClose = this.maybeClose.bind( this ); - this.throttledRefresh = this.throttledRefresh.bind( this ); - this.refresh = this.refresh.bind( this ); - this.refreshOnAnchorMove = this.refreshOnAnchorMove.bind( this ); - - this.contentNode = createRef(); - this.anchorNode = createRef(); - - this.state = { - popoverLeft: null, - popoverTop: null, - yAxis: 'top', - xAxis: 'center', - contentHeight: null, - contentWidth: null, - isMobile: false, - popoverSize: null, - - // Delay the animation after the initial render - // because the animation have impact on the height of the popover - // causing the computed position to be wrong. - isReadyToAnimate: false, +/** + * Hook used trigger an event handler once the window is resized or scrolled. + * + * @param {function} handler Event handler. + * @param {Object} ignoredScrollalbeRef scroll events inside this element are ignored. + */ +function useThrottledWindowScrollOrResize( handler, ignoredScrollalbeRef ) { + // Refresh anchor rect on resize + useEffect( () => { + let refreshHandle; + const throttledRefresh = ( event ) => { + window.cancelAnimationFrame( refreshHandle ); + if ( ignoredScrollalbeRef && event && event.type === 'scroll' && ignoredScrollalbeRef.current.contains( event.target ) ) { + return; + } + refreshHandle = window.requestAnimationFrame( handler ); }; - // Property used keep track of the previous anchor rect - // used to compute the popover position and size. - this.anchorRect = {}; - } - - componentDidMount() { - this.toggleAutoRefresh( ! this.props.hasOwnProperty( 'anchorRect' ) ); - this.refresh(); + window.addEventListener( 'resize', throttledRefresh ); + window.addEventListener( 'scroll', throttledRefresh ); - /* - * Without the setTimeout, the dom node is not being focused. Related: - * https://stackoverflow.com/questions/35522220/react-ref-with-focus-doesnt-work-without-settimeout-my-example - * - * TODO: Treat the cause, not the symptom. - */ - this.focusTimeout = setTimeout( () => { - this.focus(); - }, 0 ); - } + return () => { + window.removeEventListener( 'resize', throttledRefresh ); + window.removeEventListener( 'scroll', throttledRefresh ); + }; + }, [] ); +} - componentDidUpdate( prevProps ) { - if ( prevProps.position !== this.props.position ) { - this.computePopoverPosition( this.state.popoverSize, this.anchorRect ); +/** + * Hook used to compute and update the anchor position properly. + * + * @param {Object} anchorRef reference to the popover anchor element. + * @param {Object} contentRef reference to the popover content element. + * @param {Object} anchorRect anchor Rect prop used to override the computed value. + * @param {Function} getAnchorRect function used to override the anchor value computation algorithm. + * + * @return {Object} Anchor position. + */ +function useAnchor( anchorRef, contentRef, anchorRect, getAnchorRect ) { + const [ anchor, setAnchor ] = useState( null ); + const refreshAnchorRect = () => { + if ( ! anchorRef.current ) { + return; } - if ( prevProps.anchorRect !== this.props.anchorRect ) { - this.refreshOnAnchorMove(); + let newAnchor; + if ( anchorRect ) { + newAnchor = anchorRect; + } else if ( getAnchorRect ) { + newAnchor = getAnchorRect( anchorRef.current ); + } else { + const rect = anchorRef.current.parentNode.getBoundingClientRect(); + // subtract padding + const { paddingTop, paddingBottom } = window.getComputedStyle( anchorRef.current.parentNode ); + const topPad = parseInt( paddingTop, 10 ); + const bottomPad = parseInt( paddingBottom, 10 ); + newAnchor = { + x: rect.left, + y: rect.top + topPad, + width: rect.width, + height: rect.height - topPad - bottomPad, + left: rect.left, + right: rect.right, + top: rect.top + topPad, + bottom: rect.bottom - bottomPad, + }; } - const hasAnchorRect = this.props.hasOwnProperty( 'anchorRect' ); - if ( hasAnchorRect !== prevProps.hasOwnProperty( 'anchorRect' ) ) { - this.toggleAutoRefresh( ! hasAnchorRect ); + const didAnchorRectChange = ! isShallowEqual( newAnchor, anchor ); + if ( didAnchorRectChange ) { + setAnchor( newAnchor ); } - } + }; + useEffect( refreshAnchorRect, [ anchorRect, getAnchorRect ] ); + useEffect( () => { + if ( ! anchorRect ) { + /* + * There are sometimes we need to reposition or resize the popover that are not + * handled by the resize/scroll window events (i.e. CSS changes in the layout + * that changes the position of the anchor). + * + * For these situations, we refresh the popover every 0.5s + */ + const intervalHandle = setInterval( refreshAnchorRect, 500 ); + + return () => clearInterval( intervalHandle ); + } + }, [ anchorRect ] ); - componentWillUnmount() { - clearTimeout( this.focusTimeout ); - this.toggleAutoRefresh( false ); - } + useThrottledWindowScrollOrResize( refreshAnchorRect, contentRef ); - toggleAutoRefresh( isActive ) { - window.cancelAnimationFrame( this.rafHandle ); + return anchor; +} - // Refresh the popover every time the window is resized or scrolled - const handler = isActive ? 'addEventListener' : 'removeEventListener'; - window[ handler ]( 'resize', this.throttledRefresh ); - window[ handler ]( 'scroll', this.throttledRefresh, true ); +/** + * Hook used to compute the initial size of an element. + * The popover applies styling to limit the height of the element, + * we only care about the initial size. + * + * @param {Object} ref Reference to the popover content element. + * + * @return {Object} Content size. + */ +function useInitialContentSize( ref ) { + const [ contentSize, setContentSize ] = useState( null ); + useEffect( () => { + const contentRect = ref.current.getBoundingClientRect(); + setContentSize( { + width: contentRect.width, + height: contentRect.height, + } ); + }, [] ); - /* - * There are sometimes we need to reposition or resize the popover that are not - * handled by the resize/scroll window events (i.e. CSS changes in the layout - * that changes the position of the anchor). - * - * For these situations, we refresh the popover every 0.5s - */ - if ( isActive ) { - this.autoRefresh = setInterval( this.refreshOnAnchorMove, 500 ); - } else { - clearInterval( this.autoRefresh ); - } - } + return contentSize; +} - throttledRefresh( event ) { - window.cancelAnimationFrame( this.rafHandle ); - if ( event && event.type === 'scroll' && this.contentNode.current.contains( event.target ) ) { +/** + * Hook used to compute and update the position of the popover + * based on the anchor position and the content size. + * + * @param {Object} anchor Anchor Position. + * @param {Object} contentSize Content Size. + * @param {string} position Position prop. + * @param {boolean} expandOnMobile Whether to show the popover full width on mobile. + * @param {Object} contentRef Reference to the popover content element. + * + * @return {Object} Popover position. + */ +function usePopoverPosition( anchor, contentSize, position, expandOnMobile, contentRef ) { + const [ popoverPosition, setPopoverPosition ] = useState( { + popoverLeft: null, + popoverTop: null, + yAxis: 'top', + xAxis: 'center', + contentHeight: null, + contentWidth: null, + isMobile: false, + } ); + const refreshPopoverPosition = () => { + if ( ! anchor || ! contentSize ) { return; } - this.rafHandle = window.requestAnimationFrame( this.refresh ); - } - /** - * Calling refreshOnAnchorMove - * will only refresh the popover position if the anchor moves. - */ - refreshOnAnchorMove() { - const anchorRect = this.getAnchorRect( this.anchorNode.current ); - const didAnchorRectChange = ! isShallowEqual( anchorRect, this.anchorRect ); - if ( didAnchorRectChange ) { - this.anchorRect = anchorRect; - this.computePopoverPosition( this.state.popoverSize, anchorRect ); - } - } - - /** - * Calling `refresh()` will force the Popover to recalculate its size and - * position. This is useful when a DOM change causes the anchor node to change - * position. - */ - refresh() { - const anchorRect = this.getAnchorRect( this.anchorNode.current ); - const contentRect = this.contentNode.current.getBoundingClientRect(); - const popoverSize = { - width: contentRect.width, - height: contentRect.height, - }; - const didPopoverSizeChange = ! this.state.popoverSize || ( - popoverSize.width !== this.state.popoverSize.width || - popoverSize.height !== this.state.popoverSize.height + const newPopoverPosition = computePopoverPosition( + anchor, + contentSize, + position, + expandOnMobile ); - if ( didPopoverSizeChange ) { - this.setState( { popoverSize, isReadyToAnimate: true } ); + + if ( + popoverPosition.yAxis !== newPopoverPosition.yAxis || + popoverPosition.xAxis !== newPopoverPosition.xAxis || + popoverPosition.popoverLeft !== newPopoverPosition.popoverLeft || + popoverPosition.popoverTop !== newPopoverPosition.popoverTop || + popoverPosition.contentHeight !== newPopoverPosition.contentHeight || + popoverPosition.contentWidth !== newPopoverPosition.contentWidth || + popoverPosition.isMobile !== newPopoverPosition.isMobile + ) { + setPopoverPosition( newPopoverPosition ); } - this.anchorRect = anchorRect; - this.computePopoverPosition( popoverSize, anchorRect ); - } + }; + useEffect( refreshPopoverPosition, [ anchor, contentSize ] ); + useThrottledWindowScrollOrResize( refreshPopoverPosition, contentRef ); - focus() { - const { focusOnMount } = this.props; + return popoverPosition; +} - if ( ! focusOnMount || ! this.contentNode.current ) { - return; - } +/** + * Hook used to focus the first tabbable element on mount. + * + * @param {boolean|string} focusOnMount Focus on mount mode. + * @param {Object} contentRef Reference to the popover content element. + */ +function useFocusContentOnMount( focusOnMount, contentRef ) { + // Focus handling + useEffect( () => { + /* + * Without the setTimeout, the dom node is not being focused. Related: + * https://stackoverflow.com/questions/35522220/react-ref-with-focus-doesnt-work-without-settimeout-my-example + * + * TODO: Treat the cause, not the symptom. + */ + const focusTimeout = setTimeout( () => { + if ( ! focusOnMount || ! contentRef.current ) { + return; + } - if ( focusOnMount === 'firstElement' ) { + if ( focusOnMount === 'firstElement' ) { // Find first tabbable node within content and shift focus, falling // back to the popover panel itself. - const firstTabbable = focus.tabbable.find( this.contentNode.current )[ 0 ]; - - if ( firstTabbable ) { - firstTabbable.focus(); - } else { - this.contentNode.current.focus(); + const firstTabbable = focus.tabbable.find( contentRef.current )[ 0 ]; + if ( firstTabbable ) { + firstTabbable.focus(); + } else { + contentRef.current.focus(); + } + + return; } - return; - } - - if ( focusOnMount === 'container' ) { + if ( focusOnMount === 'container' ) { // Focus the popover panel itself so items in the popover are easily // accessed via keyboard navigation. - this.contentNode.current.focus(); - } - } - - getAnchorRect( anchor ) { - const { getAnchorRect, anchorRect } = this.props; - - if ( anchorRect ) { - return anchorRect; - } + contentRef.current.focus(); + } + }, 0 ); - if ( getAnchorRect ) { - return getAnchorRect( anchor ); - } + return () => clearTimeout( focusTimeout ); + }, [] ); +} - if ( ! anchor || ! anchor.parentNode ) { - return; +const Popover = ( { + headerTitle, + onClose, + onKeyDown, + children, + className, + onClickOutside = onClose, + noArrow = false, + // Disable reason: We generate the `...contentProps` rest as remainder + // of props which aren't explicitly handled by this component. + /* eslint-disable no-unused-vars */ + position = 'top', + range, + focusOnMount = 'firstElement', + anchorRect, + getAnchorRect, + expandOnMobile, + animate = true, + /* eslint-enable no-unused-vars */ + ...contentProps +} ) => { + const anchorRef = useRef( null ); + const contentRef = useRef( null ); + + // Animation + const [ isReadyToAnimate, setIsReadyToAnimate ] = useState( false ); + + // Anchor position + const anchor = useAnchor( anchorRef, contentRef, anchorRect, getAnchorRect ); + + // Content size + const contentSize = useInitialContentSize( contentRef ); + useEffect( () => { + if ( contentSize ) { + setIsReadyToAnimate( true ); } - const rect = anchor.parentNode.getBoundingClientRect(); - // subtract padding - const { paddingTop, paddingBottom } = window.getComputedStyle( anchor.parentNode ); - const topPad = parseInt( paddingTop, 10 ); - const bottomPad = parseInt( paddingBottom, 10 ); - return { - x: rect.left, - y: rect.top + topPad, - width: rect.width, - height: rect.height - topPad - bottomPad, - left: rect.left, - right: rect.right, - top: rect.top + topPad, - bottom: rect.bottom - bottomPad, - }; - } + }, [ contentSize ] ); - computePopoverPosition( popoverSize, anchorRect ) { - const { position = 'top', expandOnMobile } = this.props; - const newPopoverPosition = computePopoverPosition( - anchorRect, - popoverSize, - position, - expandOnMobile - ); + // Compute the position + const popoverPosition = usePopoverPosition( + anchor, + contentSize, + position, + expandOnMobile, + contentRef + ); - if ( - this.state.yAxis !== newPopoverPosition.yAxis || - this.state.xAxis !== newPopoverPosition.xAxis || - this.state.popoverLeft !== newPopoverPosition.popoverLeft || - this.state.popoverTop !== newPopoverPosition.popoverTop || - this.state.contentHeight !== newPopoverPosition.contentHeight || - this.state.contentWidth !== newPopoverPosition.contentWidth || - this.state.isMobile !== newPopoverPosition.isMobile - ) { - this.setState( newPopoverPosition ); - } - } - - maybeClose( event ) { - const { onKeyDown, onClose } = this.props; + useFocusContentOnMount( focusOnMount, contentRef ); + // Event handlers + const maybeClose = ( event ) => { // Close on escape if ( event.keyCode === ESCAPE && onClose ) { event.stopPropagation(); @@ -263,141 +306,105 @@ class Popover extends Component { if ( onKeyDown ) { onKeyDown( event ); } - } - - render() { - const { - headerTitle, - onClose, - children, - className, - onClickOutside = onClose, - noArrow, - // Disable reason: We generate the `...contentProps` rest as remainder - // of props which aren't explicitly handled by this component. - /* eslint-disable no-unused-vars */ - position, - range, - focusOnMount, - getAnchorRect, - expandOnMobile, - animate = true, - anchorRect, - /* eslint-enable no-unused-vars */ - ...contentProps - } = this.props; - const { - popoverLeft, - popoverTop, - yAxis, - xAxis, - contentHeight, - contentWidth, - popoverSize, - isMobile, - isReadyToAnimate, - } = this.state; - - // Compute the animation position - const yAxisMapping = { - top: 'bottom', - bottom: 'top', - }; - const xAxisMapping = { - left: 'right', - right: 'left', - }; - const animateYAxis = yAxisMapping[ yAxis ] || 'middle'; - const animateXAxis = xAxisMapping[ xAxis ] || 'center'; - - const classes = classnames( - 'components-popover', - className, - 'is-' + yAxis, - 'is-' + xAxis, - { - 'is-mobile': isMobile, - 'is-without-arrow': noArrow || ( xAxis === 'center' && yAxis === 'middle' ), - } - ); - - // Disable reason: We care to capture the _bubbled_ events from inputs - // within popover as inferring close intent. - - /* eslint-disable jsx-a11y/no-static-element-interactions */ - let content = ( - <PopoverDetectOutside onClickOutside={ onClickOutside }> - <Animate - type={ animate && isReadyToAnimate ? 'appear' : null } - options={ { origin: animateYAxis + ' ' + animateXAxis } } - > - { ( { className: animateClassName } ) => ( - <IsolatedEventContainer - className={ classnames( classes, animateClassName ) } + }; + + // Compute the animation position + const yAxisMapping = { + top: 'bottom', + bottom: 'top', + }; + const xAxisMapping = { + left: 'right', + right: 'left', + }; + const animateYAxis = yAxisMapping[ popoverPosition.yAxis ] || 'middle'; + const animateXAxis = xAxisMapping[ popoverPosition.xAxis ] || 'center'; + + const classes = classnames( + 'components-popover', + className, + 'is-' + popoverPosition.yAxis, + 'is-' + popoverPosition.xAxis, + { + 'is-mobile': popoverPosition.isMobile, + 'is-without-arrow': noArrow || ( + popoverPosition.xAxis === 'center' && + popoverPosition.yAxis === 'middle' + ), + } + ); + + // Disable reason: We care to capture the _bubbled_ events from inputs + // within popover as inferring close intent. + + /* eslint-disable jsx-a11y/no-static-element-interactions */ + let content = ( + <PopoverDetectOutside onClickOutside={ onClickOutside }> + <Animate + type={ animate && isReadyToAnimate ? 'appear' : null } + options={ { origin: animateYAxis + ' ' + animateXAxis } } + > + { ( { className: animateClassName } ) => ( + <IsolatedEventContainer + className={ classnames( classes, animateClassName ) } + style={ { + top: ! popoverPosition.isMobile && popoverPosition.popoverTop ? popoverPosition.popoverTop + 'px' : undefined, + left: ! popoverPosition.isMobile && popoverPosition.popoverLeft ? popoverPosition.popoverLeft + 'px' : undefined, + visibility: contentSize ? undefined : 'hidden', + } } + { ...contentProps } + onKeyDown={ maybeClose } + > + { popoverPosition.isMobile && ( + <div className="components-popover__header"> + <span className="components-popover__header-title"> + { headerTitle } + </span> + <IconButton className="components-popover__close" icon="no-alt" onClick={ onClose } /> + </div> + ) } + <div + ref={ contentRef } + className="components-popover__content" style={ { - top: ! isMobile && popoverTop ? popoverTop + 'px' : undefined, - left: ! isMobile && popoverLeft ? popoverLeft + 'px' : undefined, - visibility: popoverSize ? undefined : 'hidden', + maxHeight: ! popoverPosition.isMobile && popoverPosition.contentHeight ? popoverPosition.contentHeight + 'px' : undefined, + maxWidth: ! popoverPosition.isMobile && popoverPosition.contentWidth ? popoverPosition.contentWidth + 'px' : undefined, } } - { ...contentProps } - onKeyDown={ this.maybeClose } + tabIndex="-1" > - { isMobile && ( - <div className="components-popover__header"> - <span className="components-popover__header-title"> - { headerTitle } - </span> - <IconButton className="components-popover__close" icon="no-alt" onClick={ onClose } /> - </div> - ) } - <div - ref={ this.contentNode } - className="components-popover__content" - style={ { - maxHeight: ! isMobile && contentHeight ? contentHeight + 'px' : undefined, - maxWidth: ! isMobile && contentWidth ? contentWidth + 'px' : undefined, - } } - tabIndex="-1" - > - { children } - </div> - </IsolatedEventContainer> - ) } - </Animate> - </PopoverDetectOutside> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - - // Apply focus to element as long as focusOnMount is truthy; false is - // the only "disabled" value. - if ( focusOnMount ) { - content = <FocusManaged>{ content }</FocusManaged>; - } - - return ( - <Consumer> - { ( { getSlot } ) => { - // In case there is no slot context in which to render, - // default to an in-place rendering. - if ( getSlot && getSlot( SLOT_NAME ) ) { - content = <Fill name={ SLOT_NAME }>{ content }</Fill>; - } - - return ( - <span ref={ this.anchorNode }> - { content } - { isMobile && expandOnMobile && <ScrollLock /> } - </span> - ); - } } - </Consumer> - ); + { children } + </div> + </IsolatedEventContainer> + ) } + </Animate> + </PopoverDetectOutside> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ + + // Apply focus to element as long as focusOnMount is truthy; false is + // the only "disabled" value. + if ( focusOnMount ) { + content = <FocusManaged>{ content }</FocusManaged>; } -} -Popover.defaultProps = { - focusOnMount: 'firstElement', - noArrow: false, + return ( + <Consumer> + { ( { getSlot } ) => { + // In case there is no slot context in which to render, + // default to an in-place rendering. + if ( getSlot && getSlot( SLOT_NAME ) ) { + content = <Fill name={ SLOT_NAME }>{ content }</Fill>; + } + + return ( + <span ref={ anchorRef }> + { content } + { popoverPosition.isMobile && expandOnMobile && <ScrollLock /> } + </span> + ); + } } + </Consumer> + ); }; const PopoverContainer = Popover; diff --git a/packages/components/src/popover/test/__snapshots__/index.js.snap b/packages/components/src/popover/test/__snapshots__/index.js.snap index a9c4ed32102d3b..9813681b30734e 100644 --- a/packages/components/src/popover/test/__snapshots__/index.js.snap +++ b/packages/components/src/popover/test/__snapshots__/index.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Popover #render() should pass additional props to portaled element 1`] = ` +exports[`Popover should pass additional props to portaled element 1`] = ` <span> <div tabindex="-1" @@ -23,7 +23,7 @@ exports[`Popover #render() should pass additional props to portaled element 1`] </span> `; -exports[`Popover #render() should render content 1`] = ` +exports[`Popover should render content 1`] = ` <span> <div tabindex="-1" diff --git a/packages/components/src/popover/test/index.js b/packages/components/src/popover/test/index.js index 0ba92b4337c924..1ae2b3e932fc59 100644 --- a/packages/components/src/popover/test/index.js +++ b/packages/components/src/popover/test/index.js @@ -2,108 +2,83 @@ * External dependencies */ import TestUtils from 'react-dom/test-utils'; -import ReactDOM from 'react-dom'; -import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; /** * Internal dependencies */ import Popover from '../'; -describe( 'Popover', () => { - describe( '#componentDidUpdate()', () => { - let wrapper; - beforeEach( () => { - jest.spyOn( Popover.prototype, 'computePopoverPosition' ).mockImplementation( noop ); - jest.spyOn( Popover.prototype, 'toggleAutoRefresh' ).mockImplementation( noop ); - } ); - - afterEach( () => { - jest.restoreAllMocks(); - - // Resetting keyboard state is deferred, so ensure that timers are - // consumed to avoid leaking into other tests. - jest.runAllTimers(); - - if ( document.activeElement ) { - document.activeElement.blur(); - } - } ); +jest.useFakeTimers(); - it( 'should turn on auto refresh', () => { - wrapper = TestUtils.renderIntoDocument( <Popover /> ); - expect( Popover.prototype.toggleAutoRefresh ).toHaveBeenCalledWith( true ); - expect( Popover.prototype.computePopoverPosition ).toHaveBeenCalled(); - } ); +class PopoverWrapper extends Component { + render() { + return <Popover { ...this.props } />; + } +} - it( 'should turn off auto refresh', () => { - wrapper = TestUtils.renderIntoDocument( <Popover /> ); - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); - expect( Popover.prototype.toggleAutoRefresh ).toHaveBeenCalledWith( false ); - } ); +describe( 'Popover', () => { + afterEach( () => { + if ( document.activeElement ) { + document.activeElement.blur(); + } + } ); - it( 'should set offset and forced positions on changed position', () => { - const node = document.createElement( 'div' ); - wrapper = ReactDOM.render( <Popover />, node ); - jest.clearAllMocks(); + it( 'should focus when opening in response to keyboard event', () => { + // As in the real world, these occur in sequence before the popover + // has been mounted. Keyup's resetting is deferred. + document.dispatchEvent( new window.KeyboardEvent( 'keydown' ) ); + document.dispatchEvent( new window.KeyboardEvent( 'keyup' ) ); - ReactDOM.render( <Popover position={ 'bottom right' } />, node ); + // An ideal test here would mount with an input child and focus the + // child, but in context of JSDOM the inputs are not visible and + // are therefore skipped as tabbable, defaulting to popover. + let wrapper; + TestUtils.act( () => { + wrapper = TestUtils.renderIntoDocument( <PopoverWrapper /> ); - expect( Popover.prototype.toggleAutoRefresh ).not.toHaveBeenCalled(); - expect( Popover.prototype.computePopoverPosition ).toHaveBeenCalled(); - } ); + jest.advanceTimersByTime( 1 ); - it( 'should focus when opening in response to keyboard event', ( done ) => { - // As in the real world, these occur in sequence before the popover - // has been mounted. Keyup's resetting is deferred. - document.dispatchEvent( new window.KeyboardEvent( 'keydown' ) ); - document.dispatchEvent( new window.KeyboardEvent( 'keyup' ) ); - - // An ideal test here would mount with an input child and focus the - // child, but in context of JSDOM the inputs are not visible and - // are therefore skipped as tabbable, defaulting to popover. - wrapper = TestUtils.renderIntoDocument( <Popover /> ); - - setTimeout( () => { - const content = TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-popover__content' - ); - expect( document.activeElement ).toBe( content ); - done(); - }, 1 ); - - jest.runAllTimers(); + const content = TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-popover__content' + ); + expect( document.activeElement ).toBe( content ); } ); + } ); - it( 'should allow focus-on-open behavior to be disabled', ( done ) => { - const activeElement = document.activeElement; - - wrapper = TestUtils.renderIntoDocument( <Popover focusOnMount={ false } /> ); + it( 'should allow focus-on-open behavior to be disabled', () => { + const activeElement = document.activeElement; + TestUtils.act( () => { + TestUtils.renderIntoDocument( <Popover focusOnMount={ false } /> ); - setTimeout( () => { - expect( document.activeElement ).toBe( activeElement ); - done(); - } ); + jest.advanceTimersByTime( 1 ); - jest.runAllTimers(); + expect( document.activeElement ).toBe( activeElement ); } ); } ); - describe( '#render()', () => { - it( 'should render content', () => { - const wrapper = TestUtils.renderIntoDocument( <Popover>Hello</Popover> ); - const content = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'span' ); - - expect( content ).toMatchSnapshot(); + it( 'should render content', () => { + let wrapper; + TestUtils.act( () => { + wrapper = TestUtils.renderIntoDocument( <PopoverWrapper>Hello</PopoverWrapper> ); } ); + const content = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'span' ); - it( 'should pass additional props to portaled element', () => { - const wrapper = TestUtils.renderIntoDocument( <Popover role="tooltip">Hello</Popover> ); - const content = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'span' ); + expect( content ).toMatchSnapshot(); + } ); - expect( content ).toMatchSnapshot(); + it( 'should pass additional props to portaled element', () => { + let wrapper; + TestUtils.act( () => { + wrapper = TestUtils.renderIntoDocument( <PopoverWrapper role="tooltip">Hello</PopoverWrapper> ); } ); + const content = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'span' ); + + expect( content ).toMatchSnapshot(); } ); } ); From d08dcd89fd83cdcd0f899b3df8c4a0d4565976bd Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 17 May 2019 10:35:23 -0700 Subject: [PATCH 126/664] Fix links from old to new handbook (#15705) Updates links to go straight to the section of the new handbook location. A generic redirect is setup, but also better to refer directly to the canonical URL. --- README.md | 4 ++-- .../backward-compatibility/deprecations.md | 2 +- .../block-tutorial/generate-blocks-with-wp-cli.md | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 861c13ff675a00..9072e34081d6b5 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Blocks are the unifying evolution of what is now covered, in different ways, by Imagine a custom `employee` block that a client can drag onto an `About` page to automatically display a picture, name, and bio of all the employees. Imagine a whole universe of plugins just as flexible, all extending WordPress in the same way. Imagine simplified menus and widgets. Users who can instantly understand and use WordPress—and 90% of plugins. This will allow you to easily compose beautiful posts like <a href="http://moc.co/sandbox/example-post/">this example</a>. -Check out the <a href="https://wordpress.org/gutenberg/handbook/designers-developers/faq/">FAQ</a> for answers to the most common questions about the project. +Check out the <a href="https://developer.wordpress.org/block-editor/contributors/faq/">FAQ</a> for answers to the most common questions about the project. ## Compatibility @@ -71,7 +71,7 @@ Please see <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUT - <a href="http://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/">Gutenberg, or the Ship of Theseus</a>, with examples of what Gutenberg might do in the future - <a href="https://make.wordpress.org/core/2017/01/17/editor-technical-overview/">Editor Technical Overview</a> -- <a href="https://wordpress.org/gutenberg/handbook/contributors/design/">Design Principles and block design best practices</a> +- <a href="https://developer.wordpress.org/block-editor/contributors/design/">Design Principles and block design best practices</a> - <a href="https://github.com/Automattic/wp-post-grammar">WP Post Grammar Parser</a> - <a href="https://make.wordpress.org/core/tag/gutenberg/">Development updates on make.wordpress.org</a> - <a href="https://wordpress.org/gutenberg/handbook/">Documentation: Creating Blocks, Reference, and Guidelines</a> diff --git a/docs/designers-developers/developers/backward-compatibility/deprecations.md b/docs/designers-developers/developers/backward-compatibility/deprecations.md index 5d5e2e3904fb9a..9421833ba0290e 100644 --- a/docs/designers-developers/developers/backward-compatibility/deprecations.md +++ b/docs/designers-developers/developers/backward-compatibility/deprecations.md @@ -59,7 +59,7 @@ For features included in the Gutenberg plugin, the deprecation policy is intende - The PHP function `gutenberg_get_block_categories` has been removed. Use [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/) instead. - The PHP function `register_tinymce_scripts` has been removed. Use [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/) instead. - The PHP function `gutenberg_register_post_types` has been removed. -- The `gutenberg` theme support option has been removed. Use [`align-wide`](https://wordpress.org/gutenberg/handbook/designers-developers/developers/themes/theme-support/#wide-alignment) instead. +- The `gutenberg` theme support option has been removed. Use [`align-wide`](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#wide-alignment) instead. - The PHP function `gutenberg_prepare_blocks_for_js` has been removed. Use [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/) instead. - The PHP function `gutenberg_load_list_reusable_blocks` has been removed. - The PHP function `_gutenberg_utf8_split` has been removed. Use `_mb_substr` instead. diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md b/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md index acfe63dac2fe70..6108ec88b9da8e 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md @@ -62,7 +62,7 @@ This will generate 4 files inside the `movies` plugin directory. All files conta * Registers all block assets so that they can be enqueued through Gutenberg in * the corresponding context. * - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type/ + * @see https://developer.wordpress.org/block-editor/tutorials/block-tutorial/writing-your-first-block-type/ */ function movie_block_init() { $dir = dirname( __FILE__ ); @@ -109,23 +109,23 @@ add_action( 'init', 'movie_block_init' ); ( function( wp ) { /** * Registers a new block provided a unique name and an object defining its behavior. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block + * @see https://developer.wordpress.org/block-editor/developers/block-api/block-registration/ */ var registerBlockType = wp.blocks.registerBlockType; /** * Returns a new element of given type. Element is an abstraction layer atop React. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-element/ + * @see https://developer.wordpress.org/block-editor/packages/packages-element/ */ var el = wp.element.createElement; /** * Retrieves the translation of text. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-i18n/ + * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/ */ var __ = wp.i18n.__; /** * Every block starts by registering a new block type definition. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block + * @see https://developer.wordpress.org/block-editor/developers/block-api/block-registration/ */ registerBlockType( 'movies/movie', { /** @@ -151,7 +151,7 @@ add_action( 'init', 'movie_block_init' ); /** * The edit function describes the structure of your block in the context of the editor. * This represents what the editor will render when the block is used. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit + * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/ * * @param {Object} [props] Properties passed from the editor. * @return {Element} Element to render. @@ -167,7 +167,7 @@ add_action( 'init', 'movie_block_init' ); /** * The save function defines the way in which the different attributes should be combined * into the final markup, which is then serialized by Gutenberg into `post_content`. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save + * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save * * @return {Element} Element to render. */ From efedf931ca994b555819c6943184b224c8bdbd56 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 17 May 2019 14:58:43 -0400 Subject: [PATCH 127/664] Build Tooling: Remove WebpackRTLPlugin (#15711) --- package-lock.json | 1211 ++------------------------------------------- package.json | 3 +- webpack.config.js | 6 - 3 files changed, 41 insertions(+), 1179 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f830dff76a576..afe4c3a99dc7d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2572,87 +2572,6 @@ "physical-cpu-count": "^2.0.0" } }, - "@romainberger/css-diff": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@romainberger/css-diff/-/css-diff-1.0.3.tgz", - "integrity": "sha1-ztOHU11PQqQqwf4TwJ3pf1rhNEw=", - "dev": true, - "requires": { - "lodash.merge": "^4.4.0", - "postcss": "^5.0.21" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "@samverschueren/stream-to-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", @@ -5120,12 +5039,6 @@ "lodash.uniq": "^4.5.0" } }, - "caniuse-db": { - "version": "1.0.30000871", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000871.tgz", - "integrity": "sha1-8ZlcH+MYkmSadgWVeoDJJRhCPU0=", - "dev": true - }, "caniuse-lite": { "version": "1.0.30000957", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000957.tgz", @@ -5877,57 +5790,6 @@ "safe-buffer": "^5.0.1" } }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "^1.1.3" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -6192,17 +6054,6 @@ "color-name": "^1.0.0" } }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "^0.11.0", - "css-color-names": "0.0.4", - "has": "^1.0.1" - } - }, "colors": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", @@ -7742,12 +7593,6 @@ } } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -8079,12 +7924,6 @@ "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", "dev": true }, - "electron-to-chromium": { - "version": "1.3.52", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz", - "integrity": "sha1-0tnxJwuko7lnuDHEDvcftNmrXOA=", - "dev": true - }, "elegant-spinner": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", @@ -9475,12 +9314,6 @@ "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", "dev": true }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -13824,12 +13657,6 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, - "lodash.merge": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", - "dev": true - }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -14187,12 +14014,6 @@ "escape-string-regexp": "^1.0.4" } }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, "mathml-tag-names": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz", @@ -16854,216 +16675,55 @@ "postcss": "^7.0.0" } }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", "dev": true, "requires": { - "postcss": "^5.0.14", - "uniqs": "^2.0.0" + "htmlparser2": "^3.10.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } } } }, - "postcss-filter-plugins": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", - "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", + "postcss-jsx": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.0.tgz", + "integrity": "sha512-/lWOSXSX5jlITCKFkuYU2WLFdrncZmjSVyNpHAunEgirZXLwI8RjU556e3Uz4mv0WVHnJA9d3JWb36lK9Yx99g==", "dev": true, "requires": { - "postcss": "^5.0.4" + "@babel/core": ">=7.1.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "postcss-html": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", - "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", - "dev": true, - "requires": { - "htmlparser2": "^3.10.0" - }, - "dependencies": { - "htmlparser2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", - "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", - "dev": true, - "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.0.6" - } - }, - "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "postcss-jsx": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.0.tgz", - "integrity": "sha512-/lWOSXSX5jlITCKFkuYU2WLFdrncZmjSVyNpHAunEgirZXLwI8RjU556e3Uz4mv0WVHnJA9d3JWb36lK9Yx99g==", - "dev": true, - "requires": { - "@babel/core": ">=7.1.0" - }, - "dependencies": { - "@babel/core": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", - "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", + "@babel/core": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.2.tgz", + "integrity": "sha512-59vB0RWt09cAct5EIe58+NzGP4TFSD3Bz//2/ELy3ZeTeKF6VTD1AXlH8BGGbCX0PuobZBsIzO7IAI9PH67eKw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -17245,88 +16905,6 @@ "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", "dev": true }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.10", - "postcss-value-parser": "^3.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "postcss-merge-longhand": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", @@ -17658,87 +17236,6 @@ "postcss-value-parser": "^3.0.0" } }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "postcss-reduce-initial": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", @@ -17817,17 +17314,6 @@ "postcss": "^7.0.0" } }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, "postcss-svgo": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", @@ -17863,88 +17349,6 @@ "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", "dev": true }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "posthtml": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.11.3.tgz", @@ -18000,12 +17404,6 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, "pretty-format": { "version": "24.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", @@ -18252,16 +17650,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -18667,55 +18055,19 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } + "dev": true, + "requires": { + "util.promisify": "^1.0.0" } }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", "dev": true, "requires": { - "balanced-match": "^0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" } }, "redux": { @@ -20467,12 +19819,6 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, "string-argv": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", @@ -22717,477 +22063,6 @@ "tiny-lr": "^1.1.1" } }, - "webpack-rtl-plugin": { - "version": "github:yoavf/webpack-rtl-plugin#fc5a2f20dd99fde8f86f297844aefde601780fa3", - "from": "github:yoavf/webpack-rtl-plugin#develop", - "dev": true, - "requires": { - "@romainberger/css-diff": "^1.0.3", - "async": "^2.0.0-rc.6", - "cssnano": "^3.7.1", - "postcss": "^5.0.21", - "rtlcss": "^2.0.4", - "webpack-sources": "^0.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "^1.7.6", - "caniuse-db": "^1.0.30000634", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^5.2.16", - "postcss-value-parser": "^3.2.3" - } - }, - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" - } - }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "^1.3.6", - "caniuse-db": "^1.0.30000529", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "^1.1.2" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "^6.3.1", - "decamelize": "^1.1.2", - "defined": "^1.0.0", - "has": "^1.0.1", - "object-assign": "^4.0.1", - "postcss": "^5.0.14", - "postcss-calc": "^5.2.0", - "postcss-colormin": "^2.1.8", - "postcss-convert-values": "^2.3.4", - "postcss-discard-comments": "^2.0.4", - "postcss-discard-duplicates": "^2.0.1", - "postcss-discard-empty": "^2.0.1", - "postcss-discard-overridden": "^0.1.1", - "postcss-discard-unused": "^2.2.1", - "postcss-filter-plugins": "^2.0.0", - "postcss-merge-idents": "^2.1.5", - "postcss-merge-longhand": "^2.0.1", - "postcss-merge-rules": "^2.0.3", - "postcss-minify-font-values": "^1.0.2", - "postcss-minify-gradients": "^1.0.1", - "postcss-minify-params": "^1.0.4", - "postcss-minify-selectors": "^2.0.4", - "postcss-normalize-charset": "^1.1.0", - "postcss-normalize-url": "^3.0.7", - "postcss-ordered-values": "^2.1.0", - "postcss-reduce-idents": "^2.2.2", - "postcss-reduce-initial": "^1.0.0", - "postcss-reduce-transforms": "^1.0.3", - "postcss-svgo": "^2.1.1", - "postcss-unique-selectors": "^2.0.2", - "postcss-value-parser": "^3.2.3", - "postcss-zindex": "^2.0.1" - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "^1.0.9", - "source-map": "^0.5.3" - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" - } - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "^5.0.2", - "postcss-message-helpers": "^2.0.0", - "reduce-css-calc": "^1.2.6" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "^1.0.5", - "postcss": "^5.0.13", - "postcss-value-parser": "^3.2.3" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "^5.0.11", - "postcss-value-parser": "^3.1.2" - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "^5.0.14" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "^5.0.14" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "^5.0.16" - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "^1.5.2", - "caniuse-api": "^1.5.2", - "postcss": "^5.0.4", - "postcss-selector-parser": "^2.2.2", - "vendors": "^1.0.0" - } - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "dev": true, - "requires": { - "postcss": "^5.0.12", - "postcss-value-parser": "^3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.2", - "postcss-value-parser": "^3.0.2", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.2", - "has": "^1.0.1", - "postcss": "^5.0.14", - "postcss-selector-parser": "^2.0.0" - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "^5.0.5" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^1.4.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.1" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.8", - "postcss-value-parser": "^3.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "^2.0.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3", - "svgo": "^0.7.0" - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "source-list-map": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", - "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "~1.0.1", - "colors": "~1.1.2", - "csso": "~2.3.1", - "js-yaml": "~3.7.0", - "mkdirp": "~0.5.1", - "sax": "~1.2.1", - "whet.extend": "~0.9.9" - } - }, - "webpack-sources": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-0.1.5.tgz", - "integrity": "sha1-qh86vw8NdNtxEcQOUAuE+WZkB1A=", - "dev": true, - "requires": { - "source-list-map": "~0.1.7", - "source-map": "~0.5.3" - } - } - } - }, "webpack-sources": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", @@ -23261,12 +22136,6 @@ "webidl-conversions": "^4.0.2" } }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 7986263f49cfe0..8b0a3453903d4b 100644 --- a/package.json +++ b/package.json @@ -121,8 +121,7 @@ "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", - "webpack": "4.8.3", - "webpack-rtl-plugin": "github:yoavf/webpack-rtl-plugin#develop" + "webpack": "4.8.3" }, "npmPackageJsonLintConfig": { "extends": "@wordpress/npm-package-json-lint-config", diff --git a/webpack.config.js b/webpack.config.js index a8b11fd9aac6f9..ba130ce0992d4a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,6 @@ * External dependencies */ const { DefinePlugin } = require( 'webpack' ); -const WebpackRTLPlugin = require( 'webpack-rtl-plugin' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); const postcss = require( 'postcss' ); const { get, escapeRegExp } = require( 'lodash' ); @@ -47,11 +46,6 @@ module.exports = { // eslint-disable-next-line @wordpress/gutenberg-phase 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), } ), - // Create RTL files with a -rtl suffix - new WebpackRTLPlugin( { - suffix: '-rtl', - minify: defaultConfig.mode === 'production' ? { safe: true } : false, - } ), new CustomTemplatedPathPlugin( { basename( path, data ) { let rawRequest; From eddd4fcd30f6a4ac8ce221888a8bcde397158476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Sat, 18 May 2019 19:02:45 +0200 Subject: [PATCH 128/664] RichText: stabilize onSplit (#14765) * RichText: stablize onSplit * Select the correct block * Add e2e tests * Fix e2e test * Fix styled paragraph split * Remove paragraph special onReplace * Address comments * Update packages/block-editor/src/components/rich-text/index.js Co-Authored-By: Riad Benguella <benguella@gmail.com> --- .../developers/data/data-core-block-editor.md | 1 + .../src/components/block-list/block.js | 4 +- .../src/components/rich-text/README.md | 6 +- .../src/components/rich-text/index.js | 130 +++++++++--------- packages/block-editor/src/store/actions.js | 9 +- packages/block-editor/src/store/reducer.js | 15 +- packages/block-library/src/heading/edit.js | 23 ++-- packages/block-library/src/list/edit.js | 28 ++-- packages/block-library/src/paragraph/edit.js | 74 ++-------- .../splitting-merging.test.js.snap | 6 + .../blocks/__snapshots__/heading.test.js.snap | 24 +++- .../e2e-tests/specs/blocks/heading.test.js | 19 ++- .../e2e-tests/specs/splitting-merging.test.js | 10 ++ 13 files changed, 171 insertions(+), 178 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 380b3366a64e00..6e496446d6be20 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -1008,6 +1008,7 @@ _Parameters_ - _clientIds_ `(string|Array<string>)`: Block client ID(s) to replace. - _blocks_ `(Object|Array<Object>)`: Replacement block(s). +- _indexToSelect_ `number`: Index of replacement block to select. <a name="replaceInnerBlocks" href="#replaceInnerBlocks">#</a> **replaceInnerBlocks** diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 87b3bccdc2bed9..cccebd02de640f 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -708,8 +708,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { } } }, - onReplace( blocks ) { - replaceBlocks( [ ownProps.clientId ], blocks ); + onReplace( blocks, indexToSelect ) { + replaceBlocks( [ ownProps.clientId ], blocks, indexToSelect ); }, onShiftSelection() { if ( ! ownProps.isSelectionEnabled ) { diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md index b3565b729cca3a..cca45bce3ec047 100644 --- a/packages/block-editor/src/components/rich-text/README.md +++ b/packages/block-editor/src/components/rich-text/README.md @@ -25,9 +25,13 @@ Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs *Optional.* By default, a line break will be inserted on <kbd>Enter</kbd>. If the editable field can contain multiple paragraphs, this property can be set to create new paragraphs on <kbd>Enter</kbd>. +### `onSplit( value: String ): Function` + +*Optional.* Called when the content can be split, where `value` is a piece of content being split off. Here you should create a new block with that content and return it. Note that you also need to provide `onReplace` in order for this to take any effect. + ### `onReplace( blocks: Array ): Function` -*Optional.* Called when the `RichText` instance is empty and it can be replaced with the given blocks. +*Optional.* Called when the `RichText` instance can be replaced with the given blocks. ### `onMerge( forward: Boolean ): Function` diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index a34e315adefac1..0b03bcca6a490d 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -39,10 +39,10 @@ import { __unstableIndentListItems as indentListItems, __unstableGetActiveFormats as getActiveFormats, __unstableUpdateFormats as updateFormats, + replace, } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; -import deprecated from '@wordpress/deprecated'; import isShallowEqual from '@wordpress/is-shallow-equal'; /** @@ -113,17 +113,6 @@ export class RichText extends Component { this.multilineWrapperTags = [ 'ul', 'ol' ]; } - if ( this.props.onSplit ) { - this.onSplit = this.props.onSplit; - - deprecated( 'wp.editor.RichText onSplit prop', { - plugin: 'Gutenberg', - alternative: 'wp.editor.RichText unstableOnSplit prop', - } ); - } else if ( this.props.unstableOnSplit ) { - this.onSplit = this.props.unstableOnSplit; - } - this.onFocus = this.onFocus.bind( this ); this.onBlur = this.onBlur.bind( this ); this.onChange = this.onChange.bind( this ); @@ -143,6 +132,7 @@ export class RichText extends Component { this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); this.onPointerDown = this.onPointerDown.bind( this ); + this.onSplit = this.onSplit.bind( this ); this.patterns = getPatterns( { onReplace, @@ -266,6 +256,8 @@ export class RichText extends Component { // Only process file if no HTML is present. // Note: a pasted file may have the URL as plain text. const item = find( [ ...items, ...files ], ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); + const record = this.getRecord(); + if ( item && ! html ) { const file = item.getAsFile ? item.getAsFile() : item; const content = pasteHandler( { @@ -281,14 +273,12 @@ export class RichText extends Component { if ( shouldReplace ) { this.props.onReplace( content ); } else if ( this.onSplit ) { - this.splitContent( content ); + this.onSplit( record, content ); } return; } - const record = this.getRecord(); - // There is a selection, check if a URL is pasted. if ( ! isCollapsed( record ) ) { const pastedText = ( html || plainText ).replace( /<[^>]+>/g, '' ).trim(); @@ -309,13 +299,14 @@ export class RichText extends Component { } } - const shouldReplace = this.props.onReplace && this.isEmpty(); + const canReplace = this.props.onReplace && this.isEmpty(); + const canSplit = this.props.onReplace && this.props.onSplit; let mode = 'INLINE'; - if ( shouldReplace ) { + if ( canReplace ) { mode = 'BLOCKS'; - } else if ( this.onSplit ) { + } else if ( canSplit ) { mode = 'AUTO'; } @@ -328,17 +319,20 @@ export class RichText extends Component { } ); if ( typeof content === 'string' ) { - const recordToInsert = create( { html: content } ); - this.onChange( insert( record, recordToInsert ) ); - } else if ( this.onSplit ) { - if ( ! content.length ) { - return; + let valueToInsert = create( { html: content } ); + + // If the content should be multiline, we should process text + // separated by a line break as separate lines. + if ( this.multilineTag ) { + valueToInsert = replace( valueToInsert, /\n+/g, LINE_SEPARATOR ); } - if ( shouldReplace ) { + this.onChange( insert( record, valueToInsert ) ); + } else if ( content.length > 0 ) { + if ( canReplace ) { this.props.onReplace( content ); } else { - this.splitContent( content, { paste: true } ); + this.onSplit( record, content ); } } } @@ -615,6 +609,8 @@ export class RichText extends Component { */ onKeyDown( event ) { const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; + const { onReplace, onSplit } = this.props; + const canSplit = onReplace && onSplit; if ( // Only override left and right keys without modifiers pressed. @@ -734,15 +730,15 @@ export class RichText extends Component { if ( this.multilineTag ) { if ( event.shiftKey ) { this.onChange( insert( record, '\n' ) ); - } else if ( this.onSplit && isEmptyLine( record ) ) { - this.onSplit( ...split( record ).map( this.valueToFormat ) ); + } else if ( canSplit && isEmptyLine( record ) ) { + this.onSplit( record ); } else { this.onChange( insertLineSeparator( record ) ); } - } else if ( event.shiftKey || ! this.onSplit ) { + } else if ( event.shiftKey || ! canSplit ) { this.onChange( insert( record, '\n' ) ); } else { - this.splitContent(); + this.onSplit( record ); } } } @@ -845,50 +841,56 @@ export class RichText extends Component { } /** - * Splits the content at the location of the selection. - * - * Replaces the content of the editor inside this element with the contents - * before the selection. Sends the elements after the selection to the `onSplit` - * handler. + * Signals to the RichText owner that the block can be replaced with two + * blocks as a result of splitting the block by pressing enter, or with + * blocks as a result of splitting the block by pasting block content in the + * instance. * - * @param {Array} blocks The blocks to add after the split point. - * @param {Object} context The context for splitting. + * @param {Object} record The rich text value to split. + * @param {Array} pastedBlocks The pasted blocks to insert, if any. */ - splitContent( blocks = [], context = {} ) { - if ( ! this.onSplit ) { + onSplit( record, pastedBlocks = [] ) { + const { + onReplace, + onSplit, + __unstableOnSplitMiddle: onSplitMiddle, + } = this.props; + + if ( ! onReplace || ! onSplit ) { return; } - const record = this.createRecord(); - let [ before, after ] = split( record ); - - // In case split occurs at the trailing or leading edge of the field, - // assume that the before/after values respectively reflect the current - // value. This also provides an opportunity for the parent component to - // determine whether the before/after value has changed using a trivial - // strict equality operation. - if ( isEmpty( after ) ) { - before = record; - } else if ( isEmpty( before ) ) { - after = record; - } + const blocks = []; + const [ before, after ] = split( record ); + const hasPastedBlocks = pastedBlocks.length > 0; - // If pasting and the split would result in no content other than the - // pasted blocks, remove the before and after blocks. - if ( context.paste ) { - before = isEmpty( before ) ? null : before; - after = isEmpty( after ) ? null : after; + // Create a block with the content before the caret if there's no pasted + // blocks, or if there are pasted blocks and the value is not empty. + // We do not want a leading empty block on paste, but we do if split + // with e.g. the enter key. + if ( ! hasPastedBlocks || ! isEmpty( before ) ) { + blocks.push( onSplit( this.valueToFormat( before ) ) ); } - if ( before ) { - before = this.valueToFormat( before ); + if ( hasPastedBlocks ) { + blocks.push( ...pastedBlocks ); + } else if ( onSplitMiddle ) { + blocks.push( onSplitMiddle() ); } - if ( after ) { - after = this.valueToFormat( after ); + // If there's pasted blocks, append a block with the content after the + // caret. Otherwise, do append and empty block if there is no + // `onSplitMiddle` prop, but if there is and the content is empty, the + // middle block is enough to set focus in. + if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { + blocks.push( onSplit( this.valueToFormat( after ) ) ); } - this.onSplit( before, after, ...blocks ); + // If there are pasted blocks, set the selection to the last one. + // Otherwise, set the selection to the second block. + const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; + + onReplace( blocks, indexToSelect ); } /** @@ -997,12 +999,6 @@ export class RichText extends Component { return value; } - // Guard for blocks passing `null` in onSplit callbacks. May be removed - // if onSplit is revised to not pass a `null` value. - if ( value === null ) { - return create(); - } - return value; } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 42d866c04b21d5..466a9cbeb96c26 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -218,12 +218,14 @@ export function toggleSelection( isSelectionEnabled = true ) { * Returns an action object signalling that a blocks should be replaced with * one or more replacement blocks. * - * @param {(string|string[])} clientIds Block client ID(s) to replace. - * @param {(Object|Object[])} blocks Replacement block(s). + * @param {(string|string[])} clientIds Block client ID(s) to replace. + * @param {(Object|Object[])} blocks Replacement block(s). + * @param {number} indexToSelect Index of replacement block to + * select. * * @yields {Object} Action object. */ -export function* replaceBlocks( clientIds, blocks ) { +export function* replaceBlocks( clientIds, blocks, indexToSelect ) { clientIds = castArray( clientIds ); blocks = castArray( blocks ); const rootClientId = yield select( @@ -249,6 +251,7 @@ export function* replaceBlocks( clientIds, blocks ) { clientIds, blocks, time: Date.now(), + indexToSelect, }; yield* ensureDefaultBlock(); } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 3b2f160fadeba8..d9d0ecbdd28b64 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -787,25 +787,24 @@ export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action ) return state; } - // If there are replacement blocks, assign last block as the next - // selected block, otherwise set to null. - const lastBlock = last( action.blocks ); + const indexToSelect = action.indexToSelect || action.blocks.length - 1; + const blockToSelect = action.blocks[ indexToSelect ]; - if ( ! lastBlock ) { + if ( ! blockToSelect ) { return BLOCK_SELECTION_INITIAL_STATE; } if ( - lastBlock.clientId === state.start.clientId && - lastBlock.clientId === state.end.clientId + blockToSelect.clientId === state.start.clientId && + blockToSelect.clientId === state.end.clientId ) { return state; } return { ...BLOCK_SELECTION_INITIAL_STATE, - start: { clientId: lastBlock.clientId }, - end: { clientId: lastBlock.clientId }, + start: { clientId: blockToSelect.clientId }, + end: { clientId: blockToSelect.clientId }, }; } case 'TOGGLE_SELECTION': diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 5c57b5e5d57bc1..048c1b9d67ddfc 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -20,7 +20,6 @@ export default function HeadingEdit( { attributes, setAttributes, mergeBlocks, - insertBlocksAfter, onReplace, className, } ) { @@ -52,17 +51,17 @@ export default function HeadingEdit( { value={ content } onChange={ ( value ) => setAttributes( { content: value } ) } onMerge={ mergeBlocks } - unstableOnSplit={ - insertBlocksAfter ? - ( before, after, ...blocks ) => { - setAttributes( { content: before } ); - insertBlocksAfter( [ - ...blocks, - createBlock( 'core/paragraph', { content: after } ), - ] ); - } : - undefined - } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( 'core/paragraph' ); + } + + return createBlock( 'core/heading', { + ...attributes, + content: value, + } ); + } } + onReplace={ onReplace } onRemove={ () => onReplace( [] ) } style={ { textAlign: align } } className={ className } diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 9695563538efda..585aecd6aa7ab8 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -5,9 +5,13 @@ import { __ } from '@wordpress/i18n'; import { createBlock } from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { name } from './'; + export default function ListEdit( { attributes, - insertBlocksAfter, setAttributes, mergeBlocks, onReplace, @@ -26,25 +30,9 @@ export default function ListEdit( { className={ className } placeholder={ __( 'Write list…' ) } onMerge={ mergeBlocks } - unstableOnSplit={ - insertBlocksAfter ? - ( before, after, ...blocks ) => { - if ( ! blocks.length ) { - blocks.push( createBlock( 'core/paragraph' ) ); - } - - if ( after !== '<li></li>' ) { - blocks.push( createBlock( 'core/list', { - ordered, - values: after, - } ) ); - } - - setAttributes( { values: before } ); - insertBlocksAfter( blocks ); - } : - undefined - } + onSplit={ ( value ) => createBlock( name, { ordered, values: value } ) } + __unstableOnSplitMiddle={ () => createBlock( 'core/paragraph' ) } + onReplace={ onReplace } onRemove={ () => onReplace( [] ) } onTagNameChange={ ( tag ) => setAttributes( { ordered: tag === 'ol' } ) } /> diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 7fcf059100a004..021a7369f3422a 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -49,23 +49,7 @@ class ParagraphBlock extends Component { constructor() { super( ...arguments ); - this.onReplace = this.onReplace.bind( this ); this.toggleDropCap = this.toggleDropCap.bind( this ); - this.splitBlock = this.splitBlock.bind( this ); - } - - onReplace( blocks ) { - const { attributes, onReplace } = this.props; - onReplace( blocks.map( ( block, index ) => ( - index === 0 && block.name === name ? - { ...block, - attributes: { - ...attributes, - ...block.attributes, - }, - } : - block - ) ) ); } toggleDropCap() { @@ -77,49 +61,6 @@ class ParagraphBlock extends Component { return checked ? __( 'Showing large initial letter.' ) : __( 'Toggle to show a large initial letter.' ); } - /** - * Split handler for RichText value, namely when content is pasted or the - * user presses the Enter key. - * - * @param {?Array} before Optional before value, to be used as content - * in place of what exists currently for the - * block. If undefined, the block is deleted. - * @param {?Array} after Optional after value, to be appended in a new - * paragraph block to the set of blocks passed - * as spread. - * @param {...WPBlock} blocks Optional blocks inserted between the before - * and after value blocks. - */ - splitBlock( before, after, ...blocks ) { - const { - attributes, - insertBlocksAfter, - setAttributes, - onReplace, - } = this.props; - - if ( after !== null ) { - // Append "After" content as a new paragraph block to the end of - // any other blocks being inserted after the current paragraph. - blocks.push( createBlock( name, { content: after } ) ); - } - - if ( blocks.length && insertBlocksAfter ) { - insertBlocksAfter( blocks ); - } - - const { content } = attributes; - if ( before === null ) { - // If before content is omitted, treat as intent to delete block. - onReplace( [] ); - } else if ( content !== before ) { - // Only update content if it has in-fact changed. In case that user - // has created a new paragraph at end of an existing one, the value - // of before will be strictly equal to the current content. - setAttributes( { content: before } ); - } - } - render() { const { attributes, @@ -239,10 +180,19 @@ class ParagraphBlock extends Component { content: nextContent, } ); } } - unstableOnSplit={ this.splitBlock } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( name ); + } + + return createBlock( name, { + ...attributes, + content: value, + } ); + } } onMerge={ mergeBlocks } - onReplace={ this.props.onReplace && this.onReplace } - onRemove={ onReplace && ( () => onReplace( [] ) ) } + onReplace={ onReplace } + onRemove={ onReplace ? () => onReplace( [] ) : undefined } aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) } placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) } /> diff --git a/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap b/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap index f449ce86a0568d..6046a90ae894f4 100644 --- a/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap @@ -83,3 +83,9 @@ exports[`splitting and merging blocks should split and merge paragraph blocks us <p>BeforeSecond:Second</p> <!-- /wp:paragraph -->" `; + +exports[`splitting and merging blocks should undo split in one go 1`] = ` +"<!-- wp:paragraph --> +<p>12</p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap index 6cffb6ad28a9fc..71bb261abc29a8 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap @@ -1,13 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Separator can be created by prefixing existing content with number signs and a space 1`] = ` +exports[`Heading can be created by prefixing existing content with number signs and a space 1`] = ` "<!-- wp:heading {\\"level\\":4} --> <h4>4</h4> <!-- /wp:heading -->" `; -exports[`Separator can be created by prefixing number sign and a space 1`] = ` +exports[`Heading can be created by prefixing number sign and a space 1`] = ` "<!-- wp:heading {\\"level\\":3} --> <h3>3</h3> <!-- /wp:heading -->" `; + +exports[`Heading should create a paragraph block above when pressing enter at the start 1`] = ` +"<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>a</h2> +<!-- /wp:heading -->" +`; + +exports[`Heading should create a paragraph block below when pressing enter at the end 1`] = ` +"<!-- wp:heading --> +<h2>a</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/blocks/heading.test.js b/packages/e2e-tests/specs/blocks/heading.test.js index a31a77be3adb70..2690ad4690b5b6 100644 --- a/packages/e2e-tests/specs/blocks/heading.test.js +++ b/packages/e2e-tests/specs/blocks/heading.test.js @@ -7,7 +7,7 @@ import { createNewPost, } from '@wordpress/e2e-test-utils'; -describe( 'Separator', () => { +describe( 'Heading', () => { beforeEach( async () => { await createNewPost(); } ); @@ -27,4 +27,21 @@ describe( 'Separator', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should create a paragraph block above when pressing enter at the start', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '## a' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should create a paragraph block below when pressing enter at the end', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '## a' ); + await page.keyboard.press( 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/splitting-merging.test.js b/packages/e2e-tests/specs/splitting-merging.test.js index 8df6e104645ae4..8c3559e3f3645c 100644 --- a/packages/e2e-tests/specs/splitting-merging.test.js +++ b/packages/e2e-tests/specs/splitting-merging.test.js @@ -202,4 +202,14 @@ describe( 'splitting and merging blocks', () => { // And focus is retained: expect( await isInDefaultBlock() ).toBe( true ); } ); + + it( 'should undo split in one go', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '12' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'z' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 72f05906d1e9e2718812e477056fbe0694ffaf78 Mon Sep 17 00:00:00 2001 From: "Emerson \"Duke\" Almeida" <emersonalmeidax@gmail.com> Date: Sun, 19 May 2019 17:54:19 -0300 Subject: [PATCH 129/664] Update link to Lerna project (#15720) --- packages/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/README.md b/packages/README.md index 3edfe78fe7dea8..e4fb5723f3e693 100644 --- a/packages/README.md +++ b/packages/README.md @@ -124,5 +124,5 @@ NPM_CONFIG_OTP=123456 npm run publish:prod Choose the correct version based on `CHANGELOG.md` files, confirm your choices and let Lerna do its magic. -[lerna]: https://lernajs.io/ +[lerna]: https://lerna.js.org/ [npm]: https://www.npmjs.com/ From c5f95a21771b0d6766024fb4af9e7e0a9da12391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Mon, 20 May 2019 08:55:06 +0200 Subject: [PATCH 130/664] Move the "View Posts..." anchor out of the role=toolbar section in the header (#15693) --- .../components/header/fullscreen-mode-close/style.scss | 4 ++-- .../src/components/header/header-toolbar/index.js | 6 ------ packages/edit-post/src/components/header/index.js | 10 +++++++--- packages/edit-post/src/components/header/style.scss | 4 ++++ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss index 26d43a35d74584..7baf306afb125d 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/style.scss @@ -4,11 +4,11 @@ display: none; @include break-medium() { - display: block; + display: flex; border-top: 0; border-bottom: 0; border-left: 0; - margin: -9px 10px -9px -10px; + margin: -9px 10px -8px -10px; padding: 9px 10px; } } diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 56f8dd7d5fdb2e..8ff5e2209594df 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -18,11 +18,6 @@ import { EditorHistoryUndo, } from '@wordpress/editor'; -/** - * Internal dependencies - */ -import FullscreenModeClose from '../fullscreen-mode-close'; - function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isTextModeEnabled } ) { const toolbarAriaLabel = hasFixedToolbar ? /* translators: accessibility text for the editor toolbar when Top Toolbar is on */ @@ -35,7 +30,6 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isText className="edit-post-header-toolbar" aria-label={ toolbarAriaLabel } > - <FullscreenModeClose /> <div> <Inserter disabled={ ! showInserter } position="bottom right" /> <DotTip tipId="core/editor.inserter"> diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 2a16364c7bd2a2..dff5622593603a 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -14,11 +14,12 @@ import { DotTip } from '@wordpress/nux'; /** * Internal dependencies */ -import MoreMenu from './more-menu'; +import FullscreenModeClose from './fullscreen-mode-close'; import HeaderToolbar from './header-toolbar'; +import MoreMenu from './more-menu'; import PinnedPlugins from './pinned-plugins'; -import shortcuts from '../../keyboard-shortcuts'; import PostPublishButtonOrToggle from './post-publish-button-or-toggle'; +import shortcuts from '../../keyboard-shortcuts'; function Header( { closeGeneralSidebar, @@ -38,7 +39,10 @@ function Header( { className="edit-post-header" tabIndex="-1" > - <HeaderToolbar /> + <div className="edit-post-header__toolbar"> + <FullscreenModeClose /> + <HeaderToolbar /> + </div> <div className="edit-post-header__settings"> { ! isPublishSidebarOpened && ( // This button isn't completely hidden by the publish sidebar. diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index 2cebe45d53f7c0..6acb7b5ab8a353 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -59,6 +59,10 @@ @include editor-left(".edit-post-header"); +.edit-post-header__toolbar { + display: flex; +} + .edit-post-header__settings { display: inline-flex; align-items: center; From c667d58bba20ce367fcb1176682d35370469f4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Mon, 20 May 2019 09:31:52 +0200 Subject: [PATCH 131/664] Remove chrome fix (#15665) We only support the last two major Chrome versions. The current version is 74, and a patch for this has landed upstream in chrome 72 https://bugs.chromium.org/p/chromium/issues/detail?id=737691 --- packages/components/src/draggable/index.js | 58 ++++++---------------- 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js index ab660a7de1dd43..f0bc9bbc71a40e 100644 --- a/packages/components/src/draggable/index.js +++ b/packages/components/src/draggable/index.js @@ -14,20 +14,14 @@ const cloneWrapperClass = 'components-draggable__clone'; const cloneHeightTransformationBreakpoint = 700; const clonePadding = 20; -const isChromeUA = ( ) => /Chrome/i.test( window.navigator.userAgent ); -const documentHasIframes = ( ) => [ ...document.getElementById( 'editor' ).querySelectorAll( 'iframe' ) ].length > 0; - class Draggable extends Component { constructor() { super( ...arguments ); this.onDragStart = this.onDragStart.bind( this ); this.onDragOver = this.onDragOver.bind( this ); - this.onDrop = this.onDrop.bind( this ); this.onDragEnd = this.onDragEnd.bind( this ); this.resetDragState = this.resetDragState.bind( this ); - - this.isChromeAndHasIframes = false; } componentWillUnmount() { @@ -36,21 +30,21 @@ class Draggable extends Component { /** * Removes the element clone, resets cursor, and removes drag listener. - * @param {Object} event The non-custom DragEvent. + * + * @param {Object} event The non-custom DragEvent. */ onDragEnd( event ) { const { onDragEnd = noop } = this.props; - if ( event ) { - event.preventDefault(); - } + event.preventDefault(); this.resetDragState(); this.props.setTimeout( onDragEnd ); } - /* + /** * Updates positioning of element clone based on mouse movement during dragging. - * @param {Object} event The non-custom DragEvent. + * + * @param {Object} event The non-custom DragEvent. */ onDragOver( event ) { this.cloneWrapper.style.top = @@ -63,21 +57,17 @@ class Draggable extends Component { this.cursorTop = event.clientY; } - onDrop( ) { - // As per https://html.spec.whatwg.org/multipage/dnd.html#dndevents - // the target node for the dragend is the source node that started the drag operation, - // while drop event's target is the current target element. - this.onDragEnd( null ); - } - /** - * - Clones the current element and spawns clone over original element. - * - Adds a fake temporary drag image to avoid browser defaults. - * - Sets transfer data. - * - Adds dragover listener. - * @param {Object} event The non-custom DragEvent. - * @param {string} elementId The HTML id of the element to be dragged. - * @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic. + * This method does a couple of things: + * + * - Clones the current element and spawns clone over original element. + * - Adds a fake temporary drag image to avoid browser defaults. + * - Sets transfer data. + * - Adds dragover listener. + * + * @param {Object} event The non-custom DragEvent. + * @param {string} elementId The HTML id of the element to be dragged. + * @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic. */ onDragStart( event ) { const { elementId, transferData, onDragStart = noop } = this.props; @@ -140,17 +130,6 @@ class Draggable extends Component { document.body.classList.add( 'is-dragging-components-draggable' ); document.addEventListener( 'dragover', this.onDragOver ); - // Fixes https://bugs.chromium.org/p/chromium/issues/detail?id=737691#c8 - // dragend event won't be dispatched in the chrome browser - // when iframes are affected by the drag operation. So, in that case, - // we use the drop event to wrap up the dragging operation. - // This way the hack is contained to a specific use case and the external API - // still relies mostly on the dragend event. - if ( isChromeUA() && documentHasIframes() ) { - this.isChromeAndHasIframes = true; - document.addEventListener( 'drop', this.onDrop ); - } - this.props.setTimeout( onDragStart ); } @@ -166,11 +145,6 @@ class Draggable extends Component { this.cloneWrapper = null; } - if ( this.isChromeAndHasIframes ) { - this.isChromeAndHasIframes = false; - document.removeEventListener( 'drop', this.onDrop ); - } - // Reset cursor. document.body.classList.remove( 'is-dragging-components-draggable' ); } From 5169edb41e57f5330e0d36c5715cc9a55838c6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Mon, 20 May 2019 09:32:45 +0200 Subject: [PATCH 132/664] Block gallery: add support for image reordering (#14768) --- packages/block-library/src/gallery/edit.js | 33 +++++++++++++++++++ .../block-library/src/gallery/editor.scss | 19 +++++++++-- .../src/gallery/gallery-image.js | 21 ++++++++++-- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index e6b109da9ad0e3..cb1188c010ba1b 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -50,6 +50,9 @@ class GalleryEdit extends Component { this.setLinkTo = this.setLinkTo.bind( this ); this.setColumnsNumber = this.setColumnsNumber.bind( this ); this.toggleImageCrop = this.toggleImageCrop.bind( this ); + this.onMove = this.onMove.bind( this ); + this.onMoveForward = this.onMoveForward.bind( this ); + this.onMoveBackward = this.onMoveBackward.bind( this ); this.onRemoveImage = this.onRemoveImage.bind( this ); this.setImageAttributes = this.setImageAttributes.bind( this ); this.setAttributes = this.setAttributes.bind( this ); @@ -84,6 +87,32 @@ class GalleryEdit extends Component { }; } + onMove( oldIndex, newIndex ) { + const images = [ ...this.props.attributes.images ]; + images.splice( newIndex, 1, this.props.attributes.images[ oldIndex ] ); + images.splice( oldIndex, 1, this.props.attributes.images[ newIndex ] ); + this.setState( { selectedImage: newIndex } ); + this.setAttributes( { images } ); + } + + onMoveForward( oldIndex ) { + return () => { + if ( oldIndex === this.props.attributes.images.length - 1 ) { + return; + } + this.onMove( oldIndex, oldIndex + 1 ); + }; + } + + onMoveBackward( oldIndex ) { + return () => { + if ( oldIndex === 0 ) { + return; + } + this.onMove( oldIndex, oldIndex - 1 ); + }; + } + onRemoveImage( index ) { return () => { const images = filter( this.props.attributes.images, ( img, i ) => index !== i ); @@ -256,7 +285,11 @@ class GalleryEdit extends Component { url={ img.url } alt={ img.alt } id={ img.id } + isFirstItem={ index === 0 } + isLastItem={ ( index + 1 ) === images.length } isSelected={ isSelected && this.state.selectedImage === index } + onMoveBackward={ this.onMoveBackward( index ) } + onMoveForward={ this.onMoveForward( index ) } onRemove={ this.onRemoveImage( index ) } onSelect={ this.onSelectImage( index ) } setAttributes={ ( attrs ) => this.setImageAttributes( index, attrs ) } diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index ffd0f7847a09cd..21623fad95abc9 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -59,6 +59,7 @@ ul.wp-block-gallery { } } + .is-selected .block-library-gallery-item__move-menu, .is-selected .block-library-gallery-item__inline-menu { background-color: theme(primary); @@ -79,11 +80,9 @@ ul.wp-block-gallery { } } +.block-library-gallery-item__move-menu, .block-library-gallery-item__inline-menu { padding: 2px; - position: absolute; - top: -2px; - right: -2px; display: inline-flex; z-index: z-index(".block-library-gallery-item__inline-menu"); @@ -92,6 +91,20 @@ ul.wp-block-gallery { } } +.block-library-gallery-item__inline-menu { + position: absolute; + top: -2px; + right: -2px; +} + +.block-library-gallery-item__move-menu { + position: absolute; + top: -2px; + left: -2px; +} + +.blocks-gallery-item__move-backward, +.blocks-gallery-item__move-forward, .blocks-gallery-item__remove { padding: 0; } diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index 2c91f13df2c6ab..2db0b6d822ae1b 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -86,7 +86,7 @@ class GalleryImage extends Component { } render() { - const { url, alt, id, linkTo, link, isSelected, caption, onRemove, setAttributes, 'aria-label': ariaLabel } = this.props; + const { url, alt, id, linkTo, link, isFirstItem, isLastItem, isSelected, caption, onRemove, onMoveForward, onMoveBackward, setAttributes, 'aria-label': ariaLabel } = this.props; let href; @@ -128,11 +128,28 @@ class GalleryImage extends Component { return ( <figure className={ className }> { href ? <a href={ href }>{ img }</a> : img } + <div className="block-library-gallery-item__move-menu"> + <IconButton + icon="arrow-left" + onClick={ isFirstItem ? undefined : onMoveBackward } + className="blocks-gallery-item__move-backward" + label={ __( 'Move Image Backward' ) } + aria-disabled={ isFirstItem } + disabled={ ! isSelected } + /> + <IconButton + icon="arrow-right" + onClick={ isLastItem ? undefined : onMoveForward } + className="blocks-gallery-item__move-forward" + label={ __( 'Move Image Forward' ) } + aria-disabled={ isLastItem } + disabled={ ! isSelected } + /> + </div> <div className="block-library-gallery-item__inline-menu"> <IconButton icon="no-alt" onClick={ onRemove } - onFocus={ this.onSelectImage } className="blocks-gallery-item__remove" label={ __( 'Remove Image' ) } disabled={ ! isSelected } From 19d1cbfd43daeaf53fc9b3987e2c21eff54aab4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Mon, 20 May 2019 09:58:39 +0200 Subject: [PATCH 133/664] Fix: Allow dropping blocks into blocks with button block appender (#15702) --- .../components/button-block-appender/index.js | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index 1d0ee924bf153b..7fd751165f5b45 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -12,25 +12,29 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import BlockDropZone from '../block-drop-zone'; import Inserter from '../inserter'; function ButtonBlockAppender( { rootClientId, className } ) { return ( - <Inserter - rootClientId={ rootClientId } - renderToggle={ ( { onToggle, disabled, isOpen } ) => ( - <Button - className={ classnames( className, 'block-editor-button-block-appender' ) } - onClick={ onToggle } - aria-expanded={ isOpen } - disabled={ disabled } - > - <span className="screen-reader-text">{ __( 'Add Block' ) }</span> - <Icon icon="insert" /> - </Button> - ) } - isAppender - /> + <> + <BlockDropZone rootClientId={ rootClientId } /> + <Inserter + rootClientId={ rootClientId } + renderToggle={ ( { onToggle, disabled, isOpen } ) => ( + <Button + className={ classnames( className, 'block-editor-button-block-appender' ) } + onClick={ onToggle } + aria-expanded={ isOpen } + disabled={ disabled } + > + <span className="screen-reader-text">{ __( 'Add Block' ) }</span> + <Icon icon="insert" /> + </Button> + ) } + isAppender + /> + </> ); } From 716b80ddb600743f2e3bc50176d4c93cf9e45b05 Mon Sep 17 00:00:00 2001 From: Jorge Bernal <jbernal@gmail.com> Date: Mon, 20 May 2019 11:40:00 +0200 Subject: [PATCH 134/664] Merge mobile v1.5.0 fixes into master (#15703) * Fix rich image caption toolbar and focus (#15685) * RichText: ownProps has precedence over block context. In this way, instances of RichText outside blocks can pass and handle their own custom onFocus and isSelected * RichText: Remove isSelected from block edit context. * Fix lint issues * RichText: Revert removing isSelected from block context (#15698) Removing it generates a regression where inserting a new list block, the block is not selected --- .../src/components/rich-text/index.native.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 9b9af9c49eee86..6d2275d8e783e7 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -821,11 +821,18 @@ RichText.defaultProps = { const RichTextContainer = compose( [ withInstanceId, - withBlockEditContext( ( { clientId, onFocus, isSelected, onCaretVerticalPositionChange }, ownProps ) => { + withBlockEditContext( ( { clientId, onFocus, onCaretVerticalPositionChange, isSelected }, ownProps ) => { + // ownProps.onFocus and isSelected needs precedence over the block edit context + if ( ownProps.isSelected !== undefined ) { + isSelected = ownProps.isSelected; + } + if ( ownProps.onFocus !== undefined ) { + onFocus = ownProps.onFocus; + } return { - clientId, isSelected, - onFocus: onFocus || ownProps.onFocus, + clientId, + onFocus, onCaretVerticalPositionChange, }; } ), From 9d56b8b3f90c51aa9035b242f5ace7c45c7d55df Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 20 May 2019 08:22:13 -0400 Subject: [PATCH 135/664] Builld Tooling: Skip Chromium download in Travis by default (#15712) * Builld Tooling: Skip Chromium download in Travis by default * Build Tooling: Set skip value to empty string Truthy consideration: https://github.com/GoogleChrome/puppeteer/blob/c6c32491ec0a0209ab651c098e955d15c9305ab9/install.js#L23 Boolean( "false" ) === true --- .travis.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index dc51393e18fed0..02b57e11c88e60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ branches: before_install: - nvm install +env: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true + jobs: include: - name: Lint @@ -88,7 +90,7 @@ jobs: - ./bin/run-wp-unit-tests.sh - name: E2E tests (Admin with plugins) (1/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true + env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -97,7 +99,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true + env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -106,7 +108,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (3/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true + env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -115,7 +117,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (4/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true + env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -124,7 +126,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author + env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -133,7 +135,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author + env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -142,7 +144,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (3/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author + env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: @@ -151,7 +153,7 @@ jobs: - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (4/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author + env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-local-env.sh script: From d2a164beba3d5430dbac9d9b92b5385137720a21 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Mon, 20 May 2019 15:50:41 +0300 Subject: [PATCH 136/664] [Mobile] native mobile release v1.5.1 (#15734) * Fix rich image caption toolbar and focus (#15685) * RichText: ownProps has precedence over block context. In this way, instances of RichText outside blocks can pass and handle their own custom onFocus and isSelected * RichText: Remove isSelected from block edit context. * Fix lint issues * RichText: Revert removing isSelected from block context (#15698) Removing it generates a regression where inserting a new list block, the block is not selected * Don't have AztecAndroid set caret if out-of-bounds * Fix lint issues --- .../src/components/rich-text/index.native.js | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 6d2275d8e783e7..c9cb56397dcae2 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -737,16 +737,22 @@ export class RichText extends Component { let selection = null; if ( this.needsSelectionUpdate ) { this.needsSelectionUpdate = false; - - // Aztec performs some html text cleanup while parsing it so, its internal representation gets out-of-sync with the - // representation of the format-lib on the RN side. We need to avoid trying to set the caret position because it may - // be outside the text bounds and crash Aztec, at least on Android. - if ( ! this.isIOS && this.willTrimSpaces( html ) ) { - // the html will get trimmed by the cleaning up functions in Aztec and caret position will get out-of-sync. - // So, skip forcing it, let Aztec just do its best and just log the fact. - console.warn( 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' ); - } else { - selection = { start: this.props.selectionStart, end: this.props.selectionEnd }; + selection = { start: this.props.selectionStart, end: this.props.selectionEnd }; + + // On AztecAndroid, setting the caret to an out-of-bounds position will crash the editor so, let's check for some cases. + if ( ! this.isIOS ) { + // Aztec performs some html text cleanup while parsing it so, its internal representation gets out-of-sync with the + // representation of the format-lib on the RN side. We need to avoid trying to set the caret position because it may + // be outside the text bounds and crash Aztec, at least on Android. + if ( this.willTrimSpaces( html ) ) { + // the html will get trimmed by the cleaning up functions in Aztec and caret position will get out-of-sync. + // So, skip forcing it, let Aztec just do its best and just log the fact. + console.warn( 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' ); + selection = null; + } else if ( this.props.selectionStart > record.text.length || this.props.selectionEnd > record.text.length ) { + console.warn( 'Oops, selection will land outside the text, skipping setting it...' ); + selection = null; + } } } From 83a08155b44a60f9894a5177a23bb084974e6b48 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 20 May 2019 10:10:17 -0400 Subject: [PATCH 137/664] Library Export Default Webpack Plugin: Rewrite as CommonJS (#15710) --- package-lock.json | 1 - packages/library-export-default-webpack-plugin/CHANGELOG.md | 5 +++++ .../library-export-default-webpack-plugin/{src => }/index.js | 0 packages/library-export-default-webpack-plugin/package.json | 5 +---- 4 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 packages/library-export-default-webpack-plugin/CHANGELOG.md rename packages/library-export-default-webpack-plugin/{src => }/index.js (100%) diff --git a/package-lock.json b/package-lock.json index afe4c3a99dc7d8..b8f5e28385deaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3378,7 +3378,6 @@ "version": "file:packages/library-export-default-webpack-plugin", "dev": true, "requires": { - "@babel/runtime": "^7.4.4", "lodash": "^4.17.11", "webpack-sources": "^1.1.0" } diff --git a/packages/library-export-default-webpack-plugin/CHANGELOG.md b/packages/library-export-default-webpack-plugin/CHANGELOG.md new file mode 100644 index 00000000000000..66b9261df4a8bc --- /dev/null +++ b/packages/library-export-default-webpack-plugin/CHANGELOG.md @@ -0,0 +1,5 @@ +## Master + +### Internal + +- The module is no longer transpiled as part of a build process, and instead exposes its source directly as the `main` file. diff --git a/packages/library-export-default-webpack-plugin/src/index.js b/packages/library-export-default-webpack-plugin/index.js similarity index 100% rename from packages/library-export-default-webpack-plugin/src/index.js rename to packages/library-export-default-webpack-plugin/index.js diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index 91353134cbedce..66537bc38c1b76 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -22,12 +22,9 @@ "node": ">=8" }, "files": [ - "build", - "build-module" + "index.js" ], - "main": "build/index.js", "dependencies": { - "@babel/runtime": "^7.4.4", "lodash": "^4.17.11", "webpack-sources": "^1.1.0" }, From 6cb4c4da146fc1ae64a2f72ecd27b9af8e7456f0 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Mon, 20 May 2019 17:56:43 +0300 Subject: [PATCH 138/664] [Mobile]Add unit-tests for media related components (#15119) * Pass media type to bridge * Add video block * Add media upload ui * Add video block * Extract media upload * Fix lint issues * Integrate RN video * Integrate RN video * Fix lint issues * Rename MediaUploadUI * Separate Android and iOS video player configs * Fix lint issues * Revert unwanted auto linter fix * Update order of video block * Fix typo * Enhance placeholder * Add retry action for video block * Remove unused state property * Add some exports for unit-testing * Re-add passing payload to `onMediaUploadStateReset` --- .../src/components/media-upload/index.native.js | 15 +++++++++------ .../src/image/media-upload-progress.native.js | 16 ++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index d34b3061403ef3..9efed29dc1e821 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -17,18 +17,21 @@ import { Picker } from '@wordpress/block-editor'; export const MEDIA_TYPE_IMAGE = 'image'; export const MEDIA_TYPE_VIDEO = 'video'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA = 'take_media'; -const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; +export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; +export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA = 'take_media'; +export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; -class MediaUpload extends React.Component { +export const OPTION_TAKE_VIDEO = __( 'Take a Video' ); +export const OPTION_TAKE_PHOTO = __( 'Take a Photo' ); + +export class MediaUpload extends React.Component { getTakeMediaLabel() { const { mediaType } = this.props; if ( mediaType === MEDIA_TYPE_IMAGE ) { - return __( 'Take a Photo' ); + return OPTION_TAKE_PHOTO; } else if ( mediaType === MEDIA_TYPE_VIDEO ) { - return __( 'Take a Video' ); + return OPTION_TAKE_VIDEO; } } diff --git a/packages/block-library/src/image/media-upload-progress.native.js b/packages/block-library/src/image/media-upload-progress.native.js index 6abf295339cc42..8e22d5ed1d2bf2 100644 --- a/packages/block-library/src/image/media-upload-progress.native.js +++ b/packages/block-library/src/image/media-upload-progress.native.js @@ -20,12 +20,12 @@ import { __ } from '@wordpress/i18n'; */ import ImageSize from './image-size'; -const MEDIA_UPLOAD_STATE_UPLOADING = 1; -const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; -const MEDIA_UPLOAD_STATE_FAILED = 3; -const MEDIA_UPLOAD_STATE_RESET = 4; +export const MEDIA_UPLOAD_STATE_UPLOADING = 1; +export const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; +export const MEDIA_UPLOAD_STATE_FAILED = 3; +export const MEDIA_UPLOAD_STATE_RESET = 4; -class MediaUploadProgress extends React.Component { +export class MediaUploadProgress extends React.Component { constructor( props ) { super( props ); @@ -64,7 +64,7 @@ class MediaUploadProgress extends React.Component { this.finishMediaUploadWithFailure( payload ); break; case MEDIA_UPLOAD_STATE_RESET: - this.mediaUploadStateReset(); + this.mediaUploadStateReset( payload ); break; } } @@ -90,10 +90,10 @@ class MediaUploadProgress extends React.Component { } } - mediaUploadStateReset( ) { + mediaUploadStateReset( payload ) { this.setState( { isUploadInProgress: false, isUploadFailed: false } ); if ( this.props.onMediaUploadStateReset ) { - this.props.onMediaUploadStateReset(); + this.props.onMediaUploadStateReset( payload ); } } From 3e14a307f8d6669c012886b843af432c625fe2e5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 20 May 2019 11:27:17 -0400 Subject: [PATCH 139/664] Custom Template Path Webpack Plugin: Rewrite as CommonJS (#15709) * Custom Template Path Webpack Plugin: Rewrite as CommonJS * Custom Template Path Webpack Plugin: CHANGELOG Unreleased -> Master --- package-lock.json | 1 - packages/custom-templated-path-webpack-plugin/CHANGELOG.md | 6 +++++- .../custom-templated-path-webpack-plugin/{src => }/index.js | 0 packages/custom-templated-path-webpack-plugin/package.json | 6 +----- 4 files changed, 6 insertions(+), 7 deletions(-) rename packages/custom-templated-path-webpack-plugin/{src => }/index.js (100%) diff --git a/package-lock.json b/package-lock.json index b8f5e28385deaf..787fab54e81bee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3087,7 +3087,6 @@ "version": "file:packages/custom-templated-path-webpack-plugin", "dev": true, "requires": { - "@babel/runtime": "^7.4.4", "escape-string-regexp": "^1.0.5" } }, diff --git a/packages/custom-templated-path-webpack-plugin/CHANGELOG.md b/packages/custom-templated-path-webpack-plugin/CHANGELOG.md index 3d97245e0fca25..7b34a52f1071ad 100644 --- a/packages/custom-templated-path-webpack-plugin/CHANGELOG.md +++ b/packages/custom-templated-path-webpack-plugin/CHANGELOG.md @@ -1,9 +1,13 @@ -## Unreleased +## Master ### Bug Fixes - Resolved an issue where only the value of the first matched tag would be produced. +### Internal + +- The module is no longer transpiled as part of a build process, and instead exposes its source directly as the `main` file. + ## 1.1.0 (2018-07-12) ### Internal diff --git a/packages/custom-templated-path-webpack-plugin/src/index.js b/packages/custom-templated-path-webpack-plugin/index.js similarity index 100% rename from packages/custom-templated-path-webpack-plugin/src/index.js rename to packages/custom-templated-path-webpack-plugin/index.js diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index adcb3375229112..edadb063e05182 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -22,13 +22,9 @@ "node": ">=8" }, "files": [ - "build", - "build-module" + "index.js" ], - "main": "build/index.js", - "module": "build-module/index.js", "dependencies": { - "@babel/runtime": "^7.4.4", "escape-string-regexp": "^1.0.5" }, "peerDependencies": { From 62623f523cc2a1b64f487e0c2e4fd02baf57628f Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Mon, 20 May 2019 09:12:56 -0700 Subject: [PATCH 140/664] Set max-height on Shortcode block textarea (#15365) * Fixes #15032. Allows field to expand with text. * Added a max-height on this. --- packages/block-library/src/shortcode/editor.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/block-library/src/shortcode/editor.scss b/packages/block-library/src/shortcode/editor.scss index cbb22c2ba15080..3ad5cf89400248 100644 --- a/packages/block-library/src/shortcode/editor.scss +++ b/packages/block-library/src/shortcode/editor.scss @@ -16,10 +16,8 @@ } .block-editor-plain-text { - flex-grow: 1; - line-height: 1; - max-height: 30px; width: 80%; + max-height: 250px; } .dashicon { From d1cc70a377151f174a959788485599fad09249bc Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 20 May 2019 11:46:02 -0700 Subject: [PATCH 141/664] Update links to new handbook (#15714) * Update link to Block Editor handbook * Update Documentation link in plugin to handbook * Update links to new handbook location * Switch to relative links --- README.md | 2 +- gutenberg.php | 2 +- packages/block-editor/src/components/inner-blocks/README.md | 4 ++-- packages/blocks/README.md | 2 +- packages/blocks/src/api/registration.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9072e34081d6b5..494b9ba91e7bef 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,6 @@ Please see <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUT - <a href="https://developer.wordpress.org/block-editor/contributors/design/">Design Principles and block design best practices</a> - <a href="https://github.com/Automattic/wp-post-grammar">WP Post Grammar Parser</a> - <a href="https://make.wordpress.org/core/tag/gutenberg/">Development updates on make.wordpress.org</a> -- <a href="https://wordpress.org/gutenberg/handbook/">Documentation: Creating Blocks, Reference, and Guidelines</a> +- <a href="https://developer.wordpress.org/block-editor/">Documentation: Creating Blocks, Reference, and Guidelines</a> <br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> diff --git a/gutenberg.php b/gutenberg.php index a03313638fe7b6..7b121cc8970fae 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -62,7 +62,7 @@ function gutenberg_menu() { $submenu['gutenberg'][] = array( __( 'Documentation', 'gutenberg' ), 'edit_posts', - 'https://wordpress.org/gutenberg/handbook/', + 'https://developer.wordpress.org/block-editor/', ); } } diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index 331e15c4c4f84d..ef8b015b713b50 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -71,7 +71,7 @@ The previous code block restricts all blocks, so only child blocks explicitly re * **Type:** `Array<Array<Object>>` The template is defined as a list of block items. Such blocks can have predefined attributes, placeholder, content, etc. Block templates allow specifying a default initial state for an InnerBlocks area. -More information about templates can be found in [template docs](https://wordpress.org/gutenberg/handbook/templates/). +More information about templates can be found in [template docs](/docs/developers/block-api/block-templates.md). ```jsx const TEMPLATE = [ [ 'core/columns', {}, [ @@ -100,7 +100,7 @@ If false the selection should not be updated when child blocks specified in the ### `templateLock` * **Type:** `String|Boolean` -Template locking of `InnerBlocks` is similar to [Custom Post Type templates locking](https://wordpress.org/gutenberg/handbook/templates/#locking). +Template locking of `InnerBlocks` is similar to [Custom Post Type templates locking](/docs/developers/block-api/block-templates.md#locking). Template locking allows locking the `InnerBlocks` area for the current template. *Options:* diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 03442bbe5e7297..9bc199a54452f2 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -40,7 +40,7 @@ The `enqueue_block_editor_assets` hook is only run in the Gutenberg editor conte The following sections will describe what you'll need to include in `block.js` to describe the behavior of your custom block. -Note that all JavaScript code samples in this document are enclosed in a function that is evaluated immediately afterwards. We recommend using either ES6 modules [as used in this project](https://wordpress.org/gutenberg/handbook/reference/coding-guidelines/#imports) (documentation on setting up a plugin with Webpack + ES6 modules coming soon) or these ["immediately-invoked function expressions"](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression) as used in this document. Both of these methods ensure that your plugin's variables will not pollute the global `window` object, which could cause incompatibilities with WordPress core or with other plugins. +Note that all JavaScript code samples in this document are enclosed in a function that is evaluated immediately afterwards. We recommend using either ES6 modules [as used in this project](/docs/contributors/develop/coding-guidelines.md#imports) (documentation on setting up a plugin with Webpack + ES6 modules coming soon) or these ["immediately-invoked function expressions"](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression) as used in this document. Both of these methods ensure that your plugin's variables will not pollute the global `window` object, which could cause incompatibilities with WordPress core or with other plugins. ## Example diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 61b1d8f2c3c442..8f1155fbce3400 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -150,7 +150,7 @@ export function registerBlockType( name, settings ) { if ( ! isValidIcon( settings.icon.src ) ) { console.error( 'The icon passed is invalid. ' + - 'The icon should be a string, an element, a function, or an object following the specifications documented in https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-registration/#icon-optional' + 'The icon should be a string, an element, a function, or an object following the specifications documented in https://developer.wordpress.org/block-editor/developers/block-api/block-registration/#icon-optional' ); return; } From 7320d0dc6e55fafc1e130ffde5f125aa460abd66 Mon Sep 17 00:00:00 2001 From: Miguel Vieira <miguelfeliciovieira@gmail.com> Date: Mon, 20 May 2019 22:23:45 +0100 Subject: [PATCH 142/664] Fix issue #15652 (#15736) * Fix issue #15652 * Small enhancement to deal with empty inputs. * Added changelog entry. --- packages/components/CHANGELOG.md | 1 + packages/components/src/date-time/time.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index abde63ef28198d..d73cd8dbfb85ae 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -7,6 +7,7 @@ ### Bug Fixes - Fixed display of reset button when using RangeControl `allowReset` prop. +- Fixed minutes field of `DateTimePicker` missed '0' before single digit values. ## 7.3.0 (2019-04-16) diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 3d0092df6155fe..55937b10170319 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -189,7 +189,10 @@ class TimePicker extends Component { } onChangeMinutes( event ) { - this.setState( { minutes: event.target.value } ); + const minutes = event.target.value; + this.setState( { + minutes: ( minutes === '' ) ? '' : ( '0' + minutes ).slice( -2 ), + } ); } renderMonth( month ) { From c38327df97fc250d9af5d340f2f68a5db7c76eaf Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 21 May 2019 09:08:15 +0100 Subject: [PATCH 143/664] Refactor fill component to avoid relying on componentWillUpdate and use React Hooks instead (#15541) --- packages/components/src/slot-fill/context.js | 3 +- packages/components/src/slot-fill/fill.js | 82 +++++++++----------- packages/components/src/slot-fill/slot.js | 2 +- 3 files changed, 39 insertions(+), 48 deletions(-) diff --git a/packages/components/src/slot-fill/context.js b/packages/components/src/slot-fill/context.js index a39ac94013e0af..7ee0d9806c4003 100644 --- a/packages/components/src/slot-fill/context.js +++ b/packages/components/src/slot-fill/context.js @@ -97,13 +97,12 @@ class SlotFillProvider extends Component { if ( this.slots[ name ] !== slotInstance ) { return []; } - return sortBy( this.fills[ name ], 'occurrence' ); } resetFillOccurrence( name ) { forEach( this.fills[ name ], ( instance ) => { - instance.resetOccurrence(); + instance.occurrence = undefined; } ); } diff --git a/packages/components/src/slot-fill/fill.js b/packages/components/src/slot-fill/fill.js index 7c2b3fb0ad5a28..a0c2876643f048 100644 --- a/packages/components/src/slot-fill/fill.js +++ b/packages/components/src/slot-fill/fill.js @@ -6,7 +6,7 @@ import { isFunction } from 'lodash'; /** * WordPress dependencies */ -import { Component, createPortal } from '@wordpress/element'; +import { createPortal, useLayoutEffect, useRef, useState } from '@wordpress/element'; /** * Internal dependencies @@ -15,64 +15,56 @@ import { Consumer } from './context'; let occurrences = 0; -class FillComponent extends Component { - constructor() { - super( ...arguments ); - this.occurrence = ++occurrences; - } +function FillComponent( { name, getSlot, children, registerFill, unregisterFill } ) { + // Random state used to rerender the component if needed, ideally we don't need this + const [ , updateRerenderState ] = useState( {} ); + const rerender = () => updateRerenderState( {} ); - componentDidMount() { - const { registerFill } = this.props; + const ref = useRef( { + name, + children, + } ); - registerFill( this.props.name, this ); + if ( ! ref.current.occurrence ) { + ref.current.occurrence = ++occurrences; } - componentWillUpdate() { - if ( ! this.occurrence ) { - this.occurrence = ++occurrences; - } - const { getSlot } = this.props; - const slot = getSlot( this.props.name ); + useLayoutEffect( () => { + ref.current.forceUpdate = rerender; + registerFill( name, ref.current ); + return () => unregisterFill( name, ref.current ); + }, [] ); + + useLayoutEffect( () => { + ref.current.children = children; + const slot = getSlot( name ); if ( slot && ! slot.props.bubblesVirtually ) { slot.forceUpdate(); } - } - - componentWillUnmount() { - const { unregisterFill } = this.props; + }, [ children ] ); - unregisterFill( this.props.name, this ); - } + useLayoutEffect( () => { + if ( name === ref.current.name ) { + // ignore initial effect + return; + } + unregisterFill( ref.current.name, ref.current ); + ref.current.name = name; + registerFill( name, ref.current ); + }, [ name ] ); - componentDidUpdate( prevProps ) { - const { name, unregisterFill, registerFill } = this.props; + const slot = getSlot( name ); - if ( prevProps.name !== name ) { - unregisterFill( prevProps.name, this ); - registerFill( name, this ); - } + if ( ! slot || ! slot.node || ! slot.props.bubblesVirtually ) { + return null; } - resetOccurrence() { - this.occurrence = null; + // If a function is passed as a child, provide it with the fillProps. + if ( isFunction( children ) ) { + children = children( slot.props.fillProps ); } - render() { - const { name, getSlot } = this.props; - let { children } = this.props; - const slot = getSlot( name ); - - if ( ! slot || ! slot.node || ! slot.props.bubblesVirtually ) { - return null; - } - - // If a function is passed as a child, provide it with the fillProps. - if ( isFunction( children ) ) { - children = children( slot.props.fillProps ); - } - - return createPortal( children, slot.node ); - } + return createPortal( children, slot.node ); } const Fill = ( props ) => ( diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 68f11b1395b83a..6de69f74f865a3 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -64,7 +64,7 @@ class SlotComponent extends Component { const fills = map( getFills( name, this ), ( fill ) => { const fillKey = fill.occurrence; - const fillChildren = isFunction( fill.props.children ) ? fill.props.children( fillProps ) : fill.props.children; + const fillChildren = isFunction( fill.children ) ? fill.children( fillProps ) : fill.children; return Children.map( fillChildren, ( child, childIndex ) => { if ( ! child || isString( child ) ) { From 0773762250afaec366e58d4e9e19c52efa92519a Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Tue, 21 May 2019 04:12:04 -0400 Subject: [PATCH 144/664] [Mobile] Video block ui/ux enhancements (#15551) * Pass media type to bridge * Add video block * Add media upload ui * Add video block * Extract media upload * Fix lint issues * Integrate RN video * Integrate RN video * Fix lint issues * Rename MediaUploadUI * Separate Android and iOS video player configs * Fix lint issues * Revert unwanted auto linter fix * Update order of video block * Fix typo * Enhance placeholder * Add retry action for video block * Remove unused state property * Fixed problem with cancel/retry uploading of video * Set proper bottom sheet icons for video block * Make sure that icon block has latest value of isUploadingInProgress * Remove unnecessary code for video copy/paste handling We are not supporting video copy paste, that code was copy/paste from image block * Removing poster support poster support isn't present on wp.com yet and react-native-video has an ugly bug about posters so we'll need to solve that first, after that we'll re-add this. * Implemented latest ui-ux on video block * Aligned image block with video block * Fixed logic on iOS when selecting video block first time, avoid to play a video * Fixed crash when opening the post which previously had cancelled image/video upload * Set black background for uploaded video * Fixed retry icon color on image upload failed state * Avoid loading video before it's uploaded Fixes failing tests * Fixed failed tests * Set image background as black color with 50% alpha when image is in failed state * Make sure that image/video block is selected when user taps on it * Fixed issue with missing placeholder while image is uploading * Reuse Icon function in image/video block and block picker * Fixes issue with retry state background * Fix icon crash and inject props to SVG icons * Update video upload icon color and size, also caption padding * Update Icon to accept a style prop * Revert "Update Icon to accept a style prop" This reverts commit a5bf475e2f7d3bbb0bd0cd2a86c32d692803dfd7. * Fix retry icon size --- .../media-placeholder/index.native.js | 23 +++++------ .../media-placeholder/styles.native.scss | 12 +++++- .../block-library/src/image/edit.native.js | 38 ++++++++++++++---- .../block-library/src/image/icon-retry.js | 10 +++++ packages/block-library/src/image/icon.js | 6 ++- .../src/image/media-upload-progress.native.js | 6 +-- .../src/image/styles.native.scss | 22 +++++++++- .../block-library/src/video/edit.native.js | 40 ++++++++++++++----- .../block-library/src/video/icon-retry.js | 10 +++++ packages/block-library/src/video/icon.js | 6 ++- .../block-library/src/video/style.native.scss | 37 ++++++++++++++++- .../src/video/video-player.android.js | 6 ++- .../src/video/video-player.ios.js | 4 +- .../components/src/spinner/index.native.js | 9 +++-- .../components/src/spinner/style.native.scss | 4 ++ 15 files changed, 184 insertions(+), 49 deletions(-) create mode 100644 packages/block-library/src/image/icon-retry.js create mode 100644 packages/block-library/src/video/icon-retry.js create mode 100644 packages/components/src/spinner/style.native.scss diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index db98ff4f206753..a19a28588200c9 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -7,7 +7,6 @@ import { View, Text, TouchableWithoutFeedback } from 'react-native'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { Dashicon } from '@wordpress/components'; import { MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/block-editor'; /** @@ -31,21 +30,12 @@ function MediaPlaceholder( props ) { } } - let placeholderIcon = icon; - if ( placeholderIcon === undefined ) { - if ( isImage ) { - placeholderIcon = 'format-image'; - } else if ( isVideo ) { - placeholderIcon = 'format-video'; - } - } - let instructions = labels.instructions; if ( instructions === undefined ) { if ( isImage ) { - instructions = __( 'CHOOSE IMAGE' ); + instructions = __( 'ADD IMAGE' ); } else if ( isVideo ) { - instructions = __( 'CHOOSE VIDEO' ); + instructions = __( 'ADD VIDEO' ); } } @@ -70,11 +60,16 @@ function MediaPlaceholder( props ) { ) } accessibilityRole={ 'button' } accessibilityHint={ accessibilityHint } - onPress={ open } + onPress={ ( event ) => { + props.onFocus( event ); + open(); + } } > <View style={ styles.emptyStateContainer }> { getMediaOptions() } - <Dashicon icon={ placeholderIcon } /> + <View style={ styles.modalIcon }> + { icon } + </View> <Text style={ styles.emptyStateTitle }> { placeholderTitle } </Text> diff --git a/packages/block-editor/src/components/media-placeholder/styles.native.scss b/packages/block-editor/src/components/media-placeholder/styles.native.scss index d3001491eb73aa..f76be4b8f36a42 100644 --- a/packages/block-editor/src/components/media-placeholder/styles.native.scss +++ b/packages/block-editor/src/components/media-placeholder/styles.native.scss @@ -4,7 +4,7 @@ flex-direction: column; align-items: center; justify-content: center; - background-color: #e9eff3; + background-color: $gray-light; padding-left: 12; padding-right: 12; padding-top: 12; @@ -21,7 +21,15 @@ .emptyStateDescription { text-align: center; - color: #0087be; + color: $blue-wordpress; font-size: 14; font-weight: 500; } + +.modalIcon { + width: 24px; + height: 24px; + justify-content: center; + align-items: center; + fill: $gray-dark; +} diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index cf1872a0a2fe48..b2ca7452b8c8b8 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,7 +2,7 @@ * External dependencies */ import React from 'react'; -import { View, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; +import { View, ImageBackground, Text, TouchableWithoutFeedback, Dimensions } from 'react-native'; import { requestMediaImport, mediaUploadSync, @@ -17,7 +17,6 @@ import { isEmpty } from 'lodash'; import { Toolbar, ToolbarButton, - Dashicon, } from '@wordpress/components'; import { MediaPlaceholder, @@ -37,10 +36,15 @@ import { doAction, hasAction } from '@wordpress/hooks'; */ import styles from './styles.scss'; import MediaUploadProgress from './media-upload-progress'; +import SvgIcon from './icon'; +import SvgIconRetry from './icon-retry'; const LINK_DESTINATION_CUSTOM = 'custom'; const LINK_DESTINATION_NONE = 'none'; +// Default Image ratio 4:3 +const IMAGE_ASPECT_RATIO = 4 / 3; + class ImageEdit extends React.Component { constructor( props ) { super( props ); @@ -187,6 +191,14 @@ class ImageEdit extends React.Component { } } + getIcon( isRetryIcon ) { + if ( isRetryIcon ) { + return <SvgIconRetry fill={ styles.iconRetry.fill } />; + } + + return <SvgIcon fill={ styles.icon.fill } />; + } + render() { const { attributes, isSelected, setAttributes } = this.props; const { url, caption, height, width, alt, href, id } = attributes; @@ -256,11 +268,15 @@ class ImageEdit extends React.Component { <MediaPlaceholder mediaType={ MEDIA_TYPE_IMAGE } onSelectURL={ this.onSelectMediaUploadOption } + icon={ this.getIcon( false ) } + onFocus={ this.props.onFocus } /> </View> ); } + const imageContainerHeight = Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO; + return ( <TouchableWithoutFeedback accessible={ ! isSelected } @@ -298,12 +314,20 @@ class ImageEdit extends React.Component { onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } onMediaUploadStateReset={ this.mediaUploadStateReset } - renderContent={ ( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryIconName, retryMessage } ) => { + renderContent={ ( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryMessage } ) => { const opacity = isUploadInProgress ? 0.3 : 1; + const icon = this.getIcon( isUploadFailed ); + + const iconContainer = ( + <View style={ styles.modalIcon }> + { icon } + </View> + ); + return ( <View style={ { flex: 1 } } > - { ! imageWidthWithinContainer && <View style={ styles.imageContainer } > - <Dashicon icon={ 'format-image' } size={ 300 } /> + { ! imageWidthWithinContainer && <View style={ [ styles.imageContainer, { height: imageContainerHeight } ] } > + { this.getIcon( false ) } </View> } <ImageBackground style={ { width: finalWidth, height: finalHeight, opacity } } @@ -314,8 +338,8 @@ class ImageEdit extends React.Component { accessibilityLabel={ alt } > { isUploadFailed && - <View style={ styles.imageContainer } > - <Dashicon icon={ retryIconName } ariaPressed={ 'dashicon-active' } /> + <View style={ [ styles.imageContainer, { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)' } ] } > + { iconContainer } <Text style={ styles.uploadFailedText }>{ retryMessage }</Text> </View> } diff --git a/packages/block-library/src/image/icon-retry.js b/packages/block-library/src/image/icon-retry.js new file mode 100644 index 00000000000000..66baaa93dcfb38 --- /dev/null +++ b/packages/block-library/src/image/icon-retry.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +function svg( props ) { + return <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" { ...props }><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill={ 'none' } /></SVG>; +} + +export default svg; diff --git a/packages/block-library/src/image/icon.js b/packages/block-library/src/image/icon.js index b029bab8fbe98a..ad8a11857013a0 100644 --- a/packages/block-library/src/image/icon.js +++ b/packages/block-library/src/image/icon.js @@ -3,4 +3,8 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; +function svg( props ) { + return <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" { ...props }><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; +} + +export default svg; diff --git a/packages/block-library/src/image/media-upload-progress.native.js b/packages/block-library/src/image/media-upload-progress.native.js index 8e22d5ed1d2bf2..18856fb596606f 100644 --- a/packages/block-library/src/image/media-upload-progress.native.js +++ b/packages/block-library/src/image/media-upload-progress.native.js @@ -19,6 +19,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import ImageSize from './image-size'; +import styles from './styles.scss'; export const MEDIA_UPLOAD_STATE_UPLOADING = 1; export const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; @@ -118,11 +119,10 @@ export class MediaUploadProgress extends React.Component { const { isUploadInProgress, isUploadFailed } = this.state; const showSpinner = this.state.isUploadInProgress; const progress = this.state.progress * 100; - const retryIconName = 'image-rotate'; const retryMessage = __( 'Failed to insert media.\nPlease tap for options.' ); return ( - <View style={ { flex: 1 } }> + <View style={ styles.mediaUploadProgress }> { showSpinner && <Spinner progress={ progress } /> } { coverUrl && <ImageSize src={ coverUrl } > @@ -147,7 +147,6 @@ export class MediaUploadProgress extends React.Component { finalWidth, finalHeight, imageWidthWithinContainer, - retryIconName, retryMessage, } ) ); } } @@ -156,7 +155,6 @@ export class MediaUploadProgress extends React.Component { { ! coverUrl && this.props.renderContent( { isUploadInProgress, isUploadFailed, - retryIconName, retryMessage, } ) } </View> diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index fc4e82aded534b..4250ff170e328b 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -1,9 +1,9 @@ // @format .imageContainer { - flex: 1; justify-content: center; align-items: center; + background-color: $gray-lighten-30; } .uploadFailedText { @@ -21,3 +21,23 @@ .clearSettingsButton { color: $alert-red; } + +.mediaUploadProgress { + flex: 1; + background-color: $gray-lighten-30; +} + +.modalIcon { + width: 80px; + height: 80px; + justify-content: center; + align-items: center; +} + +.iconRetry { + fill: #fff; +} + +.icon { + fill: $gray-dark; +} diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index cb1309b24f9890..2d295ead99f7d8 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -19,7 +19,6 @@ import { import { Toolbar, ToolbarButton, - Dashicon, } from '@wordpress/components'; import { MediaPlaceholder, @@ -38,6 +37,8 @@ import { doAction, hasAction } from '@wordpress/hooks'; */ import MediaUploadProgress from '../image/media-upload-progress'; import style from './style.scss'; +import SvgIcon from './icon'; +import SvgIconRetry from './icon-retry'; const VIDEO_ASPECT_RATIO = 1.7; @@ -127,6 +128,14 @@ class VideoEdit extends React.Component { } } + getIcon( isRetryIcon, isUploadInProgress ) { + if ( isRetryIcon ) { + return <SvgIconRetry fill={ style.icon.fill } />; + } + + return <SvgIcon fill={ isUploadInProgress ? style.iconUploading.fill : style.icon.fill } />; + } + render() { const { attributes, isSelected, setAttributes } = this.props; const { caption, id, src } = attributes; @@ -156,6 +165,8 @@ class VideoEdit extends React.Component { <MediaPlaceholder mediaType={ MEDIA_TYPE_VIDEO } onSelectURL={ this.onSelectMediaUploadOption } + icon={ this.getIcon( false ) } + onFocus={ this.props.onFocus } /> </View> ); @@ -180,29 +191,38 @@ class VideoEdit extends React.Component { onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } onUpdateMediaProgress={ this.updateMediaProgress } onMediaUploadStateReset={ this.mediaUploadStateReset } - renderContent={ ( { isUploadInProgress, isUploadFailed, retryIconName, retryMessage } ) => { - const opacity = ( isUploadInProgress || isUploadFailed ) ? 0.3 : 1; + renderContent={ ( { isUploadInProgress, isUploadFailed, retryMessage } ) => { const showVideo = src && ! isUploadInProgress && ! isUploadFailed; - const iconName = isUploadFailed ? retryIconName : 'format-video'; + const icon = this.getIcon( isUploadFailed, isUploadInProgress ); + const styleIconContainer = isUploadFailed ? style.modalIconRetry : style.modalIcon; + + const iconContainer = ( + <View style={ styleIconContainer }> + { icon } + </View> + ); const videoStyle = { height: videoContainerHeight, ...style.video, }; + const containerStyle = showVideo && isSelected ? style.containerFocused : style.container; + return ( - <View onLayout={ this.onVideoContanerLayout } style={ { flex: 1 } }> - { showVideo && + <View onLayout={ this.onVideoContanerLayout } style={ containerStyle }> + { showVideo && isURL( src ) && <Video + isSelected={ isSelected } + style={ [ videoStyle, { backgroundColor: 'black' } ] } source={ { uri: src } } - style={ videoStyle } paused={ true } muted={ true } /> } { ! showVideo && - <View style={ { ...videoStyle, ...style.placeholder, opacity } }> - { videoContainerHeight > 0 && <Dashicon icon={ iconName } size={ 80 } style={ style.placeholderIcon } /> } + <View style={ { ...videoStyle, ...style.placeholder } }> + { videoContainerHeight > 0 && iconContainer } { isUploadFailed && <Text style={ style.uploadFailedText }>{ retryMessage }</Text> } </View> } @@ -211,7 +231,7 @@ class VideoEdit extends React.Component { } } /> { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( - <View style={ { padding: 12, flex: 1 } }> + <View style={ { paddingTop: 8, paddingBottom: 0, flex: 1 } }> <TextInput style={ { textAlign: 'center' } } fontFamily={ this.props.fontFamily || ( style[ 'caption-text' ].fontFamily ) } diff --git a/packages/block-library/src/video/icon-retry.js b/packages/block-library/src/video/icon-retry.js new file mode 100644 index 00000000000000..82a4be4805c455 --- /dev/null +++ b/packages/block-library/src/video/icon-retry.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +function svg( props ) { + return <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" { ...props }><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG>; +} + +export default svg; diff --git a/packages/block-library/src/video/icon.js b/packages/block-library/src/video/icon.js index 90dd0d64d58ff6..9cf0a1b9989874 100644 --- a/packages/block-library/src/video/icon.js +++ b/packages/block-library/src/video/icon.js @@ -3,4 +3,8 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /></SVG>; +function svg( props ) { + return <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" { ...props }><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /></SVG>; +} + +export default svg; diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss index cff1b9e31c80e9..c9f3279bdd9fbc 100644 --- a/packages/block-library/src/video/style.native.scss +++ b/packages/block-library/src/video/style.native.scss @@ -1,7 +1,7 @@ // @format .video { - background-color: #e9eff3; + background-color: $gray-lighten-30; width: 100%; } @@ -15,7 +15,7 @@ justify-content: center; align-items: center; fill: $gray-dark; - background-color: #e9eff3; + background-color: $gray-lighten-30; } .uploadFailedText { @@ -24,6 +24,39 @@ margin-top: 5; } +.modalIcon { + width: 40px; + height: 40px; + justify-content: center; + align-items: center; +} + +.modalIconRetry { + width: 80px; + height: 80px; + justify-content: center; + align-items: center; +} + +.container { + flex: 1; +} + +.containerFocused { + flex: 1; + border-color: $blue-medium; + border-width: 2px; + border-style: solid; +} + .caption-text { font-family: $default-regular-font; } + +.icon { + fill: $gray-dark; +} + +.iconUploading { + fill: $gray-lighten-20; +} diff --git a/packages/block-library/src/video/video-player.android.js b/packages/block-library/src/video/video-player.android.js index a9dcd4cbdf52ea..efe91ceda019fa 100644 --- a/packages/block-library/src/video/video-player.android.js +++ b/packages/block-library/src/video/video-player.android.js @@ -10,13 +10,15 @@ import { default as VideoPlayer } from 'react-native-video'; import styles from './video-player.scss'; const Video = ( props ) => { + const { isSelected, ...videoProps } = props; + return ( <View style={ styles.videoContainer }> <VideoPlayer - { ...props } + { ...videoProps } // We are using built-in player controls becasue manually // calling presentFullscreenPlayer() is not working for android - controls={ true } + controls={ isSelected } /> </View> ); diff --git a/packages/block-library/src/video/video-player.ios.js b/packages/block-library/src/video/video-player.ios.js index 47ea7f7cfc0b7e..0713afc404b1a3 100644 --- a/packages/block-library/src/video/video-player.ios.js +++ b/packages/block-library/src/video/video-player.ios.js @@ -41,7 +41,7 @@ class Video extends Component { } render() { - const { style } = this.props; + const { isSelected, style } = this.props; const { isLoaded } = this.state; return ( @@ -59,7 +59,7 @@ class Video extends Component { onLoadStart={ this.onLoadStart } /> { isLoaded && - <TouchableOpacity onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> + <TouchableOpacity disabled={ ! isSelected } onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> <View style={ styles.playIcon }> <Dashicon icon={ 'controls-play' } ariaPressed={ 'dashicon-active' } size={ styles.playIcon.width } /> </View> diff --git a/packages/components/src/spinner/index.native.js b/packages/components/src/spinner/index.native.js index 0c7e9902ffe397..14070e423cb991 100644 --- a/packages/components/src/spinner/index.native.js +++ b/packages/components/src/spinner/index.native.js @@ -3,14 +3,17 @@ */ import { View } from 'react-native'; +/** + * Internal dependencies + */ +import style from './style.scss'; + export default function Spinner( props ) { const { progress } = props; const width = progress + '%'; return ( - <View style={ { flex: 1, height: 5, backgroundColor: '#c8d7e1' } }> - <View style={ { width, height: 5, backgroundColor: '#0087be' } } /> - </View> + <View style={ [ style.spinner, { width } ] } /> ); } diff --git a/packages/components/src/spinner/style.native.scss b/packages/components/src/spinner/style.native.scss new file mode 100644 index 00000000000000..847e7a701e8d85 --- /dev/null +++ b/packages/components/src/spinner/style.native.scss @@ -0,0 +1,4 @@ +.spinner { + height: 6; + background: $blue-wordpress; +} From 5df6a0667e4547ccdfdb8858ce71cf4efe1cd47f Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 21 May 2019 04:25:39 -0400 Subject: [PATCH 145/664] Framework: Consolidate Docker commands in site installation (#15742) --- bin/install-wordpress.sh | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index 233bf8a8b145c7..8d20d91e3a8845 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -64,13 +64,8 @@ fi # Make sure the uploads and upgrade folders exist and we have permissions to add files. echo -e $(status_message "Ensuring that files can be uploaded...") -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-content/plugins -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-config.php -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-settings.php -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/uploads -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod -v 767 /var/www/html/wp-content/uploads -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/upgrade -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-content/upgrade +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER mkdir -p /var/www/html/wp-content/uploads /var/www/html/wp-content/upgrade +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER chmod 767 /var/www/html/wp-content/plugins /var/www/html/wp-config.php /var/www/html/wp-settings.php /var/www/html/wp-content/uploads /var/www/html/wp-content/upgrade CURRENT_WP_VERSION=$(docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run -T --rm $CLI core version) echo -e $(status_message "Current WordPress version: $CURRENT_WP_VERSION...") @@ -97,9 +92,7 @@ docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin activate if [ "$POPULAR_PLUGINS" == "true" ]; then echo -e $(status_message "Activating popular plugins...") - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install advanced-custom-fields --activate --quiet - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install jetpack --activate --quiet - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install wpforms-lite --activate --quiet + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install advanced-custom-fields jetpack wpforms-lite --activate --quiet fi # Install a dummy favicon to avoid 404 errors. From 22ab40696f25ddbe76ed667e722f49b11641c0c7 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 21 May 2019 10:53:38 +0100 Subject: [PATCH 146/664] Add color options to heading block. (#15625) --- packages/block-library/src/heading/block.json | 12 ++ packages/block-library/src/heading/edit.js | 103 +++++++++++++++++- packages/block-library/src/heading/save.js | 36 +++++- packages/block-library/src/heading/style.scss | 10 ++ packages/block-library/src/style.scss | 1 + 5 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 packages/block-library/src/heading/style.scss diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 120bece3d39ae3..5582c4bb402635 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -17,6 +17,18 @@ }, "placeholder": { "type": "string" + }, + "textColor": { + "type": "string" + }, + "customTextColor": { + "type": "string" + }, + "backgroundColor": { + "type": "string" + }, + "customBackgroundColor": { + "type": "string" } } } diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 048c1b9d67ddfc..378078824fc4d9 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * Internal dependencies */ @@ -7,21 +12,85 @@ import HeadingToolbar from './heading-toolbar'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { PanelBody } from '@wordpress/components'; +import { PanelBody, withFallbackStyles } from '@wordpress/components'; +import { compose } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { - RichText, + AlignmentToolbar, BlockControls, InspectorControls, - AlignmentToolbar, + RichText, + withColors, + PanelColorSettings, + ContrastChecker, } from '@wordpress/block-editor'; +import { memo } from '@wordpress/element'; + +const { getComputedStyle } = window; +const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { + const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes; + const editableNode = node.querySelector( '[contenteditable="true"]' ); + //verify if editableNode is available, before using getComputedStyle. + const computedStyles = editableNode ? getComputedStyle( editableNode ) : null; + return { + fallbackBackgroundColor: backgroundColor || ! computedStyles ? undefined : computedStyles.backgroundColor, + fallbackTextColor: textColor || ! computedStyles ? undefined : computedStyles.color, + fallbackFontSize: fontSize || customFontSize || ! computedStyles ? undefined : parseInt( computedStyles.fontSize ) || undefined, + }; +} ); + +const HeadingColorUI = memo( + function( { + backgroundColorValue, + setBackgroundColor, + textColorValue, + setTextColor, + fallbackTextColor, + fallbackBackgroundColor, + } ) { + return ( + <PanelColorSettings + title={ __( 'Color Settings' ) } + initialOpen={ false } + colorSettings={ [ + { + value: backgroundColorValue, + onChange: setBackgroundColor, + label: __( 'Background Color' ), + }, + { + value: textColorValue, + onChange: setTextColor, + label: __( 'Text Color' ), + }, + ] } + > + <ContrastChecker + { ...{ + textColor: textColorValue, + backgroundColor: backgroundColorValue, + fallbackTextColor, + fallbackBackgroundColor, + } } + isLargeText + /> + </PanelColorSettings> + ); + } +); -export default function HeadingEdit( { +function HeadingEdit( { attributes, setAttributes, mergeBlocks, onReplace, className, + backgroundColor, + textColor, + setBackgroundColor, + setTextColor, + fallbackBackgroundColor, + fallbackTextColor, } ) { const { align, content, level, placeholder } = attributes; const tagName = 'h' + level; @@ -43,6 +112,14 @@ export default function HeadingEdit( { } } /> </PanelBody> + <HeadingColorUI + backgroundColorValue={ backgroundColor.color } + fallbackBackgroundColor={ fallbackBackgroundColor } + fallbackTextColor={ fallbackTextColor } + setBackgroundColor={ setBackgroundColor } + setTextColor={ setTextColor } + textColorValue={ textColor.color } + /> </InspectorControls> <RichText identifier="content" @@ -63,10 +140,24 @@ export default function HeadingEdit( { } } onReplace={ onReplace } onRemove={ () => onReplace( [] ) } - style={ { textAlign: align } } - className={ className } + className={ classnames( className, { + 'has-background': backgroundColor.color, + 'has-text-color': textColor.color, + [ backgroundColor.class ]: backgroundColor.class, + [ textColor.class ]: textColor.class, + } ) } placeholder={ placeholder || __( 'Write heading…' ) } + style={ { + backgroundColor: backgroundColor.color, + color: textColor.color, + textAlign: align, + } } /> </> ); } + +export default compose( [ + withColors( 'backgroundColor', { textColor: 'color' } ), + applyFallbackStyles, +] )( HeadingEdit ); diff --git a/packages/block-library/src/heading/save.js b/packages/block-library/src/heading/save.js index 522013158ee997..84c0f33463bccc 100644 --- a/packages/block-library/src/heading/save.js +++ b/packages/block-library/src/heading/save.js @@ -1,16 +1,46 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { RichText } from '@wordpress/block-editor'; +import { + getColorClassName, + RichText, +} from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { align, level, content } = attributes; + const { + align, + backgroundColor, + customBackgroundColor, + level, + content, + textColor, + customTextColor, + } = attributes; const tagName = 'h' + level; + const textClass = getColorClassName( 'color', textColor ); + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + + const className = classnames( { + 'has-background': backgroundColor || customBackgroundColor, + [ textClass ]: textClass, + [ backgroundClass ]: backgroundClass, + } ); + return ( <RichText.Content + className={ className ? className : undefined } tagName={ tagName } - style={ { textAlign: align } } + style={ { + textAlign: align, + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + color: textClass ? undefined : customTextColor, + } } value={ content } /> ); diff --git a/packages/block-library/src/heading/style.scss b/packages/block-library/src/heading/style.scss new file mode 100644 index 00000000000000..31a6989c39c817 --- /dev/null +++ b/packages/block-library/src/heading/style.scss @@ -0,0 +1,10 @@ +h1, +h2, +h3, +h4, +h5, +h6 { + &.has-background { + padding: $block-bg-padding--v $block-bg-padding--h; + } +} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 73325d5d9167c1..e0bce8b7fd4d91 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -9,6 +9,7 @@ @import "./embed/style.scss"; @import "./file/style.scss"; @import "./gallery/style.scss"; +@import "./heading/style.scss"; @import "./image/style.scss"; @import "./latest-comments/style.scss"; @import "./latest-posts/style.scss"; From 24418b11d96bb6febaf9a73f09dbf2b00b50d218 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 21 May 2019 11:31:31 +0100 Subject: [PATCH 147/664] Fix: Notice blocks don't match template doesn't appear; Add test case; (#15418) --- .../e2e-tests/specs/plugins/cpt-locking.test.js | 15 +++++++++++++++ packages/editor/src/components/provider/index.js | 1 + 2 files changed, 16 insertions(+) diff --git a/packages/e2e-tests/specs/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/plugins/cpt-locking.test.js index 4239716c6c1a48..74f8f81bf67e3f 100644 --- a/packages/e2e-tests/specs/plugins/cpt-locking.test.js +++ b/packages/e2e-tests/specs/plugins/cpt-locking.test.js @@ -9,6 +9,7 @@ import { getEditedPostContent, insertBlock, pressKeyTimes, + setPostContent, } from '@wordpress/e2e-test-utils'; describe( 'cpt locking', () => { @@ -67,6 +68,20 @@ describe( 'cpt locking', () => { await pressKeyTimes( 'Backspace', textToType.length + 1 ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should show invalid template notice if the blocks do not match the templte', async () => { + const content = await getEditedPostContent(); + const [ , contentWithoutImage ] = content.split( '<!-- /wp:image -->' ); + await setPostContent( contentWithoutImage ); + const VALIDATION_PARAGRAPH_SELECTOR = '.editor-template-validation-notice .components-notice__content p'; + await page.waitForSelector( VALIDATION_PARAGRAPH_SELECTOR ); + expect( + await page.evaluate( + ( element ) => element.textContent, + await page.$( VALIDATION_PARAGRAPH_SELECTOR ) + ) + ).toEqual( 'The content of your post doesn’t match the template assigned to your post type.' ); + } ); } ); describe( 'template_lock insert', () => { diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 54405f61247b8f..31dbc721bf822f 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -89,6 +89,7 @@ class EditorProvider extends Component { 'isRTL', 'maxWidth', 'styles', + 'template', 'templateLock', 'titlePlaceholder', ] ), From 35fc599a484127317f85427741d802fa79bb4a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Tue, 21 May 2019 13:48:17 +0200 Subject: [PATCH 148/664] Block library: Extract `deprecated` field to their own files (#15057) * Block library: Extracte deprecated fields to their own files * Block library: Remove obsolete deprecations from List block * Block library: Remove obsolete deprecations from Heading block --- .../block-library/src/gallery/deprecated.js | 198 ++++++++++++++++++ packages/block-library/src/gallery/index.js | 190 +---------------- packages/block-library/src/heading/index.js | 53 +---- .../block-library/src/image/deprecated.js | 149 +++++++++++++ packages/block-library/src/image/index.js | 86 +------- packages/block-library/src/list/index.js | 49 +---- 6 files changed, 361 insertions(+), 364 deletions(-) create mode 100644 packages/block-library/src/gallery/deprecated.js create mode 100644 packages/block-library/src/image/deprecated.js diff --git a/packages/block-library/src/gallery/deprecated.js b/packages/block-library/src/gallery/deprecated.js new file mode 100644 index 00000000000000..2b77e33e0b36e4 --- /dev/null +++ b/packages/block-library/src/gallery/deprecated.js @@ -0,0 +1,198 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { map, some } from 'lodash'; + +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { defaultColumnsNumber } from './shared'; + +const deprecated = [ + { + attributes: { + images: { + type: 'array', + default: [], + source: 'query', + selector: 'ul.wp-block-gallery .blocks-gallery-item', + query: { + url: { + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + alt: { + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + }, + id: { + source: 'attribute', + selector: 'img', + attribute: 'data-id', + }, + link: { + source: 'attribute', + selector: 'img', + attribute: 'data-link', + }, + caption: { + type: 'array', + source: 'children', + selector: 'figcaption', + }, + }, + }, + columns: { + type: 'number', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + type: 'string', + default: 'none', + }, + }, + isEligible( { images, ids } ) { + return images && + images.length > 0 && + ( + ( ! ids && images ) || + ( ids && images && ids.length !== images.length ) || + some( images, ( id, index ) => { + if ( ! id && ids[ index ] !== null ) { + return true; + } + return parseInt( id, 10 ) !== ids[ index ]; + } ) + ); + }, + migrate( attributes ) { + return { + ...attributes, + ids: map( attributes.images, ( { id } ) => { + if ( ! id ) { + return null; + } + return parseInt( id, 10 ); + } ), + }; + }, + save( { attributes } ) { + const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; + return ( + <ul className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > + { images.map( ( image ) => { + let href; + + switch ( linkTo ) { + case 'media': + href = image.url; + break; + case 'attachment': + href = image.link; + break; + } + + const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } data-link={ image.link } className={ image.id ? `wp-image-${ image.id }` : null } />; + + return ( + <li key={ image.id || image.url } className="blocks-gallery-item"> + <figure> + { href ? <a href={ href }>{ img }</a> : img } + { image.caption && image.caption.length > 0 && ( + <RichText.Content tagName="figcaption" value={ image.caption } /> + ) } + </figure> + </li> + ); + } ) } + </ul> + ); + }, + }, + { + attributes: { + images: { + type: 'array', + default: [], + source: 'query', + selector: 'div.wp-block-gallery figure.blocks-gallery-image img', + query: { + url: { + source: 'attribute', + attribute: 'src', + }, + alt: { + source: 'attribute', + attribute: 'alt', + default: '', + }, + id: { + source: 'attribute', + attribute: 'data-id', + }, + }, + }, + columns: { + type: 'number', + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + type: 'string', + default: 'none', + }, + align: { + type: 'string', + default: 'none', + }, + }, + + save( { attributes } ) { + const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes; + const className = classnames( `columns-${ columns }`, { + alignnone: align === 'none', + 'is-cropped': imageCrop, + } ); + return ( + <div className={ className } > + { images.map( ( image ) => { + let href; + + switch ( linkTo ) { + case 'media': + href = image.url; + break; + case 'attachment': + href = image.link; + break; + } + + const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } />; + + return ( + <figure key={ image.id || image.url } className="blocks-gallery-image"> + { href ? <a href={ href }>{ img }</a> : img } + </figure> + ); + } ) } + </div> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index ba5e06f0df7ebc..a3c5eb00fd38d3 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -1,24 +1,17 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; -import { map, some } from 'lodash'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -import { defaultColumnsNumber } from './shared'; const { name } = metadata; @@ -35,184 +28,5 @@ export const settings = { transforms, edit, save, - deprecated: [ - { - attributes: { - images: { - type: 'array', - default: [], - source: 'query', - selector: 'ul.wp-block-gallery .blocks-gallery-item', - query: { - url: { - source: 'attribute', - selector: 'img', - attribute: 'src', - }, - alt: { - source: 'attribute', - selector: 'img', - attribute: 'alt', - default: '', - }, - id: { - source: 'attribute', - selector: 'img', - attribute: 'data-id', - }, - link: { - source: 'attribute', - selector: 'img', - attribute: 'data-link', - }, - caption: { - type: 'array', - source: 'children', - selector: 'figcaption', - }, - }, - }, - columns: { - type: 'number', - }, - imageCrop: { - type: 'boolean', - default: true, - }, - linkTo: { - type: 'string', - default: 'none', - }, - }, - isEligible( { images, ids } ) { - return images && - images.length > 0 && - ( - ( ! ids && images ) || - ( ids && images && ids.length !== images.length ) || - some( images, ( id, index ) => { - if ( ! id && ids[ index ] !== null ) { - return true; - } - return parseInt( id, 10 ) !== ids[ index ]; - } ) - ); - }, - migrate( attributes ) { - return { - ...attributes, - ids: map( attributes.images, ( { id } ) => { - if ( ! id ) { - return null; - } - return parseInt( id, 10 ); - } ), - }; - }, - save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; - return ( - <ul className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > - { images.map( ( image ) => { - let href; - - switch ( linkTo ) { - case 'media': - href = image.url; - break; - case 'attachment': - href = image.link; - break; - } - - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } data-link={ image.link } className={ image.id ? `wp-image-${ image.id }` : null } />; - - return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> - <figure> - { href ? <a href={ href }>{ img }</a> : img } - { image.caption && image.caption.length > 0 && ( - <RichText.Content tagName="figcaption" value={ image.caption } /> - ) } - </figure> - </li> - ); - } ) } - </ul> - ); - }, - }, - { - attributes: { - images: { - type: 'array', - default: [], - source: 'query', - selector: 'div.wp-block-gallery figure.blocks-gallery-image img', - query: { - url: { - source: 'attribute', - attribute: 'src', - }, - alt: { - source: 'attribute', - attribute: 'alt', - default: '', - }, - id: { - source: 'attribute', - attribute: 'data-id', - }, - }, - }, - columns: { - type: 'number', - }, - imageCrop: { - type: 'boolean', - default: true, - }, - linkTo: { - type: 'string', - default: 'none', - }, - align: { - type: 'string', - default: 'none', - }, - }, - - save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes; - const className = classnames( `columns-${ columns }`, { - alignnone: align === 'none', - 'is-cropped': imageCrop, - } ); - return ( - <div className={ className } > - { images.map( ( image ) => { - let href; - - switch ( linkTo ) { - case 'media': - href = image.url; - break; - case 'attachment': - href = image.link; - break; - } - - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } />; - - return ( - <figure key={ image.id || image.url } className="blocks-gallery-image"> - { href ? <a href={ href }>{ img }</a> : img } - </figure> - ); - } ) } - </div> - ); - }, - }, - ], + deprecated, }; diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 6c0d58b4e240bd..7706f44bb54ce6 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -1,13 +1,7 @@ -/** - * External dependencies - */ -import { omit } from 'lodash'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies @@ -16,58 +10,21 @@ import edit from './edit'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -import { getLevelFromHeadingNodeName } from './shared'; -const { name, attributes: schema } = metadata; +const { name } = metadata; export { metadata, name }; -const supports = { - className: false, - anchor: true, -}; - export const settings = { title: __( 'Heading' ), description: __( 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.' ), icon: 'heading', keywords: [ __( 'title' ), __( 'subtitle' ) ], - supports, + supports: { + className: false, + anchor: true, + }, transforms, - deprecated: [ - { - supports, - attributes: { - ...omit( schema, [ 'level' ] ), - nodeName: { - type: 'string', - source: 'property', - selector: 'h1,h2,h3,h4,h5,h6', - property: 'nodeName', - default: 'H2', - }, - }, - migrate( attributes ) { - const { nodeName, ...migratedAttributes } = attributes; - - return { - ...migratedAttributes, - level: getLevelFromHeadingNodeName( nodeName ), - }; - }, - save( { attributes } ) { - const { align, nodeName, content } = attributes; - - return ( - <RichText.Content - tagName={ nodeName.toLowerCase() } - style={ { textAlign: align } } - value={ content } - /> - ); - }, - }, - ], merge( attributes, attributesToMerge ) { return { content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js new file mode 100644 index 00000000000000..952cb77b52cc16 --- /dev/null +++ b/packages/block-library/src/image/deprecated.js @@ -0,0 +1,149 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +const blockAttributes = { + align: { + type: 'string', + }, + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + alt: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + }, + caption: { + type: 'string', + source: 'html', + selector: 'figcaption', + }, + href: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'href', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'rel', + }, + linkClass: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'class', + }, + id: { + type: 'number', + }, + width: { + type: 'number', + }, + height: { + type: 'number', + }, + linkDestination: { + type: 'string', + default: 'none', + }, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'target', + }, +}; + +const deprecated = [ + { + attributes: blockAttributes, + save( { attributes } ) { + const { url, alt, caption, align, href, width, height, id } = attributes; + + const classes = classnames( { + [ `align${ align }` ]: align, + 'is-resized': width || height, + } ); + + const image = ( + <img + src={ url } + alt={ alt } + className={ id ? `wp-image-${ id }` : null } + width={ width } + height={ height } + /> + ); + + return ( + <figure className={ classes }> + { href ? <a href={ href }>{ image }</a> : image } + { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + </figure> + ); + }, + }, + { + attributes: blockAttributes, + save( { attributes } ) { + const { url, alt, caption, align, href, width, height, id } = attributes; + + const image = ( + <img + src={ url } + alt={ alt } + className={ id ? `wp-image-${ id }` : null } + width={ width } + height={ height } + /> + ); + + return ( + <figure className={ align ? `align${ align }` : null } > + { href ? <a href={ href }>{ image }</a> : image } + { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + </figure> + ); + }, + }, + { + attributes: blockAttributes, + save( { attributes } ) { + const { url, alt, caption, align, href, width, height } = attributes; + const extraImageProps = width || height ? { width, height } : {}; + const image = <img src={ url } alt={ alt } { ...extraImageProps } />; + + let figureStyle = {}; + + if ( width ) { + figureStyle = { width }; + } else if ( align === 'left' || align === 'right' ) { + figureStyle = { maxWidth: '50%' }; + } + + return ( + <figure className={ align ? `align${ align }` : null } style={ figureStyle }> + { href ? <a href={ href }>{ image }</a> : image } + { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + </figure> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index cf8eb321c4700a..7d72cc6c103f3f 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -1,24 +1,19 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ -import { RichText } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -const { name, attributes: blockAttributes } = metadata; +const { name } = metadata; export { metadata, name }; @@ -39,80 +34,5 @@ export const settings = { }, edit, save, - deprecated: [ - { - attributes: blockAttributes, - save( { attributes } ) { - const { url, alt, caption, align, href, width, height, id } = attributes; - - const classes = classnames( { - [ `align${ align }` ]: align, - 'is-resized': width || height, - } ); - - const image = ( - <img - src={ url } - alt={ alt } - className={ id ? `wp-image-${ id }` : null } - width={ width } - height={ height } - /> - ); - - return ( - <figure className={ classes }> - { href ? <a href={ href }>{ image }</a> : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } - </figure> - ); - }, - }, - { - attributes: blockAttributes, - save( { attributes } ) { - const { url, alt, caption, align, href, width, height, id } = attributes; - - const image = ( - <img - src={ url } - alt={ alt } - className={ id ? `wp-image-${ id }` : null } - width={ width } - height={ height } - /> - ); - - return ( - <figure className={ align ? `align${ align }` : null } > - { href ? <a href={ href }>{ image }</a> : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } - </figure> - ); - }, - }, - { - attributes: blockAttributes, - save( { attributes } ) { - const { url, alt, caption, align, href, width, height } = attributes; - const extraImageProps = width || height ? { width, height } : {}; - const image = <img src={ url } alt={ alt } { ...extraImageProps } />; - - let figureStyle = {}; - - if ( width ) { - figureStyle = { width }; - } else if ( align === 'left' || align === 'right' ) { - figureStyle = { maxWidth: '50%' }; - } - - return ( - <figure className={ align ? `align${ align }` : null } style={ figureStyle }> - { href ? <a href={ href }>{ image }</a> : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } - </figure> - ); - }, - }, - ], + deprecated, }; diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index dcc1098e71b13b..d7d857502b1ad2 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -1,13 +1,7 @@ -/** - * External dependencies - */ -import { omit } from 'lodash'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies @@ -18,54 +12,19 @@ import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -const { name, attributes: schema } = metadata; +const { name } = metadata; export { metadata, name }; -const supports = { - className: false, -}; - export const settings = { title: __( 'List' ), description: __( 'Create a bulleted or numbered list.' ), icon, keywords: [ __( 'bullet list' ), __( 'ordered list' ), __( 'numbered list' ) ], - supports, + supports: { + className: false, + }, transforms, - deprecated: [ - { - supports, - attributes: { - ...omit( schema, [ 'ordered' ] ), - nodeName: { - type: 'string', - source: 'property', - selector: 'ol,ul', - property: 'nodeName', - default: 'UL', - }, - }, - migrate( attributes ) { - const { nodeName, ...migratedAttributes } = attributes; - - return { - ...migratedAttributes, - ordered: 'OL' === nodeName, - }; - }, - save( { attributes } ) { - const { nodeName, values } = attributes; - - return ( - <RichText.Content - tagName={ nodeName.toLowerCase() } - value={ values } - /> - ); - }, - }, - ], merge( attributes, attributesToMerge ) { const { values } = attributesToMerge; From 9e6c0413ed3a916739993e99fa519d81f838e6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz.ziolkowski@automattic.com> Date: Tue, 21 May 2019 13:50:05 +0200 Subject: [PATCH 149/664] Block library: Extract `deprecated` field to their own files (p.2) (#15071) --- .../block-library/src/paragraph/deprecated.js | 168 ++++++++++++++++++ packages/block-library/src/paragraph/index.js | 129 +------------- .../block-library/src/pullquote/deprecated.js | 74 ++++++++ packages/block-library/src/pullquote/index.js | 43 +---- .../block-library/src/quote/deprecated.js | 96 ++++++++++ packages/block-library/src/quote/index.js | 75 +------- 6 files changed, 350 insertions(+), 235 deletions(-) create mode 100644 packages/block-library/src/paragraph/deprecated.js create mode 100644 packages/block-library/src/pullquote/deprecated.js create mode 100644 packages/block-library/src/quote/deprecated.js diff --git a/packages/block-library/src/paragraph/deprecated.js b/packages/block-library/src/paragraph/deprecated.js new file mode 100644 index 00000000000000..c3f18db62d62c3 --- /dev/null +++ b/packages/block-library/src/paragraph/deprecated.js @@ -0,0 +1,168 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { isFinite, omit } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + RawHTML, +} from '@wordpress/element'; +import { + getColorClassName, + RichText, +} from '@wordpress/block-editor'; + +const supports = { + className: false, +}; + +const blockAttributes = { + align: { + type: 'string', + }, + content: { + type: 'string', + source: 'html', + selector: 'p', + default: '', + }, + dropCap: { + type: 'boolean', + default: false, + }, + placeholder: { + type: 'string', + }, + textColor: { + type: 'string', + }, + customTextColor: { + type: 'string', + }, + backgroundColor: { + type: 'string', + }, + customBackgroundColor: { + type: 'string', + }, + fontSize: { + type: 'string', + }, + customFontSize: { + type: 'number', + }, + direction: { + type: 'string', + enum: [ 'ltr', 'rtl' ], + }, +}; + +const deprecated = [ + { + supports, + attributes: { + ...blockAttributes, + width: { + type: 'string', + }, + }, + save( { attributes } ) { + const { + width, + align, + content, + dropCap, + backgroundColor, + textColor, + customBackgroundColor, + customTextColor, + fontSize, + customFontSize, + } = attributes; + + const textClass = getColorClassName( 'color', textColor ); + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const fontSizeClass = fontSize && `is-${ fontSize }-text`; + + const className = classnames( { + [ `align${ width }` ]: width, + 'has-background': backgroundColor || customBackgroundColor, + 'has-drop-cap': dropCap, + [ fontSizeClass ]: fontSizeClass, + [ textClass ]: textClass, + [ backgroundClass ]: backgroundClass, + } ); + + const styles = { + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + color: textClass ? undefined : customTextColor, + fontSize: fontSizeClass ? undefined : customFontSize, + textAlign: align, + }; + + return ( + <RichText.Content + tagName="p" + style={ styles } + className={ className ? className : undefined } + value={ content } + /> + ); + }, + }, + { + supports, + attributes: omit( { + ...blockAttributes, + fontSize: { + type: 'number', + }, + }, 'customFontSize', 'customTextColor', 'customBackgroundColor' ), + save( { attributes } ) { + const { width, align, content, dropCap, backgroundColor, textColor, fontSize } = attributes; + const className = classnames( { + [ `align${ width }` ]: width, + 'has-background': backgroundColor, + 'has-drop-cap': dropCap, + } ); + const styles = { + backgroundColor, + color: textColor, + fontSize, + textAlign: align, + }; + + return <p style={ styles } className={ className ? className : undefined }>{ content }</p>; + }, + migrate( attributes ) { + return omit( { + ...attributes, + customFontSize: isFinite( attributes.fontSize ) ? attributes.fontSize : undefined, + customTextColor: attributes.textColor && '#' === attributes.textColor[ 0 ] ? attributes.textColor : undefined, + customBackgroundColor: attributes.backgroundColor && '#' === attributes.backgroundColor[ 0 ] ? attributes.backgroundColor : undefined, + }, [ 'fontSize', 'textColor', 'backgroundColor' ] ); + }, + }, + { + supports, + attributes: { + ...blockAttributes, + content: { + type: 'string', + source: 'html', + default: '', + }, + }, + save( { attributes } ) { + return <RawHTML>{ attributes.content }</RawHTML>; + }, + migrate( attributes ) { + return attributes; + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index a5578252f8fee4..5ef563ce55bf33 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -1,149 +1,32 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; -import { isFinite, omit } from 'lodash'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - RawHTML, -} from '@wordpress/element'; -import { - getColorClassName, - RichText, -} from '@wordpress/block-editor'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -const { name, attributes: schema } = metadata; +const { name } = metadata; export { metadata, name }; -const supports = { - className: false, -}; - export const settings = { title: __( 'Paragraph' ), description: __( 'Start with the building block of all narrative.' ), icon, keywords: [ __( 'text' ) ], - supports, + supports: { + className: false, + }, transforms, - deprecated: [ - { - supports, - attributes: { - ...schema, - width: { - type: 'string', - }, - }, - save( { attributes } ) { - const { - width, - align, - content, - dropCap, - backgroundColor, - textColor, - customBackgroundColor, - customTextColor, - fontSize, - customFontSize, - } = attributes; - - const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); - const fontSizeClass = fontSize && `is-${ fontSize }-text`; - - const className = classnames( { - [ `align${ width }` ]: width, - 'has-background': backgroundColor || customBackgroundColor, - 'has-drop-cap': dropCap, - [ fontSizeClass ]: fontSizeClass, - [ textClass ]: textClass, - [ backgroundClass ]: backgroundClass, - } ); - - const styles = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, - color: textClass ? undefined : customTextColor, - fontSize: fontSizeClass ? undefined : customFontSize, - textAlign: align, - }; - - return ( - <RichText.Content - tagName="p" - style={ styles } - className={ className ? className : undefined } - value={ content } - /> - ); - }, - }, - { - supports, - attributes: omit( { - ...schema, - fontSize: { - type: 'number', - }, - }, 'customFontSize', 'customTextColor', 'customBackgroundColor' ), - save( { attributes } ) { - const { width, align, content, dropCap, backgroundColor, textColor, fontSize } = attributes; - const className = classnames( { - [ `align${ width }` ]: width, - 'has-background': backgroundColor, - 'has-drop-cap': dropCap, - } ); - const styles = { - backgroundColor, - color: textColor, - fontSize, - textAlign: align, - }; - - return <p style={ styles } className={ className ? className : undefined }>{ content }</p>; - }, - migrate( attributes ) { - return omit( { - ...attributes, - customFontSize: isFinite( attributes.fontSize ) ? attributes.fontSize : undefined, - customTextColor: attributes.textColor && '#' === attributes.textColor[ 0 ] ? attributes.textColor : undefined, - customBackgroundColor: attributes.backgroundColor && '#' === attributes.backgroundColor[ 0 ] ? attributes.backgroundColor : undefined, - }, [ 'fontSize', 'textColor', 'backgroundColor' ] ); - }, - }, - { - supports, - attributes: { - ...schema, - content: { - type: 'string', - source: 'html', - default: '', - }, - }, - save( { attributes } ) { - return <RawHTML>{ attributes.content }</RawHTML>; - }, - migrate( attributes ) { - return attributes; - }, - }, - ], + deprecated, merge( attributes, attributesToMerge ) { return { content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js new file mode 100644 index 00000000000000..8598fd0296dc3e --- /dev/null +++ b/packages/block-library/src/pullquote/deprecated.js @@ -0,0 +1,74 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +const blockAttributes = { + value: { + type: 'string', + source: 'html', + selector: 'blockquote', + multiline: 'p', + }, + citation: { + type: 'string', + source: 'html', + selector: 'cite', + default: '', + }, + mainColor: { + type: 'string', + }, + customMainColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + customTextColor: { + type: 'string', + }, +}; + +const deprecated = [ + { + attributes: { + ...blockAttributes, + }, + save( { attributes } ) { + const { value, citation } = attributes; + return ( + <blockquote> + <RichText.Content value={ value } multiline /> + { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + </blockquote> + ); + }, + }, { + attributes: { + ...blockAttributes, + citation: { + type: 'string', + source: 'html', + selector: 'footer', + }, + align: { + type: 'string', + default: 'none', + }, + }, + + save( { attributes } ) { + const { value, citation, align } = attributes; + + return ( + <blockquote className={ `align${ align }` }> + <RichText.Content value={ value } multiline /> + { ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> } + </blockquote> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index 0c7c29b38ac670..944f7562385b5b 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -2,18 +2,18 @@ * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies */ import { SOLID_COLOR_STYLE_NAME } from './shared'; +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; import save from './save'; -const { name, attributes: blockAttributes } = metadata; +const { name } = metadata; export { metadata, name }; @@ -30,42 +30,5 @@ export const settings = { }, edit, save, - deprecated: [ { - attributes: { - ...blockAttributes, - }, - save( { attributes } ) { - const { value, citation } = attributes; - return ( - <blockquote> - <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } - </blockquote> - ); - }, - }, { - attributes: { - ...blockAttributes, - citation: { - type: 'string', - source: 'html', - selector: 'footer', - }, - align: { - type: 'string', - default: 'none', - }, - }, - - save( { attributes } ) { - const { value, citation, align } = attributes; - - return ( - <blockquote className={ `align${ align }` }> - <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> } - </blockquote> - ); - }, - } ], + deprecated, }; diff --git a/packages/block-library/src/quote/deprecated.js b/packages/block-library/src/quote/deprecated.js new file mode 100644 index 00000000000000..61024ff336aed1 --- /dev/null +++ b/packages/block-library/src/quote/deprecated.js @@ -0,0 +1,96 @@ +/** + * External dependencies + */ +import { omit } from 'lodash'; + +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +const blockAttributes = { + value: { + type: 'string', + source: 'html', + selector: 'blockquote', + multiline: 'p', + default: '', + }, + citation: { + type: 'string', + source: 'html', + selector: 'cite', + default: '', + }, + align: { + type: 'string', + }, +}; + +const deprecated = [ + { + attributes: { + ...blockAttributes, + style: { + type: 'number', + default: 1, + }, + }, + + migrate( attributes ) { + if ( attributes.style === 2 ) { + return { + ...omit( attributes, [ 'style' ] ), + className: attributes.className ? attributes.className + ' is-style-large' : 'is-style-large', + }; + } + + return attributes; + }, + + save( { attributes } ) { + const { align, value, citation, style } = attributes; + + return ( + <blockquote + className={ style === 2 ? 'is-large' : '' } + style={ { textAlign: align ? align : null } } + > + <RichText.Content multiline value={ value } /> + { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + </blockquote> + ); + }, + }, + { + attributes: { + ...blockAttributes, + citation: { + type: 'string', + source: 'html', + selector: 'footer', + default: '', + }, + style: { + type: 'number', + default: 1, + }, + }, + + save( { attributes } ) { + const { align, value, citation, style } = attributes; + + return ( + <blockquote + className={ `blocks-quote-style-${ style }` } + style={ { textAlign: align ? align : null } } + > + <RichText.Content multiline value={ value } /> + { ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> } + </blockquote> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index 105bde9dde68bc..806b8597411cca 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -1,24 +1,19 @@ -/** - * External dependencies - */ -import { omit } from 'lodash'; - /** * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { RichText } from '@wordpress/block-editor'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; import save from './save'; import transforms from './transforms'; -const { name, attributes: blockAttributes } = metadata; +const { name } = metadata; export { metadata, name }; @@ -48,69 +43,5 @@ export const settings = { citation: attributes.citation + citation, }; }, - deprecated: [ - { - attributes: { - ...blockAttributes, - style: { - type: 'number', - default: 1, - }, - }, - - migrate( attributes ) { - if ( attributes.style === 2 ) { - return { - ...omit( attributes, [ 'style' ] ), - className: attributes.className ? attributes.className + ' is-style-large' : 'is-style-large', - }; - } - - return attributes; - }, - - save( { attributes } ) { - const { align, value, citation, style } = attributes; - - return ( - <blockquote - className={ style === 2 ? 'is-large' : '' } - style={ { textAlign: align ? align : null } } - > - <RichText.Content multiline value={ value } /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } - </blockquote> - ); - }, - }, - { - attributes: { - ...blockAttributes, - citation: { - type: 'string', - source: 'html', - selector: 'footer', - default: '', - }, - style: { - type: 'number', - default: 1, - }, - }, - - save( { attributes } ) { - const { align, value, citation, style } = attributes; - - return ( - <blockquote - className={ `blocks-quote-style-${ style }` } - style={ { textAlign: align ? align : null } } - > - <RichText.Content multiline value={ value } /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> } - </blockquote> - ); - }, - }, - ], + deprecated, }; From 389f5b8485d524bcf1f76a935aa44e966478b182 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Tue, 21 May 2019 13:13:07 +0200 Subject: [PATCH 150/664] chore(release): publish - @wordpress/a11y@2.3.0 - @wordpress/annotations@1.3.0 - @wordpress/api-fetch@3.2.0 - @wordpress/autop@2.3.0 - @wordpress/babel-plugin-import-jsx-pragma@2.2.0 - @wordpress/babel-plugin-makepot@3.1.0 - @wordpress/babel-preset-default@4.2.0 - @wordpress/blob@2.4.0 - @wordpress/block-editor@2.1.0 - @wordpress/block-library@2.5.0 - @wordpress/block-serialization-default-parser@3.2.0 - @wordpress/block-serialization-spec-parser@3.1.0 - @wordpress/blocks@6.3.0 - @wordpress/browserslist-config@2.4.0 - @wordpress/components@7.4.0 - @wordpress/compose@3.3.0 - @wordpress/core-data@2.3.0 - @wordpress/custom-templated-path-webpack-plugin@1.3.0 - @wordpress/data@4.5.0 - @wordpress/date@3.3.0 - @wordpress/dependency-extraction-webpack-plugin@1.0.0 - @wordpress/deprecated@2.3.0 - @wordpress/docgen@1.2.0 - @wordpress/dom-ready@2.3.0 - @wordpress/dom@2.3.0 - @wordpress/e2e-test-utils@2.0.0 - @wordpress/e2e-tests@1.2.0 - @wordpress/edit-post@3.4.0 - @wordpress/edit-widgets@0.3.0 - @wordpress/editor@9.3.0 - @wordpress/element@2.4.0 - @wordpress/escape-html@1.3.0 - @wordpress/eslint-plugin@2.2.0 - @wordpress/format-library@1.5.0 - @wordpress/hooks@2.3.0 - @wordpress/html-entities@2.3.0 - @wordpress/i18n@3.4.0 - @wordpress/is-shallow-equal@1.3.0 - @wordpress/jest-console@3.1.0 - @wordpress/jest-preset-default@4.1.0 - @wordpress/jest-puppeteer-axe@1.1.0 - @wordpress/keycodes@2.3.0 - @wordpress/library-export-default-webpack-plugin@1.2.0 - @wordpress/list-reusable-blocks@1.4.0 - @wordpress/notices@1.4.0 - @wordpress/npm-package-json-lint-config@1.3.0 - @wordpress/nux@3.3.0 - @wordpress/plugins@2.3.0 - @wordpress/postcss-themes@2.1.0 - @wordpress/priority-queue@1.2.0 - @wordpress/redux-routine@3.3.0 - @wordpress/rich-text@3.3.0 - @wordpress/scripts@3.2.0 - @wordpress/shortcode@2.3.0 - @wordpress/token-list@1.3.0 - @wordpress/url@2.6.0 - @wordpress/viewport@2.4.0 - @wordpress/wordcount@2.3.0 --- packages/a11y/package.json | 2 +- packages/annotations/package.json | 2 +- packages/api-fetch/package.json | 2 +- packages/autop/package.json | 2 +- packages/babel-plugin-import-jsx-pragma/package.json | 2 +- packages/babel-plugin-makepot/package.json | 2 +- packages/babel-preset-default/package.json | 2 +- packages/blob/package.json | 2 +- packages/block-editor/package.json | 2 +- packages/block-library/package.json | 2 +- packages/block-serialization-default-parser/package.json | 2 +- packages/block-serialization-spec-parser/package.json | 2 +- packages/blocks/package.json | 2 +- packages/browserslist-config/package.json | 2 +- packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/package.json | 2 +- packages/custom-templated-path-webpack-plugin/package.json | 2 +- packages/data/package.json | 2 +- packages/date/package.json | 2 +- packages/dependency-extraction-webpack-plugin/package.json | 2 +- packages/deprecated/package.json | 2 +- packages/docgen/package.json | 2 +- packages/dom-ready/package.json | 2 +- packages/dom/package.json | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-widgets/package.json | 2 +- packages/editor/package.json | 2 +- packages/element/package.json | 2 +- packages/escape-html/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/package.json | 2 +- packages/hooks/package.json | 2 +- packages/html-entities/package.json | 2 +- packages/i18n/package.json | 2 +- packages/is-shallow-equal/package.json | 2 +- packages/jest-console/package.json | 2 +- packages/jest-preset-default/package.json | 2 +- packages/jest-puppeteer-axe/package.json | 2 +- packages/keycodes/package.json | 2 +- packages/library-export-default-webpack-plugin/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/notices/package.json | 2 +- packages/npm-package-json-lint-config/package.json | 2 +- packages/nux/package.json | 2 +- packages/plugins/package.json | 2 +- packages/postcss-themes/package.json | 2 +- packages/priority-queue/package.json | 2 +- packages/redux-routine/package.json | 2 +- packages/rich-text/package.json | 2 +- packages/scripts/package.json | 2 +- packages/shortcode/package.json | 2 +- packages/token-list/package.json | 2 +- packages/url/package.json | 2 +- packages/viewport/package.json | 2 +- packages/wordcount/package.json | 2 +- 58 files changed, 58 insertions(+), 58 deletions(-) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index e7ce3cb38a718f..fc3561a2bef4b8 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "2.2.0", + "version": "2.3.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index af3c4c4a80087c..4f0c9165ff4edc 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.2.2", + "version": "1.3.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 67ed8e87c88556..1c647d817bbf02 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "3.1.2", + "version": "3.2.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/package.json b/packages/autop/package.json index 104fed8661a8e6..796a500413da8e 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "2.2.0", + "version": "2.3.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index c894a330b2fa07..6f4b029ebb5e64 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "2.1.0", + "version": "2.2.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index e90a946925ed82..802f8bd3c509d9 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "3.0.0", + "version": "3.1.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 88302e0cd14b39..22c29f779e529e 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "4.1.0", + "version": "4.2.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/package.json b/packages/blob/package.json index 27f407e49e3f17..187ad40385d1a5 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "2.3.0", + "version": "2.4.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 6b0cbc7e708720..d8b9d21b64dd4e 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "2.0.1", + "version": "2.1.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 6cc31704996066..4465a8dfefc496 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.4.4", + "version": "2.5.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 706ca2f9375be9..f4509975a0831b 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "3.1.0", + "version": "3.2.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 3761d551ae6d56..a4faf314c37169 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "3.0.0", + "version": "3.1.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 2c393ab45f107b..a00b6b505ab298 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.2.4", + "version": "6.3.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 4b5e80b54bee4f..a1480a4a821de0 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "2.3.0", + "version": "2.4.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index b47af61511f171..1dafbbe3e018df 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.3.1", + "version": "7.4.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/compose/package.json b/packages/compose/package.json index 42bb9c0451f496..f04e6d753ce11b 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "3.2.0", + "version": "3.3.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 6120bfd65da107..96b18edd614019 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.2.2", + "version": "2.3.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index edadb063e05182..b861d700441de3 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/custom-templated-path-webpack-plugin", - "version": "1.2.0", + "version": "1.3.0", "description": "Webpack plugin for creating custom path template tags.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/package.json b/packages/data/package.json index 056dc2ccac4e48..d0f2d74f354fc6 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.4.0", + "version": "4.5.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/package.json b/packages/date/package.json index 19c13e590a5af0..aecd12e085e93a 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "3.2.0", + "version": "3.3.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 9d3e816fcbb1a2..83fb5f8306222c 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "1.0.0-alpha.0", + "version": "1.0.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 6c07785549a05c..033c6ae472cf83 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "2.2.0", + "version": "2.3.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 99e5d8cf08a5ef..645008645fa8e0 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.1.0", + "version": "1.2.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 846b37503f3382..8213415b081db5 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "2.2.0", + "version": "2.3.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/package.json b/packages/dom/package.json index c38ab400278c1f..dbcb2b0ad335a1 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "2.2.4", + "version": "2.3.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index ef14ed67c613ca..4ab119dbc0557c 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "1.1.0", + "version": "2.0.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 302aa7eaf57fb3..e4b4efdc5ea03a 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.1.4", + "version": "1.2.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index d8e13fcc07f6d7..74a706fffc4dc1 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.3.4", + "version": "3.4.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 8dc27a6ef49922..d92d4a66c78068 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "0.2.0", + "version": "0.3.0", "private": true, "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", diff --git a/packages/editor/package.json b/packages/editor/package.json index cd617a40043f3f..af327112ade9ea 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.2.4", + "version": "9.3.0", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/element/package.json b/packages/element/package.json index dbeb72f81ca1a6..e67347a38cf9a3 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "2.3.0", + "version": "2.4.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 98e790cec29fa7..a6687ebeb34a5a 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "1.2.0", + "version": "1.3.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index e98733a8eea1ae..736b49aa1bcccd 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "2.1.0", + "version": "2.2.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 902632f548951e..66969a279a6e45 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.4.4", + "version": "1.5.0", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index c963823e18b342..4f8a280c48dd87 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "2.2.0", + "version": "2.3.0", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index f5f4225d8ed7f8..f395b10b352477 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "2.2.0", + "version": "2.3.0", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 0b735aab1cdc33..bdf92ce2757d52 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "3.3.0", + "version": "3.4.0", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index e58d9c72f9ca83..af3f44c51a85f3 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "1.2.0", + "version": "1.3.0", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index d939ed060ea040..6ea01b687f2701 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-console", - "version": "3.0.0", + "version": "3.1.0", "description": "Custom Jest matchers for the Console object.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index 27bff3b1f4668a..9f5eeeaf8117c5 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "4.0.0", + "version": "4.1.0", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 1ce6c86de38d25..3370fa4cb9b3a6 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-puppeteer-axe", - "version": "1.0.0", + "version": "1.1.0", "description": "Axe API integration with Jest and Puppeteer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 5d20592b2a4acb..5e6aa798c0aa85 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.2.0", + "version": "2.3.0", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index 66537bc38c1b76..d9fc8d12afc763 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "1.1.0", + "version": "1.2.0", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 7e9fac07aefc22..97efa2fbdf6552 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.3.4", + "version": "1.4.0", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/package.json b/packages/notices/package.json index 6bee5fac8c021d..237966556163a0 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.3.0", + "version": "1.4.0", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index 5e3a1a1af40c6b..885a8a5b66bba8 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "1.2.0", + "version": "1.3.0", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index c3b79b7d53fe08..4102efd88c26b3 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.2.4", + "version": "3.3.0", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 17bd04bcf6bfbf..5e93869577524f 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "2.2.0", + "version": "2.3.0", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index a23e92af7a41da..81dece9744c76e 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-themes", - "version": "2.0.0", + "version": "2.1.0", "description": "PostCSS plugin to generate theme colors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index e047551f10a352..b3d6eefbb973af 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/priority-queue", - "version": "1.1.0", + "version": "1.2.0", "description": "Generic browser priority queue.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 89abe7b9765cad..a8cdace21091e8 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "3.2.0", + "version": "3.3.0", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index b5920283fb5939..304d7fdb78b6de 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.2.2", + "version": "3.3.0", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index a2fb1460f2db9a..b20e731f753403 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "3.1.0", + "version": "3.2.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 48bc6842158a4e..490d8789ed1974 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/shortcode", - "version": "2.2.0", + "version": "2.3.0", "description": "Shortcode module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 6feee5603461e8..5d14fab2f65321 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "1.2.0", + "version": "1.3.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/package.json b/packages/url/package.json index cc3dc9b5490ad6..de69e9363e7a3b 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "2.5.0", + "version": "2.6.0", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 94b0e834054ddb..495f6bb81fafc4 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.3.0", + "version": "2.4.0", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index 0b609c5bdf2f5d..af4448b1d46f53 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "2.2.0", + "version": "2.3.0", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From cc6d90e0fcc1e77a0cfc179ecf3016b7149a2133 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Tue, 21 May 2019 13:27:14 +0200 Subject: [PATCH 151/664] Add release date and version to CHANGELOG files --- packages/autop/CHANGELOG.md | 2 +- packages/babel-plugin-makepot/CHANGELOG.md | 2 +- packages/babel-preset-default/CHANGELOG.md | 4 ++-- packages/components/CHANGELOG.md | 6 +++--- packages/data/CHANGELOG.md | 2 +- packages/dependency-extraction-webpack-plugin/CHANGELOG.md | 2 +- packages/docgen/CHANGELOG.md | 4 ++-- packages/e2e-test-utils/CHANGELOG.md | 2 +- packages/edit-post/CHANGELOG.md | 4 ++-- packages/eslint-plugin/CHANGELOG.md | 2 +- packages/jest-puppeteer-axe/CHANGELOG.md | 4 ++-- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/autop/CHANGELOG.md b/packages/autop/CHANGELOG.md index 20ffac25626560..0f40e07f861653 100644 --- a/packages/autop/CHANGELOG.md +++ b/packages/autop/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 2.3.0 (2019-05-21) ### Bug Fix diff --git a/packages/babel-plugin-makepot/CHANGELOG.md b/packages/babel-plugin-makepot/CHANGELOG.md index e6518a0d859e41..1ddb1f2212294d 100644 --- a/packages/babel-plugin-makepot/CHANGELOG.md +++ b/packages/babel-plugin-makepot/CHANGELOG.md @@ -1,4 +1,4 @@ -## v2.2.0 (2019-03-06) +## 2.2.0 (2019-03-06) ### Bug Fix diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index e6e939549f026f..4f214688f24805 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 4.2.0 (2019-05-21) ### New Features @@ -7,7 +7,7 @@ ## 4.0.0 (2019-03-06) -### Breaking Change +### Breaking Changes - Removed `babel-core` dependency acting as Babel 7 bridge ([#13922](https://github.com/WordPress/gutenberg/pull/13922). Ensure all references to `babel-core` are replaced with `@babel/core` . - Preset updated to include `@wordpress/babel-plugin-import-jsx-pragma` plugin integration ([#13540](https://github.com/WordPress/gutenberg/pull/13540)). It should no longer be explicitly included in your Babel config. diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d73cd8dbfb85ae..729769e7a872c3 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,10 +1,10 @@ -## Master +## 7.4.0 (2019-05-21) -### New Features +### New Feature - Added a new `HorizontalRule` component. -### Bug Fixes +### Bug Fix - Fixed display of reset button when using RangeControl `allowReset` prop. - Fixed minutes field of `DateTimePicker` missed '0' before single digit values. diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 79cce308253eed..533101d02e8a87 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 4.5.0 (2019-05-21) ### Bug Fix diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 46c5eb68883748..75096e5129006c 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 1.0.0 (2019-05-21) ### New Feature diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 943d22d361ba0d..2415b0ad876f6c 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,6 +1,6 @@ -## Master +## 1.2.0 (2019-05-21) -### Enhancements +### Enhancement - Docblocks including a `@private` tag will be omitted from the generated result. diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index 1b6edf2340b684..df00ecbe7c0ce2 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 2.0.0 (2019-05-21) ### Requirements diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 0f2ad4fd0eb853..55d3affac68770 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,6 +1,6 @@ -## Master +## 3.4.0 (2019-05-21) -### New Features +### New Feature - Implement the `addToGallery` option in the `MediaUpload` hook. The option allows users to open the media modal in the `gallery-library`instead of `gallery-edit` state. diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 1984bc67569bf0..2f66b8cab95140 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## x.x.x (Unreleased) +## 2.2.0 (2019-05-21) ### New Features diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md index c8e5368f48496e..1d1fe3ca36aeb7 100644 --- a/packages/jest-puppeteer-axe/CHANGELOG.md +++ b/packages/jest-puppeteer-axe/CHANGELOG.md @@ -1,6 +1,6 @@ -## Unreleased +## 1.1.0 (2019-05-21) -### New features +### New Feature - Added optional `disabledRules` option to use with `toPassAxeTests` matcher. From 8960509d97f72bf4bee68e1513a6c3fd3b0d446e Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Tue, 21 May 2019 16:38:17 +0300 Subject: [PATCH 152/664] Adds a new option to core/editor to disable code editing (#14932) * adds a new option to core/editor to disable code editing * disabled the switching KBD shortcut when codeEditing is disabled * updates block toggle tests * adds default true for code editing, adds test for code editor disabled, some code cleanup * adds overriding the editor mode preference based on editor settings serverd by server * adds documentation for two options which can be set by a WP plugin * Update docs/designers-developers/developers/filters/editor-filters.md Co-Authored-By: draganescu <me@andreidraganescu.info> * Update docs/designers-developers/developers/filters/editor-filters.md Co-Authored-By: draganescu <me@andreidraganescu.info> * Update docs/designers-developers/developers/filters/editor-filters.md Co-Authored-By: draganescu <me@andreidraganescu.info> * Update docs/designers-developers/developers/filters/editor-filters.md Co-Authored-By: draganescu <me@andreidraganescu.info> * Update docs/designers-developers/developers/filters/editor-filters.md doc edit Co-Authored-By: Pascal Birchler <pascal.birchler@gmail.com> * undo the ignore preference --- .../developers/filters/editor-filters.md | 22 ++++++++++++++++++- .../block-settings-menu/block-mode-toggle.js | 6 +++-- .../test/block-mode-toggle.js | 12 ++++++++++ .../components/header/mode-switcher/index.js | 3 ++- .../components/keyboard-shortcuts/index.js | 18 +++++++++------ packages/editor/src/store/defaults.js | 2 ++ 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/docs/designers-developers/developers/filters/editor-filters.md b/docs/designers-developers/developers/filters/editor-filters.md index 9421d7cd688019..16c30fc3179aae 100644 --- a/docs/designers-developers/developers/filters/editor-filters.md +++ b/docs/designers-developers/developers/filters/editor-filters.md @@ -1,5 +1,4 @@ # Editor Filters (Experimental) - To modify the behavior of the editor experience, the following Filters are exposed: ### `editor.PostFeaturedImage.imageSize` @@ -30,3 +29,24 @@ var customPreviewMessage = function() { wp.hooks.addFilter( 'editor.PostPreview.interstitialMarkup', 'my-plugin/custom-preview-message', customPreviewMessage ); ``` +## Editor settings + +### `block_editor_settings` +This is a PHP filter which is applied before sending settings to the WordPress block editor. + +You may find details about this filter [on its WordPress Code Reference page](https://developer.wordpress.org/reference/hooks/block_editor_settings/). + +The filter will send any setting to the initialized Editor, which means any editor setting that is used to configure the editor at initialisation can be filtered by a PHP WordPress plugin before being sent. + +### Available default editor settings + +#### `richEditingEnabled` +If it is `true` the user can edit the content using the Visual Editor. + +It is set by default to the return value of the [`user_can_richedit`](https://developer.wordpress.org/reference/functions/user_can_richedit/) function. It checks if the user can access the Visual Editor and whether it’s supported by the user’s browser. + + +#### `codeEditingEnabled` +Default `true`. Indicates whether the user can access the Code Editor **in addition** to the Visual Editor. + +If set to false the user will not be able to switch between Visual and Code editor. The option in the settings menu will not be available and the keyboard shortcut for switching editor types will not fire. diff --git a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js index 3f8260d135e441..99de4ddf40e445 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js +++ b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js @@ -12,8 +12,8 @@ import { getBlockType, hasBlockSupport } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -export function BlockModeToggle( { blockType, mode, onToggleMode, small = false } ) { - if ( ! hasBlockSupport( blockType, 'html', true ) ) { +export function BlockModeToggle( { blockType, mode, onToggleMode, small = false, isCodeEditingEnabled = true } ) { + if ( ! hasBlockSupport( blockType, 'html', true ) || ! isCodeEditingEnabled ) { return null; } @@ -36,10 +36,12 @@ export default compose( [ withSelect( ( select, { clientId } ) => { const { getBlock, getBlockMode } = select( 'core/block-editor' ); const block = getBlock( clientId ); + const isCodeEditingEnabled = select( 'core/editor' ).getEditorSettings().codeEditingEnabled; return { mode: getBlockMode( clientId ), blockType: block ? getBlockType( block.name ) : null, + isCodeEditingEnabled, }; } ), withDispatch( ( dispatch, { onToggle = noop, clientId } ) => ( { diff --git a/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js index a8d8e321eb1547..d8bfef8771b878 100644 --- a/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js +++ b/packages/block-editor/src/components/block-settings-menu/test/block-mode-toggle.js @@ -46,4 +46,16 @@ describe( 'BlockModeToggle', () => { expect( text ).toEqual( 'Edit visually' ); } ); + + it( 'should not render the Visual mode button if code editing is disabled', () => { + const wrapper = getShallowRenderOutput( + <BlockModeToggle + blockType={ { supports: { html: true } } } + mode="html" + isCodeEditingEnabled={ false } + /> + ); + + expect( wrapper ).toBe( null ); + } ); } ); diff --git a/packages/edit-post/src/components/header/mode-switcher/index.js b/packages/edit-post/src/components/header/mode-switcher/index.js index 48537170dfa131..4cfbca192e65be 100644 --- a/packages/edit-post/src/components/header/mode-switcher/index.js +++ b/packages/edit-post/src/components/header/mode-switcher/index.js @@ -50,9 +50,10 @@ function ModeSwitcher( { onSwitch, mode } ) { export default compose( [ withSelect( ( select ) => ( { isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isCodeEditingEnabled: select( 'core/editor' ).getEditorSettings().codeEditingEnabled, mode: select( 'core/edit-post' ).getEditorMode(), } ) ), - ifCondition( ( { isRichEditingEnabled } ) => isRichEditingEnabled ), + ifCondition( ( { isRichEditingEnabled, isCodeEditingEnabled } ) => isRichEditingEnabled && isCodeEditingEnabled ), withDispatch( ( dispatch ) => ( { onSwitch( mode ) { dispatch( 'core/edit-post' ).switchEditorMode( mode ); diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index bde16d74311060..bc1815921e9551 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -20,8 +20,8 @@ class EditorModeKeyboardShortcuts extends Component { } toggleMode() { - const { mode, switchMode, isRichEditingEnabled } = this.props; - if ( ! isRichEditingEnabled ) { + const { mode, switchMode, isModeSwitchEnabled } = this.props; + if ( ! isModeSwitchEnabled ) { return; } switchMode( mode === 'visual' ? 'text' : 'visual' ); @@ -54,11 +54,15 @@ class EditorModeKeyboardShortcuts extends Component { } export default compose( [ - withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, - mode: select( 'core/edit-post' ).getEditorMode(), - isEditorSidebarOpen: select( 'core/edit-post' ).isEditorSidebarOpened(), - } ) ), + withSelect( ( select ) => { + const { richEditingEnabled, codeEditingEnabled } = select( 'core/editor' ).getEditorSettings(); + + return { + isModeSwitchEnabled: richEditingEnabled && codeEditingEnabled, + mode: select( 'core/edit-post' ).getEditorMode(), + isEditorSidebarOpen: select( 'core/edit-post' ).isEditorSidebarOpened(), + }; + } ), withDispatch( ( dispatch, ownProps, { select } ) => ( { switchMode( mode ) { dispatch( 'core/edit-post' ).switchEditorMode( mode ); diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index fb49621e8e5a64..07c92803bd0a13 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -20,6 +20,7 @@ export const INITIAL_EDITS_DEFAULTS = {}; * * allowedBlockTypes boolean|Array Allowed block types * richEditingEnabled boolean Whether rich editing is enabled or not + * codeEditingEnabled boolean Whether code editing is enabled or not * enableCustomFields boolean Whether the WordPress custom fields are enabled or not * autosaveInterval number Autosave Interval * availableTemplates array? The available post templates @@ -31,6 +32,7 @@ export const EDITOR_SETTINGS_DEFAULTS = { ...SETTINGS_DEFAULTS, richEditingEnabled: true, + codeEditingEnabled: true, enableCustomFields: false, }; From 5c34f2fe327035784c1728572c9d281c9cb53147 Mon Sep 17 00:00:00 2001 From: Yorick Brown <yisforyorick@gmail.com> Date: Tue, 21 May 2019 22:17:36 +0200 Subject: [PATCH 153/664] Make text more readable (#14839) * Make text more readable * small text fix --- .../tutorials/javascript/plugins-background.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/plugins-background.md b/docs/designers-developers/developers/tutorials/javascript/plugins-background.md index 91a6ee302fe758..b7a20750855d7f 100644 --- a/docs/designers-developers/developers/tutorials/javascript/plugins-background.md +++ b/docs/designers-developers/developers/tutorials/javascript/plugins-background.md @@ -1,8 +1,12 @@ # Plugins Background -The primary means of extending WordPress is the plugin. WordPress' [Plugin Basics](https://developer.wordpress.org/plugins/the-basics/) documentation provides for more details on building a plugin. The quickest way to start is to create a new directory in `wp-content/plugins/` to hold your plugin code, for this example you can call it `myguten-plugin`. +The primary means of extending WordPress is the plugin. The WordPress [Plugin Basics](https://developer.wordpress.org/plugins/the-basics/) documentation provides details on building a plugin. -Inside of this new directory, create a file called `myguten-plugin.php` which is the server-side code that runs when your plugin is active. For now place the following in that file: +The quickest way to start is to create a new directory in `wp-content/plugins/` to contain your plugin code. For this example, call it `myguten-plugin`. + +Inside this new directory, create a file called `myguten-plugin.php`. This is the server-side code that runs when your plugin is active. + +For now, add the following code in the file: ```php <?php @@ -11,6 +15,8 @@ Plugin Name: Fancy Quote */ ``` -To summarize, you should have a directory `wp-content/plugins/myguten-plugin/` which has the single file `myguten-plugin.php`. Once that is in place, go to your plugins list in `wp-admin` and you should see your plugin listed. +To summarize, you should have a directory `wp-content/plugins/myguten-plugin/` which has the single file `myguten-plugin.php`. + +Once that is in place, go to your plugins list in `wp-admin` and you should see your plugin listed. Click **Activate** and your plugin will load with WordPress. From 5c7f68833565d1ade09d705c870b59d58d34b629 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Wed, 22 May 2019 07:58:38 +0200 Subject: [PATCH 154/664] Remove @noisysocks from CONTRIBUTORS (#15768) --- .github/CODEOWNERS | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5b6279ebafdc75..53702a4b85c26d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,22 +5,22 @@ # Data /packages/api-fetch @youknowriad @aduth @nerrad @mmtr -/packages/core-data @youknowriad @gziolo @aduth @nerrad @noisysocks +/packages/core-data @youknowriad @gziolo @aduth @nerrad /packages/data @youknowriad @aduth @nerrad @coderkevin /packages/redux-routine @youknowriad @aduth @nerrad # Blocks -/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @noisysocks @notnownikki +/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @notnownikki # Editor /packages/annotations @youknowriad @aduth @atimmer @ellatrix /packages/autop @youknowriad @aduth -/packages/block-editor @youknowriad @gziolo @talldan @noisysocks @ellatrix +/packages/block-editor @youknowriad @gziolo @talldan @ellatrix /packages/block-serialization-spec-parser @youknowriad @gziolo @aduth @dmsnell /packages/block-serialization-default-parser @youknowriad @gziolo @aduth @dmsnell -/packages/blocks @youknowriad @gziolo @aduth @noisysocks @ellatrix -/packages/edit-post @youknowriad @talldan @noisysocks -/packages/editor @youknowriad @talldan @noisysocks +/packages/blocks @youknowriad @gziolo @aduth @ellatrix +/packages/edit-post @youknowriad @talldan +/packages/editor @youknowriad @talldan /packages/list-reusable-blocks @youknowriad @aduth @noisysocks /packages/shortcode @youknowriad @aduth @@ -48,12 +48,12 @@ /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz # UI Components -/packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks @chrisvanpatten -/packages/compose @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/element @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/notices @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @chrisvanpatten +/packages/compose @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/element @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/notices @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan /packages/nux @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/viewport @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/viewport @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan # Utilities /packages/a11y @youknowriad @gziolo @aduth From 7f419f548b0bb6663e74bccad8018805d46d8dcd Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascal.birchler@gmail.com> Date: Wed, 22 May 2019 09:07:26 +0200 Subject: [PATCH 155/664] Add missing file entry for util.js (#15756) * Add missing file entry for util.js * Update CHANGELOG.md --- packages/dependency-extraction-webpack-plugin/CHANGELOG.md | 6 ++++++ packages/dependency-extraction-webpack-plugin/package.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 75096e5129006c..2450de3fc38d48 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.1 (2019-05-21) + +### Bug Fixes + +- Fix missing file entry for `util.js` in `package.json` + ## 1.0.0 (2019-05-21) ### New Feature diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 83fb5f8306222c..051dce9407814a 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -19,7 +19,8 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "files": [ - "index.js" + "index.js", + "util.js" ], "main": "index.js", "dependencies": { From c9bb61205c23f4a1b703d130e9122939ebaf2411 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Wed, 22 May 2019 09:14:12 +0200 Subject: [PATCH 156/664] chore(release): publish - @wordpress/dependency-extraction-webpack-plugin@1.0.1 - @wordpress/e2e-tests@1.2.1 - @wordpress/scripts@3.2.1 --- packages/dependency-extraction-webpack-plugin/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/scripts/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 051dce9407814a..89f7693337577b 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "1.0.0", + "version": "1.0.1", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index e4b4efdc5ea03a..33943fda4cc2fe 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.2.0", + "version": "1.2.1", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index b20e731f753403..6da22a30e26511 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "3.2.0", + "version": "3.2.1", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 9ce596cd568d30c76fd4a0257e2872da91d4966a Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Wed, 22 May 2019 09:26:29 +0200 Subject: [PATCH 157/664] Add release date and version to CHANGELOG file --- packages/dependency-extraction-webpack-plugin/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md index 2450de3fc38d48..3fe196eef7dc43 100644 --- a/packages/dependency-extraction-webpack-plugin/CHANGELOG.md +++ b/packages/dependency-extraction-webpack-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.0.1 (2019-05-21) +## 1.0.1 (2019-05-22) ### Bug Fixes From f2cafbd39ed0e905980e6ed2d151e0747aa72151 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Wed, 22 May 2019 06:33:41 -0400 Subject: [PATCH 158/664] Correct the class name for the latest posts excerpt. (#15758) On the front end only. --- packages/block-library/src/latest-posts/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index e8cd3c6fb66122..d90f692644bacb 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -59,7 +59,7 @@ function render_block_core_latest_posts( $attributes ) { $trimmed_excerpt = esc_html( wp_trim_words( $post_excerpt, $excerpt_length, ' &hellip; ' ) ); $list_items_markup .= sprintf( - '<div class="wp-block-latest-posts____post-excerpt">%1$s', + '<div class="wp-block-latest-posts__post-excerpt">%1$s', $trimmed_excerpt ); From 884b3f9e588246f40e13d01084655aed5564bd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 22 May 2019 14:04:52 +0200 Subject: [PATCH 159/664] Testing: Speed up Docker setup on Travis for e2e tests (#15748) * Testing: Speed up Docker setup on Travis for e2e tests * Testing: Speed up Docker setup on Travis for PHP tests * Testing: Add back npm run build to jobs which run PHP unit tests * Revert Docker service name change for wordpress --- .travis.yml | 35 +++++------------ bin/install-docker.sh | 13 ------- bin/install-wordpress.sh | 2 +- ...-e2e-tests.sh => reset-local-e2e-tests.sh} | 0 bin/run-wp-unit-tests.sh | 38 ++++++++++++++----- bin/setup-local-env.sh | 13 +++++++ bin/setup-travis-e2e-tests.sh | 28 ++++++++++++++ docker-compose-localdev.yml | 2 +- package.json | 2 +- 9 files changed, 82 insertions(+), 51 deletions(-) rename bin/{reset-e2e-tests.sh => reset-local-e2e-tests.sh} (100%) create mode 100755 bin/setup-travis-e2e-tests.sh diff --git a/.travis.yml b/.travis.yml index 02b57e11c88e60..5fa24d4faf998c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,39 +23,31 @@ branches: - master before_install: - - nvm install + - nvm install --latest-npm env: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true jobs: include: - name: Lint - before_install: - - nvm install --latest-npm install: - npm ci script: - npm run lint - name: Build artifacts - before_install: - - nvm install --latest-npm install: - npm ci script: - npm run check-local-changes - name: License compatibility - before_install: - - nvm install --latest-npm install: - npm ci script: - npm run check-licenses - name: JavaScript unit tests - before_install: - - nvm install --latest-npm install: - npm ci script: @@ -92,72 +84,64 @@ jobs: - name: E2E tests (Admin with plugins) (1/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (3/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (4/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (3/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (4/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - - ./bin/setup-local-env.sh + - ./bin/setup-travis-e2e-tests.sh script: - - npm run build - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) @@ -172,4 +156,3 @@ jobs: env: WP_VERSION=latest SWITCH_TO_PHP=5.2 script: - ./bin/run-wp-unit-tests.sh - diff --git a/bin/install-docker.sh b/bin/install-docker.sh index 56bb4ad3ef1c6f..d6a10c498f0420 100755 --- a/bin/install-docker.sh +++ b/bin/install-docker.sh @@ -31,16 +31,3 @@ docker-compose $DOCKER_COMPOSE_FILE_OPTIONS pull # Launch the containers. echo -e $(status_message "Starting Docker containers...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS up -d >/dev/null - -# Set up WordPress Development site. -# Note: we don't bother installing the test site right now, because that's -# done on every time `npm run test-e2e` is run. -. "$(dirname "$0")/install-wordpress.sh" - -# Install the PHPUnit test scaffolding. -echo -e $(status_message "Installing PHPUnit test scaffolding...") -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm wordpress_phpunit /app/bin/install-wp-tests.sh wordpress_test root example mysql $WP_VERSION false > /dev/null - -# Install Composer. This is only used to run WordPress Coding Standards checks. -echo -e $(status_message "Installing and updating Composer modules...") -docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm composer install diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index 8d20d91e3a8845..2b7b1ebf84b393 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -17,7 +17,7 @@ SITE_TITLE='Gutenberg Dev' # If we're installing/re-installing the test site, change the containers used. if [ "$1" == '--e2e_tests' ]; then CLI="${CLI}_e2e_tests" - CONTAINER="${CONTAINER}_e2e_tests" + CONTAINER='wordpress_e2e_tests' SITE_TITLE='Gutenberg Testing' if ! docker ps | grep -q $CONTAINER; then diff --git a/bin/reset-e2e-tests.sh b/bin/reset-local-e2e-tests.sh similarity index 100% rename from bin/reset-e2e-tests.sh rename to bin/reset-local-e2e-tests.sh diff --git a/bin/run-wp-unit-tests.sh b/bin/run-wp-unit-tests.sh index 8e1ee81db79e3d..2e5dc5b0809146 100755 --- a/bin/run-wp-unit-tests.sh +++ b/bin/run-wp-unit-tests.sh @@ -1,26 +1,46 @@ #!/usr/bin/env bash +# Exit if any command fails +set -e + +npm ci + +npm run build # Set up environment variables . "$(dirname "$0")/bootstrap-env.sh" +# Include useful functions +. "$(dirname "$0")/includes.sh" + cd "$(dirname "$0")/../" export PATH="$HOME/.composer/vendor/bin:$PATH" if [[ $DOCKER = "true" ]]; then - bin/setup-local-env.sh + # Download image updates. + echo -e $(status_message "Downloading Docker image updates...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS pull composer mysql wordpress_phpunit + + # Launch the containers. + echo -e $(status_message "Starting Docker containers...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS up -d composer mysql wordpress_phpunit >/dev/null + + # Install the PHPUnit test scaffolding. + echo -e $(status_message "Installing PHPUnit test scaffolding...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm wordpress_phpunit /app/bin/install-wp-tests.sh wordpress_test root example mysql $WP_VERSION false > /dev/null + + # Install Composer. This is only used to run WordPress Coding Standards checks. + echo -e $(status_message "Installing and updating Composer modules...") + docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm composer install else bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION source bin/install-php-phpunit.sh # Run the build because otherwise there will be a bunch of warnings about # failed `stat` calls from `filemtime()`. - composer install || exit 1 - npm install || exit 1 + composer install fi -npm run build || exit 1 - echo Running with the following versions: if [[ $DOCKER = "true" ]]; then docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm wordpress_phpunit php -v @@ -32,9 +52,9 @@ fi # Run PHPUnit tests if [[ $DOCKER = "true" ]]; then - npm run test-php || exit 1 - npm run test-unit-php-multisite || exit 1 + npm run test-php + npm run test-unit-php-multisite else - phpunit || exit 1 - WP_MULTISITE=1 phpunit || exit 1 + phpunit + WP_MULTISITE=1 phpunit fi diff --git a/bin/setup-local-env.sh b/bin/setup-local-env.sh index dcc86df34a279d..f618780e2d8c19 100755 --- a/bin/setup-local-env.sh +++ b/bin/setup-local-env.sh @@ -18,6 +18,19 @@ cd "$(dirname "$0")/.." # Check Docker is installed and running . "$(dirname "$0")/install-docker.sh" +# Set up WordPress Development site. +# Note: we don't bother installing the test site right now, because that's +# done on every time `npm run test-e2e` is run. +. "$(dirname "$0")/install-wordpress.sh" + +# Install the PHPUnit test scaffolding. +echo -e $(status_message "Installing PHPUnit test scaffolding...") +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm wordpress_phpunit /app/bin/install-wp-tests.sh wordpress_test root example mysql $WP_VERSION false > /dev/null + +# Install Composer. This is only used to run WordPress Coding Standards checks. +echo -e $(status_message "Installing and updating Composer modules...") +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm composer install + ! read -d '' GUTENBERG <<"EOT" ,⁻⁻⁻· . | | ،⁓’. . |--- ,---. ,---. |---. ,---. ,---. ,---. diff --git a/bin/setup-travis-e2e-tests.sh b/bin/setup-travis-e2e-tests.sh new file mode 100755 index 00000000000000..0ebc8bc1e6dff3 --- /dev/null +++ b/bin/setup-travis-e2e-tests.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Exit if any command fails +set -e + +npm ci + +npm run build + +# Set up environment variables +. "$(dirname "$0")/bootstrap-env.sh" + +# Include useful functions +. "$(dirname "$0")/includes.sh" + +# Change to the expected directory +cd "$(dirname "$0")/.." + +# Download image updates. +echo -e $(status_message "Downloading Docker image updates...") +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS pull mysql wordpress_e2e_tests cli_e2e_tests + +# Launch the containers. +echo -e $(status_message "Starting Docker containers...") +docker-compose $DOCKER_COMPOSE_FILE_OPTIONS up -d --remove-orphans mysql wordpress_e2e_tests cli_e2e_tests >/dev/null + +# Set up WordPress Development site. +. "$(dirname "$0")/install-wordpress.sh" --e2e_tests diff --git a/docker-compose-localdev.yml b/docker-compose-localdev.yml index 4d58031eaa74be..502132973afbcd 100644 --- a/docker-compose-localdev.yml +++ b/docker-compose-localdev.yml @@ -2,7 +2,7 @@ version: '3.1' services: - wordpress: + wordpress_dev: volumes: - ./wordpress:/var/www/html diff --git a/package.json b/package.json index 8b0a3453903d4b..c9474bcfdc3e58 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "publish:dev": "npm run build:packages && lerna publish --npm-tag next", "publish:prod": "npm run build:packages && lerna publish", "test": "npm run lint && npm run test-unit", - "pretest-e2e": "cross-env SCRIPT_DEBUG=false ./bin/reset-e2e-tests.sh", + "pretest-e2e": "cross-env SCRIPT_DEBUG=false ./bin/reset-local-e2e-tests.sh", "test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js", "test-e2e:watch": "npm run test-e2e -- --watch", "test-php": "npm run lint-php && npm run test-unit-php", From 5564e8579417189b4f8e4a78b0da9173bbc711d0 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Wed, 22 May 2019 08:27:59 -0400 Subject: [PATCH 160/664] Provide margins to Latest Posts excerpts (#15760) * Provide margins to Latest Posts block content * Provide these only to excerpts for now. * Be less specific about the excerpt margin rule. * Move margins to style.css --- packages/block-library/src/latest-posts/editor.scss | 1 + packages/block-library/src/latest-posts/style.scss | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/block-library/src/latest-posts/editor.scss b/packages/block-library/src/latest-posts/editor.scss index 3442630f42486e..a8a538967a3e82 100644 --- a/packages/block-library/src/latest-posts/editor.scss +++ b/packages/block-library/src/latest-posts/editor.scss @@ -4,6 +4,7 @@ padding-left: 0; } } + .wp-block-latest-posts li a > div { display: inline; } diff --git a/packages/block-library/src/latest-posts/style.scss b/packages/block-library/src/latest-posts/style.scss index a72331d6eed09f..9bfbbad503e964 100644 --- a/packages/block-library/src/latest-posts/style.scss +++ b/packages/block-library/src/latest-posts/style.scss @@ -35,3 +35,8 @@ color: $dark-gray-300; font-size: $default-font-size; } + +.wp-block-latest-posts__post-excerpt { + margin-top: $grid-size; + margin-bottom: $grid-size-large; +} From 084e92b0062cc59e2b80bae87be578234ad6e5f5 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Wed, 22 May 2019 09:09:06 -0400 Subject: [PATCH 161/664] Packages: Add new @wordpress/data-controls package. (#15435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new @wordpress/data-controls package * include new package in main package.json * Implement new package with @wordpress/editor store - this might be extracted to its own pull but added initially for testing. * docs tool generating this for some reason * include new package with doc generation tool * updated manifest from doc generation * updated package-lock.json * not sure why docs build tool is doing this. * use ternary * data-controls added to manifest by docs build tool * update README for editor package * updates to editor store actions tests - to use new data-controls package * add missing `@example` tag and regenerate docs * add tests for package * fix typo in changelog for package name * fix typo for package name in readme * more typos * Use lower version for initial release. Co-Authored-By: Grzegorz (Greg) Ziółkowski <grzegorz@gziolo.pl> * Add heading for API. Co-Authored-By: Grzegorz (Greg) Ziółkowski <grzegorz@gziolo.pl> * Update CHANGELOG.md version reference. Follows repo conventions about using Master as the version heading for unreleased changes. * add data-controls to webpack plugin config * Revert "add data-controls to webpack plugin config" This reverts commit 36a0b98acc6d81e2921c09db5e20c1ad68e28e47. * use named export for controls object * update docs * woop fix import on tests --- bin/update-readmes.js | 1 + docs/manifest-devhub.json | 6 + docs/manifest.json | 6 + package-lock.json | 8 + package.json | 1 + packages/data-controls/CHANGELOG.md | 3 + packages/data-controls/README.md | 136 +++++++++++++++++ packages/data-controls/package.json | 31 ++++ packages/data-controls/src/index.js | 171 ++++++++++++++++++++++ packages/data-controls/src/test/index.js | 146 ++++++++++++++++++ packages/editor/CHANGELOG.md | 1 + packages/editor/package.json | 1 + packages/editor/src/store/actions.js | 29 ++-- packages/editor/src/store/controls.js | 113 -------------- packages/editor/src/store/index.js | 2 +- packages/editor/src/store/test/actions.js | 33 +++-- 16 files changed, 539 insertions(+), 149 deletions(-) create mode 100644 packages/data-controls/CHANGELOG.md create mode 100644 packages/data-controls/README.md create mode 100644 packages/data-controls/package.json create mode 100644 packages/data-controls/src/index.js create mode 100644 packages/data-controls/src/test/index.js delete mode 100644 packages/editor/src/store/controls.js diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 63d041e7ab5098..7d96e68adce03b 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -17,6 +17,7 @@ const packages = [ 'Autogenerated selectors': 'src/selectors.js', } ], 'data', + 'data-controls', 'date', 'deprecated', 'dom', diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 09544a23854c3e..2095e773664bb8 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -1079,6 +1079,12 @@ "markdown_source": "../packages/custom-templated-path-webpack-plugin/README.md", "parent": "packages" }, + { + "title": "@wordpress/data-controls", + "slug": "packages-data-controls", + "markdown_source": "../packages/data-controls/README.md", + "parent": "packages" + }, { "title": "@wordpress/data", "slug": "packages-data", diff --git a/docs/manifest.json b/docs/manifest.json index 594d3504a50dcd..429fd5bc835ae2 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -287,6 +287,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/custom-templated-path-webpack-plugin/README.md", "parent": "packages" }, + { + "title": "@wordpress/data-controls", + "slug": "packages-data-controls", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/data-controls/README.md", + "parent": "packages" + }, { "title": "@wordpress/data", "slug": "packages-data", diff --git a/package-lock.json b/package-lock.json index 787fab54e81bee..2b1826f94d9290 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3107,6 +3107,13 @@ "turbo-combine-reducers": "^1.0.2" } }, + "@wordpress/data-controls": { + "version": "file:packages/data-controls", + "requires": { + "@wordpress/api-fetch": "file:packages/api-fetch", + "@wordpress/data": "file:packages/data" + } + }, "@wordpress/date": { "version": "file:packages/date", "requires": { @@ -3245,6 +3252,7 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/date": "file:packages/date", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/element": "file:packages/element", diff --git a/package.json b/package.json index c9474bcfdc3e58..f43c1e4cf08645 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/date": "file:packages/date", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md new file mode 100644 index 00000000000000..dff204f06c92d6 --- /dev/null +++ b/packages/data-controls/CHANGELOG.md @@ -0,0 +1,3 @@ +# Master + +Initial release of the @wordpress/data-controls package. diff --git a/packages/data-controls/README.md b/packages/data-controls/README.md new file mode 100644 index 00000000000000..08b822fe67967d --- /dev/null +++ b/packages/data-controls/README.md @@ -0,0 +1,136 @@ +# Data Controls + +The data controls module is a module intended to simplify implementation of common controls used with the [`@wordpress/data`](/packages/data/README.md) package. + +**Note:** It is assumed that the registry being used has the controls plugin enabled on it (see [more details on controls here](https://github.com/WordPress/gutenberg/tree/master/packages/data#controls)) + +## Installation + +Install the module + +```bash +npm install @wordpress/data-controls --save +``` + + _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +The following controls are available on the object returned by the module: + +## API + +<!-- START TOKEN(Autogenerated API docs) --> + +<a name="apiFetch" href="#apiFetch">#</a> **apiFetch** + +Dispatches a control action for triggering an api fetch call. + +_Usage_ + +```js +import { apiFetch } from '@wordpress/data-controls'; + +// Action generator using apiFetch +export function* myAction { + const path = '/v2/my-api/items'; + const items = yield apiFetch( { path } ); + // do something with the items. +} +``` + +_Parameters_ + +- _request_ `Object`: Arguments for the fetch request. + +_Returns_ + +- `Object`: The control descriptor. + +<a name="controls" href="#controls">#</a> **controls** + +The default export is what you use to register the controls with your custom +store. + +_Usage_ + +```js +// WordPress dependencies +import { controls } from '@wordpress/data-controls'; +import { registerStore } from '@wordpress/data'; + +// Internal dependencies +import reducer from './reducer'; +import * as selectors from './selectors'; +import * as actions from './actions'; +import * as resolvers from './resolvers'; + +registerStore ( 'my-custom-store', { + reducer, + controls, + actions, + selectors, + resolvers, +} ); +``` + +_Returns_ + +- `Object`: An object for registering the default controls with the store. + +<a name="dispatch" href="#dispatch">#</a> **dispatch** + +Dispatches a control action for triggering a registry dispatch. + +_Usage_ + +```js +import { dispatch } from '@wordpress/data-controls'; + +// Action generator using dispatch +export function* myAction { + yield dispatch( 'core/edit-post' ).togglePublishSidebar(); + // do some other things. +} +``` + +_Parameters_ + +- _storeKey_ `string`: The key for the store the action belongs to +- _actionName_ `string`: The name of the action to dispatch +- _args_ `Array`: Arguments for the dispatch action. + +_Returns_ + +- `Object`: The control descriptor. + +<a name="select" href="#select">#</a> **select** + +Dispatches a control action for triggering a registry select. + +Note: when this control action is handled, it automatically considers +selectors that may have a resolver. It will await and return the resolved +value when the selector has not been resolved yet. + +_Usage_ + +```js +import { select } from '@wordpress/data-controls'; + +// Action generator using select +export function* myAction { + const isSidebarOpened = yield select( 'core/edit-post', 'isEditorSideBarOpened' ); + // do stuff with the result from the select. +} +``` + +_Parameters_ + +- _storeKey_ `string`: The key for the store the selector belongs to +- _selectorName_ `string`: The name of the selector +- _args_ `Array`: Arguments for the select. + +_Returns_ + +- `Object`: The control descriptor. + + +<!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json new file mode 100644 index 00000000000000..9ffbf80cd5165e --- /dev/null +++ b/packages/data-controls/package.json @@ -0,0 +1,31 @@ +{ + "name": "@wordpress/data-controls", + "version": "1.0.0-beta.1", + "description": "A set of common controls for the @wordpress/data api.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "data", + "controls" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/data-controls/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/data-controls" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/data": "file:../data" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/data-controls/src/index.js b/packages/data-controls/src/index.js new file mode 100644 index 00000000000000..c209b3397b4fad --- /dev/null +++ b/packages/data-controls/src/index.js @@ -0,0 +1,171 @@ +/** + * WordPress dependencies + */ +import triggerFetch from '@wordpress/api-fetch'; +import { createRegistryControl } from '@wordpress/data'; + +/** + * Dispatches a control action for triggering an api fetch call. + * + * @param {Object} request Arguments for the fetch request. + * + * @example + * ```js + * import { apiFetch } from '@wordpress/data-controls'; + * + * // Action generator using apiFetch + * export function* myAction { + * const path = '/v2/my-api/items'; + * const items = yield apiFetch( { path } ); + * // do something with the items. + * } + * ``` + * + * @return {Object} The control descriptor. + */ +export const apiFetch = ( request ) => { + return { + type: 'API_FETCH', + request, + }; +}; + +/** + * Dispatches a control action for triggering a registry select. + * + * Note: when this control action is handled, it automatically considers + * selectors that may have a resolver. It will await and return the resolved + * value when the selector has not been resolved yet. + * + * @param {string} storeKey The key for the store the selector belongs to + * @param {string} selectorName The name of the selector + * @param {Array} args Arguments for the select. + * + * @example + * ```js + * import { select } from '@wordpress/data-controls'; + * + * // Action generator using select + * export function* myAction { + * const isSidebarOpened = yield select( 'core/edit-post', 'isEditorSideBarOpened' ); + * // do stuff with the result from the select. + * } + * ``` + * + * @return {Object} The control descriptor. + */ +export function select( storeKey, selectorName, ...args ) { + return { + type: 'SELECT', + storeKey, + selectorName, + args, + }; +} + +/** + * Dispatches a control action for triggering a registry dispatch. + * + * @param {string} storeKey The key for the store the action belongs to + * @param {string} actionName The name of the action to dispatch + * @param {Array} args Arguments for the dispatch action. + * + * @example + * ```js + * import { dispatch } from '@wordpress/data-controls'; + * + * // Action generator using dispatch + * export function* myAction { + * yield dispatch( 'core/edit-post' ).togglePublishSidebar(); + * // do some other things. + * } + * ``` + * + * @return {Object} The control descriptor. + */ +export function dispatch( storeKey, actionName, ...args ) { + return { + type: 'DISPATCH', + storeKey, + actionName, + args, + }; +} + +/** + * Utility for returning a promise that handles a selector with a resolver. + * + * @param {Object} registry The data registry. + * @param {string} storeKey The store the selector belongs to + * @param {string} selectorName The selector name + * @param {Array} args The arguments fed to the selector + * + * @return {Promise} A promise for resolving the given selector. + */ +const resolveSelect = ( registry, { storeKey, selectorName, args } ) => { + return new Promise( ( resolve ) => { + const hasFinished = () => registry.select( 'core/data' ) + .hasFinishedResolution( storeKey, selectorName, args ); + const getResult = () => registry.select( storeKey )[ selectorName ] + .apply( null, args ); + + // trigger the selector (to trigger the resolver) + const result = getResult(); + if ( hasFinished() ) { + return resolve( result ); + } + + const unsubscribe = registry.subscribe( () => { + if ( hasFinished() ) { + unsubscribe(); + resolve( getResult() ); + } + } ); + } ); +}; + +/** + * The default export is what you use to register the controls with your custom + * store. + * + * @example + * ```js + * // WordPress dependencies + * import { controls } from '@wordpress/data-controls'; + * import { registerStore } from '@wordpress/data'; + * + * // Internal dependencies + * import reducer from './reducer'; + * import * as selectors from './selectors'; + * import * as actions from './actions'; + * import * as resolvers from './resolvers'; + * + * registerStore ( 'my-custom-store', { + * reducer, + * controls, + * actions, + * selectors, + * resolvers, + * } ); + * ``` + * + * @return {Object} An object for registering the default controls with the + * store. + */ +export const controls = { + API_FETCH( { request } ) { + return triggerFetch( request ); + }, + SELECT: createRegistryControl( + ( registry ) => ( { storeKey, selectorName, args } ) => { + return registry.select( storeKey )[ selectorName ].hasResolver ? + resolveSelect( registry, { storeKey, selectorName, args } ) : + registry.select( storeKey )[ selectorName ]( ...args ); + } + ), + DISPATCH: createRegistryControl( + ( registry ) => ( { storeKey, actionName, args } ) => { + return registry.dispatch( storeKey )[ actionName ]( ...args ); + } + ), +}; diff --git a/packages/data-controls/src/test/index.js b/packages/data-controls/src/test/index.js new file mode 100644 index 00000000000000..fcd6650352af18 --- /dev/null +++ b/packages/data-controls/src/test/index.js @@ -0,0 +1,146 @@ +/** + * WordPress dependencies + */ +import triggerFetch from '@wordpress/api-fetch'; + +jest.mock( '@wordpress/api-fetch' ); + +/** + * Internal dependencies + */ +import { controls } from '../index'; + +describe( 'controls', () => { + describe( 'API_FETCH', () => { + afterEach( () => { + triggerFetch.mockClear(); + } ); + it( 'invokes the triggerFetch function', () => { + controls.API_FETCH( { request: '' } ); + expect( triggerFetch ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'invokes the triggerFetch funcion with the passed in request', () => { + controls.API_FETCH( { request: 'foo' } ); + expect( triggerFetch ).toHaveBeenCalledWith( 'foo' ); + } ); + } ); + describe( 'SELECT', () => { + const selectorWithUndefinedResolver = jest.fn(); + const selectorWithResolver = jest.fn(); + selectorWithResolver.hasResolver = true; + const selectorWithFalseResolver = jest.fn(); + selectorWithFalseResolver.hasResolver = false; + const hasFinishedResolution = jest.fn(); + const unsubscribe = jest.fn(); + let subscribedCallback; + const registryMock = { + select: ( storeKey ) => { + const stores = { + mockStore: { + selectorWithResolver, + selectorWithUndefinedResolver, + selectorWithFalseResolver, + }, + 'core/data': { + hasFinishedResolution, + }, + }; + return stores[ storeKey ]; + }, + subscribe: jest.fn( + ( subscribeCallback ) => { + subscribedCallback = subscribeCallback; + return unsubscribe; + } + ), + }; + const getSelectorArgs = ( storeKey, selectorName, ...args ) => ( + { storeKey, selectorName, args } + ); + beforeEach( () => { + selectorWithUndefinedResolver.mockReturnValue( 'foo' ); + selectorWithFalseResolver.mockReturnValue( 'bar' ); + selectorWithResolver.mockReturnValue( 'resolved' ); + hasFinishedResolution.mockReturnValue( false ); + } ); + afterEach( () => { + selectorWithUndefinedResolver.mockClear(); + selectorWithResolver.mockClear(); + selectorWithFalseResolver.mockClear(); + hasFinishedResolution.mockClear(); + unsubscribe.mockClear(); + } ); + it( 'invokes selector with undefined resolver', () => { + const testControl = controls.SELECT( registryMock ); + const value = testControl( getSelectorArgs( + 'mockStore', + 'selectorWithUndefinedResolver' + ) ); + expect( value ).toBe( 'foo' ); + expect( selectorWithUndefinedResolver ).toHaveBeenCalled(); + expect( hasFinishedResolution ).not.toHaveBeenCalled(); + } ); + it( 'invokes selector with resolver set to false', () => { + const testControl = controls.SELECT( registryMock ); + const value = testControl( getSelectorArgs( + 'mockStore', + 'selectorWithFalseResolver' + ) ); + expect( value ).toBe( 'bar' ); + expect( selectorWithFalseResolver ).toHaveBeenCalled(); + expect( hasFinishedResolution ).not.toHaveBeenCalled(); + } ); + describe( 'invokes selector with resolver set to true', () => { + const testControl = controls.SELECT( registryMock ); + it( 'returns a promise', () => { + const value = testControl( getSelectorArgs( + 'mockStore', + 'selectorWithResolver' + ) ); + expect( value ).toBeInstanceOf( Promise ); + expect( hasFinishedResolution ).toHaveBeenCalled(); + expect( registryMock.subscribe ).toHaveBeenCalled(); + } ); + it( 'selector with resolver resolves to expected result when ' + + 'finished', async () => { + const value = testControl( getSelectorArgs( + 'mockStore', + 'selectorWithResolver' + ) ); + hasFinishedResolution.mockReturnValue( true ); + subscribedCallback(); + await expect( value ).resolves.toBe( 'resolved' ); + expect( unsubscribe ).toHaveBeenCalled(); + } ); + } ); + } ); + describe( 'DISPATCH', () => { + const mockDispatch = jest.fn(); + const registryMock = { + dispatch: ( storeKey ) => { + const stores = { + mockStore: { + mockDispatch, + }, + }; + return stores[ storeKey ]; + }, + }; + beforeEach( () => { + mockDispatch.mockReturnValue( 'foo' ); + } ); + afterEach( () => { + mockDispatch.mockClear(); + } ); + it( 'invokes dispatch action', () => { + const testControl = controls.DISPATCH( registryMock ); + const value = testControl( { + storeKey: 'mockStore', + actionName: 'mockDispatch', + args: [], + } ); + expect( value ).toBe( 'foo' ); + expect( mockDispatch ).toHaveBeenCalled(); + } ); + } ); +} ); diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index da86f36a1209a0..d37f22323ec95e 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -8,6 +8,7 @@ - Refactor setupEditor effects to action-generator using controls ([#14513](https://github.com/WordPress/gutenberg/pull/14513)) - Remove redux-multi dependency (no longer needed/used with above refactor) +- Replace internal controls definitions with usage of new @wordpress/data-controls package (see [#15435](https://github.com/WordPress/gutenberg/pull/15435) ## 9.1.0 (2019-03-06) diff --git a/packages/editor/package.json b/packages/editor/package.json index af327112ade9ea..74384ff0ce3900 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -30,6 +30,7 @@ "@wordpress/compose": "file:../compose", "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", + "@wordpress/data-controls": "file:../data-controls", "@wordpress/date": "file:../date", "@wordpress/deprecated": "file:../deprecated", "@wordpress/element": "file:../element", diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 52f021d1a2f83c..4c340057c64713 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -8,16 +8,15 @@ import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; +import { dispatch, select, apiFetch } from '@wordpress/data-controls'; +import { + parse, + synchronizeBlocksWithTemplate, +} from '@wordpress/blocks'; /** * Internal dependencies */ -import { - dispatch, - select, - resolveSelect, - apiFetch, -} from './controls'; import { getPostRawValue, } from './reducer'; @@ -34,14 +33,6 @@ import { getNotificationArgumentsForTrashFail, } from './utils/notice-builder'; -/** - * WordPress dependencies - */ -import { - parse, - synchronizeBlocksWithTemplate, -} from '@wordpress/blocks'; - /** * Returns an action generator used in signalling that editor has initialized with * the specified post object and editor settings. @@ -323,7 +314,7 @@ export function* savePost( options = {} ) { 'getCurrentPostType' ); - const postType = yield resolveSelect( + const postType = yield select( 'core', 'getPostType', currentPostType @@ -347,9 +338,9 @@ export function* savePost( options = {} ) { let path = `/wp/v2/${ postType.rest_base }/${ post.id }`; let method = 'PUT'; if ( isAutosave ) { - const currentUser = yield resolveSelect( 'core', 'getCurrentUser' ); + const currentUser = yield select( 'core', 'getCurrentUser' ); const currentUserId = currentUser ? currentUser.id : undefined; - const autosavePost = yield resolveSelect( 'core', 'getAutosave', post.type, post.id, currentUserId ); + const autosavePost = yield select( 'core', 'getAutosave', post.type, post.id, currentUserId ); const mappedAutosavePost = mapValues( pick( autosavePost, AUTOSAVE_PROPERTIES ), getPostRawValue ); // Ensure autosaves contain all expected fields, using autosave or @@ -448,7 +439,7 @@ export function* refreshPost() { STORE_KEY, 'getCurrentPostType' ); - const postType = yield resolveSelect( + const postType = yield select( 'core', 'getPostType', postTypeSlug @@ -476,7 +467,7 @@ export function* trashPost() { STORE_KEY, 'getCurrentPostType' ); - const postType = yield resolveSelect( + const postType = yield select( 'core', 'getPostType', postTypeSlug diff --git a/packages/editor/src/store/controls.js b/packages/editor/src/store/controls.js deleted file mode 100644 index 597a5f726145b5..00000000000000 --- a/packages/editor/src/store/controls.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * WordPress dependencies - */ -import triggerFetch from '@wordpress/api-fetch'; -import { createRegistryControl } from '@wordpress/data'; - -/** - * Dispatches a control action for triggering an api fetch call. - * - * @param {Object} request Arguments for the fetch request. - * - * @return {Object} control descriptor. - */ -export function apiFetch( request ) { - return { - type: 'API_FETCH', - request, - }; -} - -/** - * Dispatches a control action for triggering a registry select. - * - * @param {string} storeKey - * @param {string} selectorName - * @param {Array} args Arguments for the select. - * - * @return {Object} control descriptor. - */ -export function select( storeKey, selectorName, ...args ) { - return { - type: 'SELECT', - storeKey, - selectorName, - args, - }; -} - -/** - * Dispatches a control action for triggering a registry select that has a - * resolver. - * - * @param {string} storeKey - * @param {string} selectorName - * @param {Array} args Arguments for the select. - * - * @return {Object} control descriptor. - */ -export function resolveSelect( storeKey, selectorName, ...args ) { - return { - type: 'RESOLVE_SELECT', - storeKey, - selectorName, - args, - }; -} - -/** - * Dispatches a control action for triggering a registry dispatch. - * - * @param {string} storeKey - * @param {string} actionName - * @param {Array} args Arguments for the dispatch action. - * - * @return {Object} control descriptor. - */ -export function dispatch( storeKey, actionName, ...args ) { - return { - type: 'DISPATCH', - storeKey, - actionName, - args, - }; -} - -export default { - API_FETCH( { request } ) { - return triggerFetch( request ); - }, - SELECT: createRegistryControl( - ( registry ) => ( { storeKey, selectorName, args } ) => { - return registry.select( storeKey )[ selectorName ]( ...args ); - } - ), - DISPATCH: createRegistryControl( - ( registry ) => ( { storeKey, actionName, args } ) => { - return registry.dispatch( storeKey )[ actionName ]( ...args ); - } - ), - RESOLVE_SELECT: createRegistryControl( - ( registry ) => ( { storeKey, selectorName, args } ) => { - return new Promise( ( resolve ) => { - const hasFinished = () => registry.select( 'core/data' ) - .hasFinishedResolution( storeKey, selectorName, args ); - const getResult = () => registry.select( storeKey )[ selectorName ] - .apply( null, args ); - - // trigger the selector (to trigger the resolver) - const result = getResult(); - if ( hasFinished() ) { - return resolve( result ); - } - - const unsubscribe = registry.subscribe( () => { - if ( hasFinished() ) { - unsubscribe(); - resolve( getResult() ); - } - } ); - } ); - } - ), -}; diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index 42af629bcce0d3..1ba136aaab7226 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; /** * Internal dependencies @@ -10,7 +11,6 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as selectors from './selectors'; import * as actions from './actions'; -import controls from './controls'; import { STORE_KEY } from './constants'; const store = registerStore( STORE_KEY, { diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index af7e7f60a2210b..f0ec7c84dab15c 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -3,11 +3,15 @@ */ import { BEGIN, COMMIT, REVERT } from 'redux-optimist'; +/** + * WordPress dependencies + */ +import { select, dispatch, apiFetch } from '@wordpress/data-controls'; + /** * Internal dependencies */ import * as actions from '../actions'; -import { select, dispatch, apiFetch, resolveSelect } from '../controls'; import { STORE_KEY, SAVE_POST_NOTICE_ID, @@ -15,24 +19,20 @@ import { POST_UPDATE_TRANSACTION_ID, } from '../constants'; -jest.mock( '../controls' ); +jest.mock( '@wordpress/data-controls' ); select.mockImplementation( ( ...args ) => { - const { select: actualSelect } = jest.requireActual( '../controls' ); + const { select: actualSelect } = jest + .requireActual( '@wordpress/data-controls' ); return actualSelect( ...args ); } ); dispatch.mockImplementation( ( ...args ) => { - const { dispatch: actualDispatch } = jest.requireActual( '../controls' ); + const { dispatch: actualDispatch } = jest + .requireActual( '@wordpress/data-controls' ); return actualDispatch( ...args ); } ); -resolveSelect.mockImplementation( ( ...args ) => { - const { resolveSelect: selectResolver } = jest - .requireActual( '../controls' ); - return selectResolver( ...args ); -} ); - const apiFetchThrowError = ( error ) => { apiFetch.mockClear(); apiFetch.mockImplementation( () => { @@ -43,7 +43,8 @@ const apiFetchThrowError = ( error ) => { const apiFetchDoActual = () => { apiFetch.mockClear(); apiFetch.mockImplementation( ( ...args ) => { - const { apiFetch: fetch } = jest.requireActual( '../controls' ); + const { apiFetch: fetch } = jest + .requireActual( '@wordpress/data-controls' ); return fetch( ...args ); } ); }; @@ -212,7 +213,7 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next( postTypeSlug ); expect( value ).toEqual( - resolveSelect( 'core', 'getPostType', postTypeSlug ) + select( 'core', 'getPostType', postTypeSlug ) ); }, ], @@ -278,7 +279,7 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next(); expect( value ).toEqual( - resolveSelect( 'core', 'getCurrentUser' ) + select( 'core', 'getCurrentUser' ) ); }, ], @@ -288,7 +289,7 @@ describe( 'Post generator actions', () => { () => { const { value } = fulfillment.next( currentUser ); expect( value ).toEqual( - resolveSelect( + select( 'core', 'getAutosave', postTypeSlug, @@ -533,7 +534,7 @@ describe( 'Post generator actions', () => { ); it( 'yields expected action for selecting the post type object', () => { const { value } = fulfillment.next( postTypeSlug ); - expect( value ).toEqual( resolveSelect( + expect( value ).toEqual( select( 'core', 'getPostType', postTypeSlug @@ -611,7 +612,7 @@ describe( 'Post generator actions', () => { } ); it( 'yields expected action for selecting the post type object', () => { const { value } = fulfillment.next( postTypeSlug ); - expect( value ).toEqual( resolveSelect( + expect( value ).toEqual( select( 'core', 'getPostType', postTypeSlug From 27a6ead99b6ec38106c2b8696c74fe2a6e3d24d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Wed, 22 May 2019 16:32:31 +0100 Subject: [PATCH 162/664] Update onSplit method on RN blocks to the latest version. (#15762) * Update onSplit method to latest version. This commit updates the native richtext component to use the latest version of onSplit. It also updates the custom paragraph and heading blocks to use the new onSplit. * Remove method no longer in use. * Fix this.value desync with this.props.value when event is triggered between componentWillReceiveProps and componentDidUpdate --- .../src/components/rich-text/index.native.js | 98 +++++++++---------- .../block-library/src/heading/edit.native.js | 25 ++--- .../src/paragraph/edit.native.js | 59 +++-------- 3 files changed, 73 insertions(+), 109 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index c9cb56397dcae2..d47ace5c39144b 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -93,13 +93,7 @@ export class RichText extends Component { if ( this.multilineTag === 'li' ) { this.multilineWrapperTags = [ 'ul', 'ol' ]; } - - if ( this.props.onSplit ) { - this.onSplit = this.props.onSplit; - } else if ( this.props.unstableOnSplit ) { - this.onSplit = this.props.unstableOnSplit; - } - + this.onSplit = this.onSplit.bind( this ); this.isIOS = Platform.OS === 'ios'; this.createRecord = this.createRecord.bind( this ); this.onChange = this.onChange.bind( this ); @@ -187,55 +181,61 @@ export class RichText extends Component { }; } - /* - * Splits the content at the location of the selection. - * - * Replaces the content of the editor inside this element with the contents - * before the selection. Sends the elements after the selection to the `onSplit` - * handler. + /** + * Signals to the RichText owner that the block can be replaced with two + * blocks as a result of splitting the block by pressing enter, or with + * blocks as a result of splitting the block by pasting block content in the + * instance. * + * @param {Object} record The rich text value to split. + * @param {Array} pastedBlocks The pasted blocks to insert, if any. */ - splitContent( currentRecord, blocks = [], isPasted = false ) { - if ( ! this.onSplit ) { + onSplit( record, pastedBlocks = [] ) { + const { + onReplace, + onSplit, + __unstableOnSplitMiddle: onSplitMiddle, + } = this.props; + + if ( ! onReplace || ! onSplit ) { return; } - // TODO : Fix the index position in AztecNative for Android - let [ before, after ] = split( currentRecord ); - - // In case split occurs at the trailing or leading edge of the field, - // assume that the before/after values respectively reflect the current - // value. This also provides an opportunity for the parent component to - // determine whether the before/after value has changed using a trivial - // strict equality operation. - if ( isEmpty( after ) && before.text.length === currentRecord.text.length ) { - before = currentRecord; - } else if ( isEmpty( before ) && after.text.length === currentRecord.text.length ) { - after = currentRecord; - } + const blocks = []; + const [ before, after ] = split( record ); + const hasPastedBlocks = pastedBlocks.length > 0; - // If pasting and the split would result in no content other than the - // pasted blocks, remove the before and after blocks. - if ( isPasted ) { - before = isEmpty( before ) ? null : before; - after = isEmpty( after ) ? null : after; + // Create a block with the content before the caret if there's no pasted + // blocks, or if there are pasted blocks and the value is not empty. + // We do not want a leading empty block on paste, but we do if split + // with e.g. the enter key. + if ( ! hasPastedBlocks || ! isEmpty( before ) ) { + blocks.push( onSplit( this.valueToFormat( before ) ) ); } - if ( before ) { - before = this.valueToFormat( before ); + if ( hasPastedBlocks ) { + blocks.push( ...pastedBlocks ); + } else if ( onSplitMiddle ) { + blocks.push( onSplitMiddle() ); } - if ( after ) { - after = this.valueToFormat( after ); + // If there's pasted blocks, append a block with the content after the + // caret. Otherwise, do append and empty block if there is no + // `onSplitMiddle` prop, but if there is and the content is empty, the + // middle block is enough to set focus in. + if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { + blocks.push( onSplit( this.valueToFormat( after ) ) ); } + // If there are pasted blocks, set the selection to the last one. + // Otherwise, set the selection to the second block. + const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; // The onSplit event can cause a content update event for this block. Such event should // definitely be processed by our native components, since they have no knowledge of // how the split works. Setting lastEventCount to undefined forces the native component to // always update when provided with new content. this.lastEventCount = undefined; - - this.onSplit( before, after, ...blocks ); + onReplace( blocks, indexToSelect ); } valueToFormat( value ) { @@ -364,7 +364,7 @@ export class RichText extends Component { const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; this.onFormatChangeForceChild( insertedLineBreak ); } else if ( this.onSplit && isEmptyLine( currentRecord ) ) { - this.splitContent( currentRecord ); + this.onSplit( currentRecord ); } else { this.needsSelectionUpdate = true; const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; @@ -375,7 +375,7 @@ export class RichText extends Component { const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; this.onFormatChangeForceChild( insertedLineBreak ); } else { - this.splitContent( currentRecord ); + this.onSplit( currentRecord ); } this.lastAztecEventType = 'input'; } @@ -414,7 +414,6 @@ export class RichText extends Component { * @param {PasteEvent} event The paste event which wraps `nativeEvent`. */ onPaste( event ) { - const isPasted = true; const { onSplit } = this.props; const { pastedText, pastedHtml, files } = event.nativeEvent; @@ -440,7 +439,7 @@ export class RichText extends Component { if ( shouldReplace ) { this.props.onReplace( content ); } else { - this.splitContent( currentRecord, content, isPasted ); + this.onSplit( currentRecord, content ); } return; @@ -507,7 +506,7 @@ export class RichText extends Component { if ( shouldReplace ) { this.props.onReplace( pastedContent ); } else { - this.splitContent( currentRecord, pastedContent, isPasted ); + this.onSplit( currentRecord, pastedContent ); } } } @@ -610,13 +609,6 @@ export class RichText extends Component { return value; } - componentWillReceiveProps( nextProps ) { - if ( nextProps.value !== this.value ) { - this.value = this.props.value; - this.lastEventCount = undefined; - } - } - forceSelectionUpdate( start, end ) { if ( ! this.needsSelectionUpdate ) { this.needsSelectionUpdate = true; @@ -682,6 +674,10 @@ export class RichText extends Component { } componentDidUpdate( prevProps ) { + if ( this.props.value !== this.value ) { + this.value = this.props.value; + this.lastEventCount = undefined; + } if ( this.props.isSelected && ! prevProps.isSelected ) { this._editor.focus(); // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index ded76af565b632..97d2228346466a 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -31,10 +31,10 @@ class HeadingEdit extends Component { render() { const { attributes, - insertBlocksAfter, setAttributes, mergeBlocks, style, + onReplace, } = this.props; const { @@ -80,17 +80,18 @@ class HeadingEdit extends Component { onBlur={ this.props.onBlur } // always assign onBlur as a props onChange={ ( value ) => setAttributes( { content: value } ) } onMerge={ mergeBlocks } - unstableOnSplit={ - insertBlocksAfter ? - ( before, after, ...blocks ) => { - setAttributes( { content: before } ); - insertBlocksAfter( [ - ...blocks, - createBlock( 'core/paragraph', { content: after } ), - ] ); - } : - undefined - } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( 'core/paragraph' ); + } + + return createBlock( 'core/heading', { + ...attributes, + content: value, + } ); + } } + onReplace={ onReplace } + onRemove={ () => onReplace( [] ) } placeholder={ placeholder || __( 'Write heading…' ) } /> </View> diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index b16761ace72f84..a97885c213c3a0 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -22,53 +22,9 @@ const name = 'core/paragraph'; class ParagraphEdit extends Component { constructor( props ) { super( props ); - this.splitBlock = this.splitBlock.bind( this ); this.onReplace = this.onReplace.bind( this ); } - /** - * Split handler for RichText value, namely when content is pasted or the - * user presses the Enter key. - * - * @param {?Array} before Optional before value, to be used as content - * in place of what exists currently for the - * block. If undefined, the block is deleted. - * @param {?Array} after Optional after value, to be appended in a new - * paragraph block to the set of blocks passed - * as spread. - * @param {...WPBlock} blocks Optional blocks inserted between the before - * and after value blocks. - */ - splitBlock( before, after, ...blocks ) { - const { - attributes, - insertBlocksAfter, - setAttributes, - onReplace, - } = this.props; - - if ( after !== null ) { - // Append "After" content as a new paragraph block to the end of - // any other blocks being inserted after the current paragraph. - const newBlock = createBlock( name, { content: after } ); - blocks.push( newBlock ); - } - - if ( blocks.length && insertBlocksAfter ) { - insertBlocksAfter( blocks ); - } - - const { content } = attributes; - if ( before === null ) { - onReplace( [] ); - } else if ( content !== before ) { - // Only update content if it has in-fact changed. In case that user - // has created a new paragraph at end of an existing one, the value - // of before will be strictly equal to the current content. - setAttributes( { content: before } ); - } - } - onReplace( blocks ) { const { attributes, onReplace } = this.props; onReplace( blocks.map( ( block, index ) => ( @@ -96,6 +52,7 @@ class ParagraphEdit extends Component { attributes, setAttributes, mergeBlocks, + onReplace, style, } = this.props; @@ -133,9 +90,19 @@ class ParagraphEdit extends Component { content: nextContent, } ); } } - onSplit={ this.splitBlock } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( name ); + } + + return createBlock( name, { + ...attributes, + content: value, + } ); + } } onMerge={ mergeBlocks } - onReplace={ this.onReplace } + onReplace={ onReplace } + onRemove={ onReplace ? () => onReplace( [] ) : undefined } placeholder={ placeholder || __( 'Start writing…' ) } /> </View> From f1bbc004734ea157dd72ed5bf1f808c10efc0e84 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 22 May 2019 16:36:27 +0100 Subject: [PATCH 163/664] Connnect gutenberg widgets screen to widget area endpoints (#15779) * Connect gutenberg widget screen to widget-area endpoints. * Use saveEntityRecord. * Enhancements * Add test cases * Extracted a WidgetAreas component. * Improve the setup action to avoid the need for a parameter. * update test case * Refactor pass widget are by id, have a save action. * id instead of widgetAreaId + lint fix * Correct controls import --- packages/core-data/src/entities.js | 1 + .../edit-widgets-initializer/index.js | 27 +++ .../src/components/header/index.js | 16 +- .../src/components/layout/index.js | 14 +- .../src/components/layout/style.scss | 5 - .../src/components/widget-area/index.js | 39 +++- .../src/components/widget-area/style.scss | 4 + .../src/components/widget-areas/index.js | 30 +++ packages/edit-widgets/src/index.js | 5 +- packages/edit-widgets/src/store/actions.js | 76 +++++++ packages/edit-widgets/src/store/constants.js | 6 + packages/edit-widgets/src/store/index.js | 23 +++ packages/edit-widgets/src/store/reducer.js | 69 +++++++ packages/edit-widgets/src/store/selectors.js | 38 ++++ .../edit-widgets/src/store/test/actions.js | 189 ++++++++++++++++++ .../edit-widgets/src/store/test/reducer.js | 123 ++++++++++++ .../edit-widgets/src/store/test/selectors.js | 91 +++++++++ packages/edit-widgets/src/style.scss | 1 + 18 files changed, 728 insertions(+), 29 deletions(-) create mode 100644 packages/edit-widgets/src/components/edit-widgets-initializer/index.js create mode 100644 packages/edit-widgets/src/components/widget-area/style.scss create mode 100644 packages/edit-widgets/src/components/widget-areas/index.js create mode 100644 packages/edit-widgets/src/store/actions.js create mode 100644 packages/edit-widgets/src/store/constants.js create mode 100644 packages/edit-widgets/src/store/index.js create mode 100644 packages/edit-widgets/src/store/reducer.js create mode 100644 packages/edit-widgets/src/store/selectors.js create mode 100644 packages/edit-widgets/src/store/test/actions.js create mode 100644 packages/edit-widgets/src/store/test/reducer.js create mode 100644 packages/edit-widgets/src/store/test/selectors.js diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 84c4dc3b160b18..280e54a5297f3b 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -15,6 +15,7 @@ export const defaultEntities = [ { name: 'postType', kind: 'root', key: 'slug', baseURL: '/wp/v2/types' }, { name: 'media', kind: 'root', baseURL: '/wp/v2/media', plural: 'mediaItems' }, { name: 'taxonomy', kind: 'root', key: 'slug', baseURL: '/wp/v2/taxonomies', plural: 'taxonomies' }, + { name: 'widgetArea', kind: 'root', baseURL: '/__experimental/widget-areas', plural: 'widgetAreas' }, ]; export const kinds = [ diff --git a/packages/edit-widgets/src/components/edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/edit-widgets-initializer/index.js new file mode 100644 index 00000000000000..025488a0502991 --- /dev/null +++ b/packages/edit-widgets/src/components/edit-widgets-initializer/index.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { useEffect } from '@wordpress/element'; +import { withDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import Layout from '../layout'; + +function EditWidgetsInitializer( { setupWidgetAreas } ) { + useEffect( () => { + setupWidgetAreas(); + }, [] ); + return <Layout />; +} + +export default compose( [ + withDispatch( ( dispatch ) => { + const { setupWidgetAreas } = dispatch( 'core/edit-widgets' ); + return { + setupWidgetAreas, + }; + } ), +] )( EditWidgetsInitializer ); diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js index c8ec2d3bffee9c..3dc3f650e9d8ea 100644 --- a/packages/edit-widgets/src/components/header/index.js +++ b/packages/edit-widgets/src/components/header/index.js @@ -1,10 +1,12 @@ /** * WordPress dependencies */ +import { compose } from '@wordpress/compose'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { withDispatch } from '@wordpress/data'; -function Header() { +function Header( { saveWidgetAreas } ) { return ( <div className="edit-widgets-header" @@ -17,7 +19,7 @@ function Header() { </h1> <div className="edit-widgets-header__actions"> - <Button isPrimary isLarge> + <Button isPrimary isLarge onClick={ saveWidgetAreas }> { __( 'Update' ) } </Button> </div> @@ -25,4 +27,12 @@ function Header() { ); } -export default Header; +export default compose( [ + withDispatch( ( dispatch ) => { + const { saveWidgetAreas } = dispatch( 'core/edit-widgets' ); + return { + saveWidgetAreas, + }; + } ), +] )( Header ); + diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 31d3577eb793da..06ed92455fc6e9 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -9,15 +9,9 @@ import { navigateRegions } from '@wordpress/components'; */ import Header from '../header'; import Sidebar from '../sidebar'; -import WidgetArea from '../widget-area'; +import WidgetAreas from '../widget-areas'; function Layout() { - const areas = [ - __( 'Sidebar' ), - __( 'Footer' ), - __( 'Header' ), - ]; - return ( <> <Header /> @@ -28,11 +22,7 @@ function Layout() { aria-label={ __( 'Widgets screen content' ) } tabIndex="-1" > - { areas.map( ( area, index ) => ( - <div key={ index } className="edit-widgets-layout__area"> - <WidgetArea title={ area } initialOpen={ index === 0 } /> - </div> - ) ) } + <WidgetAreas /> </div> </> ); diff --git a/packages/edit-widgets/src/components/layout/style.scss b/packages/edit-widgets/src/components/layout/style.scss index 13d763798dc59d..a7447d2dabf9d2 100644 --- a/packages/edit-widgets/src/components/layout/style.scss +++ b/packages/edit-widgets/src/components/layout/style.scss @@ -9,8 +9,3 @@ margin-top: $header-height; } } - -.edit-widgets-layout__area { - max-width: $content-width; - margin: 0 auto 30px; -} diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 5826f470756fd2..7914a4b6ea13ab 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -1,20 +1,24 @@ /** * WordPress dependencies */ +import { compose } from '@wordpress/compose'; import { Panel, PanelBody } from '@wordpress/components'; import { BlockEditorProvider, BlockList, } from '@wordpress/block-editor'; -import { useState } from '@wordpress/element'; - -function WidgetArea( { title, initialOpen } ) { - const [ blocks, updateBlocks ] = useState( [] ); +import { withDispatch, withSelect } from '@wordpress/data'; +function WidgetArea( { + blocks, + initialOpen, + updateBlocks, + widgetAreaName, +} ) { return ( - <Panel> + <Panel className="edit-widgets-widget-area"> <PanelBody - title={ title } + title={ widgetAreaName } initialOpen={ initialOpen } > <BlockEditorProvider @@ -29,4 +33,25 @@ function WidgetArea( { title, initialOpen } ) { ); } -export default WidgetArea; +export default compose( [ + withSelect( ( select, { id } ) => { + const { + getBlocksFromWidgetArea, + getWidgetArea, + } = select( 'core/edit-widgets' ); + const blocks = getBlocksFromWidgetArea( id ); + const widgetAreaName = ( getWidgetArea( id ) || {} ).name; + return { + blocks, + widgetAreaName, + }; + } ), + withDispatch( ( dispatch, { id } ) => { + return { + updateBlocks( blocks ) { + const { updateBlocksInWidgetArea } = dispatch( 'core/edit-widgets' ); + updateBlocksInWidgetArea( id, blocks ); + }, + }; + } ), +] )( WidgetArea ); diff --git a/packages/edit-widgets/src/components/widget-area/style.scss b/packages/edit-widgets/src/components/widget-area/style.scss new file mode 100644 index 00000000000000..d9307abab0a798 --- /dev/null +++ b/packages/edit-widgets/src/components/widget-area/style.scss @@ -0,0 +1,4 @@ +.edit-widgets-widget-area { + max-width: $content-width; + margin: 0 auto 30px; +} diff --git a/packages/edit-widgets/src/components/widget-areas/index.js b/packages/edit-widgets/src/components/widget-areas/index.js new file mode 100644 index 00000000000000..99a3f1e8689fa6 --- /dev/null +++ b/packages/edit-widgets/src/components/widget-areas/index.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import WidgetArea from '../widget-area'; + +function WidgetAreas( { areas } ) { + return areas.map( ( { id }, index ) => ( + <WidgetArea + key={ id } + id={ id } + initialOpen={ index === 0 } + /> + ) ); +} + +export default compose( [ + withSelect( ( select ) => { + const { getWidgetAreas } = select( 'core/edit-widgets' ); + const areas = getWidgetAreas(); + return { + areas, + }; + } ), +] )( WidgetAreas ); diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 2181853fa461de..ede34168d9cb0c 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -7,7 +7,8 @@ import { registerCoreBlocks } from '@wordpress/block-library'; /** * Internal dependencies */ -import Layout from './components/layout'; +import './store'; +import EditWidgetsInitializer from './components/edit-widgets-initializer'; /** * Initilizes the widgets screen @@ -17,7 +18,7 @@ import Layout from './components/layout'; export function initialize( id ) { registerCoreBlocks(); render( - <Layout />, + <EditWidgetsInitializer />, document.getElementById( id ) ); } diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js new file mode 100644 index 00000000000000..dd87ca78c2e6af --- /dev/null +++ b/packages/edit-widgets/src/store/actions.js @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import { get, map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { parse, serialize } from '@wordpress/blocks'; +import { dispatch, select } from '@wordpress/data-controls'; + +/** + * Yields an action object that setups the widget areas. + * + * @yields {Object} Action object. + */ +export function* setupWidgetAreas() { + const widgetAreas = yield select( + 'core', + 'getEntityRecords', + 'root', + 'widgetArea' + ); + yield { + type: 'SETUP_WIDGET_AREAS', + widgetAreas: map( widgetAreas, ( { content, ...widgetAreaProperties } ) => { + return { + ...widgetAreaProperties, + blocks: parse( get( content, [ 'raw' ], '' ) ), + }; + } ), + }; +} + +/** + * Returns an action object used to update the blocks in a specific widget area. + * + * @param {string} widgetAreaId Id of the widget area. + * @param {Object[]} blocks Array of blocks that should be part of the widget area. + * + * @return {Object} Action object. + */ +export function updateBlocksInWidgetArea( widgetAreaId, blocks = [] ) { + return { + type: 'UPDATE_BLOCKS_IN_WIDGET_AREA', + widgetAreaId, + blocks, + }; +} + +/** + * Action that performs the logic to save widget areas. + * + * @yields {Object} Action object. + */ +export function* saveWidgetAreas() { + const widgetAreas = yield select( + 'core/edit-widgets', + 'getWidgetAreas', + ); + for ( const { id } of widgetAreas ) { + const blocks = yield select( + 'core/edit-widgets', + 'getBlocksFromWidgetArea', + id + ); + yield dispatch( + 'core', + 'saveWidgetArea', + { + id, + content: serialize( blocks ), + } + ); + } +} diff --git a/packages/edit-widgets/src/store/constants.js b/packages/edit-widgets/src/store/constants.js new file mode 100644 index 00000000000000..4968386ea38ea1 --- /dev/null +++ b/packages/edit-widgets/src/store/constants.js @@ -0,0 +1,6 @@ +/** + * Constant for the store module (or reducer) key. + * @type {string} + */ +export const STORE_KEY = 'core/edit-widgets'; + diff --git a/packages/edit-widgets/src/store/index.js b/packages/edit-widgets/src/store/index.js new file mode 100644 index 00000000000000..cd12827d6aa001 --- /dev/null +++ b/packages/edit-widgets/src/store/index.js @@ -0,0 +1,23 @@ +/** + * WordPress dependencies + */ +import { registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import reducer from './reducer'; +import * as actions from './actions'; +import * as selectors from './selectors'; + +const store = registerStore( 'core/edit-widgets', { + reducer, + actions, + selectors, + controls, +} ); + +store.dispatch( { type: 'INIT' } ); + +export default store; diff --git a/packages/edit-widgets/src/store/reducer.js b/packages/edit-widgets/src/store/reducer.js new file mode 100644 index 00000000000000..ee5d664c7b1b81 --- /dev/null +++ b/packages/edit-widgets/src/store/reducer.js @@ -0,0 +1,69 @@ +/** + * External dependencies + */ +import { keyBy, mapValues, pick } from 'lodash'; + +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; + +/** + * Reducer storing some properties of each widget area. + * + * @param {Array} state Current state. + * @param {Object} action Action object. + * + * @return {Array} Updated state. + */ +export function widgetAreas( state = {}, action = {} ) { + switch ( action.type ) { + case 'SETUP_WIDGET_AREAS': + return mapValues( + keyBy( action.widgetAreas, 'id' ), + ( value ) => pick( value, [ + 'name', + 'id', + 'description', + ] ) + ); + } + + return state; +} + +/** + * Reducer storing the blocks part of each widget area. + * + * @param {Array} state Current state. + * @param {Object} action Action object. + * + * @return {Array} Updated state. + */ +export function widgetAreaBlocks( state = {}, action = {} ) { + switch ( action.type ) { + case 'SETUP_WIDGET_AREAS': + return mapValues( + keyBy( action.widgetAreas, 'id' ), + ( value ) => value.blocks + ); + case 'UPDATE_BLOCKS_IN_WIDGET_AREA': { + const blocks = state[ action.widgetAreaId ] || []; + // check if change is required + if ( blocks === action.blocks ) { + return state; + } + return { + ...state, + [ action.widgetAreaId ]: action.blocks, + }; + } + } + + return state; +} + +export default combineReducers( { + widgetAreas, + widgetAreaBlocks, +} ); diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js new file mode 100644 index 00000000000000..6034b1b9a5461a --- /dev/null +++ b/packages/edit-widgets/src/store/selectors.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import { toArray } from 'lodash'; +import createSelector from 'rememo'; + +/** + * Returns an array of widget areas. + * + * @param {Object} state Widget editor state. + * @return {Object[]} Array of widget areas. + */ +export const getWidgetAreas = createSelector( + ( state ) => ( toArray( state.widgetAreas ) ), + ( state ) => [ state.widgetAreas ] +); + +/** + * Returns a widget area object. + * + * @param {Object} state Widget editor state. + * @param {string} widgetAreaId Id of the widget area. + * @return {Object} Array of widget areas. + */ +export function getWidgetArea( state, widgetAreaId ) { + return state.widgetAreas[ widgetAreaId ]; +} + +/** + * Returns an array of blocks part of a widget area. + * + * @param {Object} state Widget editor state. + * @param {string} widgetAreaId Id of the widget area. + * @return {Object[]} Array of blocks. + */ +export function getBlocksFromWidgetArea( state, widgetAreaId ) { + return state.widgetAreaBlocks[ widgetAreaId ]; +} diff --git a/packages/edit-widgets/src/store/test/actions.js b/packages/edit-widgets/src/store/test/actions.js new file mode 100644 index 00000000000000..c56793b14be15b --- /dev/null +++ b/packages/edit-widgets/src/store/test/actions.js @@ -0,0 +1,189 @@ +/** + * WordPress dependencies + */ +import { createBlock, serialize } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +/** + * Internal dependencies + */ +import { + saveWidgetAreas, + setupWidgetAreas, + updateBlocksInWidgetArea, +} from '../actions'; + +describe( 'actions', () => { + beforeAll( () => { + registerCoreBlocks(); + } ); + + describe( 'setupWidgetAreas', () => { + it( 'should yield SETUP_WIDGET_AREAS action', () => { + const setupWidgetAreasGen = setupWidgetAreas(); + setupWidgetAreasGen.next(); + expect( + setupWidgetAreasGen.next( [ + { + id: 'sidebar-1', + content: { + raw: '<!-- wp:paragraph --><p>Test block</p><!-- /wp:paragraph -->', + }, + }, + { + id: 'footer-1', + }, + ] ) + ).toMatchObject( { + done: false, + value: { + type: 'SETUP_WIDGET_AREAS', + widgetAreas: [ + { + id: 'sidebar-1', + blocks: [ { + name: 'core/paragraph', + attributes: { + content: 'Test block', + }, + } ], + }, + { + id: 'footer-1', + blocks: [], + }, + ], + }, + } ); + expect( setupWidgetAreasGen.next() ).toEqual( { + done: true, + value: undefined, + } ); + } ); + } ); + + describe( 'updateBlocksInWidgetArea', () => { + it( 'should return UPDATE_BLOCKS_IN_WIDGET_AREA action', () => { + expect( updateBlocksInWidgetArea( 'sidebar-1', [ + { + name: 'test/ribs', + attributes: { + myAttr: false, + }, + }, + ] ) ).toEqual( { + type: 'UPDATE_BLOCKS_IN_WIDGET_AREA', + widgetAreaId: 'sidebar-1', + blocks: [ + { + name: 'test/ribs', + attributes: { + myAttr: false, + }, + }, + ], + } ); + } ); + } ); + + describe( 'saveWidgetAreas', () => { + it( 'should yield the actions to save a widget area', () => { + const saveWidgetAreasGen = saveWidgetAreas(); + + expect( + saveWidgetAreasGen.next() + ).toEqual( { + done: false, + value: { + type: 'SELECT', + storeKey: 'core/edit-widgets', + selectorName: 'getWidgetAreas', + args: [], + }, + } ); + + expect( + saveWidgetAreasGen.next( [ + { + id: 'sidebar-1', + blocks: [ { + name: 'core/paragraph', + attributes: { + content: 'Test block', + }, + } ], + }, + { + id: 'footer-1', + blocks: [], + }, + ] ) + ).toEqual( { + done: false, + value: { + type: 'SELECT', + storeKey: 'core/edit-widgets', + selectorName: 'getBlocksFromWidgetArea', + args: [ 'sidebar-1' ], + }, + } ); + + expect( + saveWidgetAreasGen.next( [ + createBlock( 'core/paragraph', { content: 'Content' } ), + ] ) + ).toEqual( { + done: false, + value: { + type: 'DISPATCH', + storeKey: 'core', + actionName: 'saveWidgetArea', + args: [ { + id: 'sidebar-1', + content: serialize( + createBlock( 'core/paragraph', { content: 'Content' } ) + ), + } ], + }, + } ); + + expect( + saveWidgetAreasGen.next() + ).toEqual( { + done: false, + value: { + type: 'SELECT', + storeKey: 'core/edit-widgets', + selectorName: 'getBlocksFromWidgetArea', + args: [ 'footer-1' ], + }, + } ); + + expect( + saveWidgetAreasGen.next( [ + createBlock( 'core/button', { text: 'My Button' } ), + ] ) + ).toEqual( { + done: false, + value: { + type: 'DISPATCH', + storeKey: 'core', + actionName: 'saveWidgetArea', + args: [ { + id: 'footer-1', + content: serialize( + createBlock( 'core/button', { text: 'My Button' } ) + ), + } ], + }, + } ); + + expect( + saveWidgetAreasGen.next() + ).toEqual( { + done: true, + value: undefined, + } ); + } ); + } ); +} ); diff --git a/packages/edit-widgets/src/store/test/reducer.js b/packages/edit-widgets/src/store/test/reducer.js new file mode 100644 index 00000000000000..35bb0b68954c7d --- /dev/null +++ b/packages/edit-widgets/src/store/test/reducer.js @@ -0,0 +1,123 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ +import { + widgetAreas, + widgetAreaBlocks, +} from '../reducer'; + +describe( 'state', () => { + describe( 'widgetAreas', () => { + it( 'should correctly initialize the state', () => { + expect( widgetAreas() ).toEqual( {} ); + } ); + + it( 'should correctly return the state', () => { + const original = deepFreeze( widgetAreas() ); + const state = widgetAreas( original, { + type: 'SETUP_WIDGET_AREAS', + widgetAreas: [ + { + id: 'sidebar-1', + name: 'My sidebar', + description: 'A test sidebar', + irrelevantProp: 'ok', + }, + ], + } ); + + expect( state ).toEqual( { + 'sidebar-1': { + id: 'sidebar-1', + name: 'My sidebar', + description: 'A test sidebar', + }, + } ); + } ); + } ); + describe( 'widgetAreaBlocks', () => { + it( 'should correctly initialize the state', () => { + expect( widgetAreaBlocks() ).toEqual( {} ); + } ); + + it( 'should correctly return the state after a setup action', () => { + const original = deepFreeze( widgetAreaBlocks() ); + const state = widgetAreaBlocks( original, { + type: 'SETUP_WIDGET_AREAS', + widgetAreas: [ + { + id: 'sidebar-1', + name: 'My sidebar', + description: 'A test sidebar', + blocks: [ { + clientId: 'ribs', + name: 'core/test', + attributes: { + isOk: true, + }, + } ], + }, + ], + } ); + + expect( state ).toEqual( { + 'sidebar-1': [ { + clientId: 'ribs', + name: 'core/test', + attributes: { + isOk: true, + }, + } ], + } ); + } ); + it( 'should correctly update the blocks', () => { + const original = deepFreeze( { + 'sidebar-1': [ { + clientId: 'ribs', + name: 'core/test', + attributes: { + isOk: true, + }, + } ], + } ); + const state = widgetAreaBlocks( original, { + type: 'UPDATE_BLOCKS_IN_WIDGET_AREA', + widgetAreaId: 'sidebar-1', + blocks: [ { + clientId: 'test', + name: 'core/test-ok', + attributes: { + content: 'Content', + }, + }, { + clientId: 'ribs', + name: 'core/test', + attributes: { + isOk: true, + }, + } ], + } ); + + expect( state ).toEqual( { + 'sidebar-1': [ { + clientId: 'test', + name: 'core/test-ok', + attributes: { + content: 'Content', + }, + }, { + clientId: 'ribs', + name: 'core/test', + attributes: { + isOk: true, + }, + } ], + } ); + } ); + } ); +} ); diff --git a/packages/edit-widgets/src/store/test/selectors.js b/packages/edit-widgets/src/store/test/selectors.js new file mode 100644 index 00000000000000..62e0db78f43026 --- /dev/null +++ b/packages/edit-widgets/src/store/test/selectors.js @@ -0,0 +1,91 @@ +/** + * Internal dependencies + */ +import { + getBlocksFromWidgetArea, + getWidgetArea, + getWidgetAreas, +} from '../selectors'; + +describe( 'selectors', () => { + describe( 'getWidgetAreas', () => { + it( 'should return an array with the widget areas', () => { + const state = { + widgetAreas: { + 'sidebar-1': { + id: 'sidebar-1', + }, + 'footer-1': { + id: 'footer-1', + }, + }, + }; + + expect( getWidgetAreas( state ) ).toEqual( [ + { + id: 'sidebar-1', + }, + { + id: 'footer-1', + }, + ] ); + } ); + } ); + + describe( 'getWidgetArea', () => { + it( 'should return an object with the widget area', () => { + const state = { + widgetAreas: { + 'sidebar-1': { + id: 'sidebar-1', + name: 'Sidebar', + }, + 'footer-1': { + id: 'footer-1', + description: 'a footer', + }, + }, + }; + + expect( getWidgetArea( state, 'sidebar-1' ) ).toEqual( { + id: 'sidebar-1', + name: 'Sidebar', + } ); + + expect( getWidgetArea( state, 'footer-1' ) ).toEqual( { + id: 'footer-1', + description: 'a footer', + } ); + + expect( getWidgetArea( state, 'footer-3' ) ).toEqual( undefined ); + } ); + } ); + + describe( 'getBlocksFromWidgetArea', () => { + it( 'should return the blocks in a widget area', () => { + const state = { + widgetAreaBlocks: { + 'sidebar-1': [ + { + name: 'test/ribs', + attributes: { + myAttr: false, + }, + }, + ], + 'footer-1': [], + }, + }; + + expect( getBlocksFromWidgetArea( state, 'sidebar-1' ) ).toEqual( [ + { + name: 'test/ribs', + attributes: { + myAttr: false, + }, + }, + ] ); + expect( getBlocksFromWidgetArea( state, 'footer-1' ) ).toEqual( [] ); + } ); + } ); +} ); diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 0a61df82f3c262..4be0d14910338a 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -1,6 +1,7 @@ @import "./components/header/style.scss"; @import "./components/layout/style.scss"; @import "./components/sidebar/style.scss"; +@import "./components/widget-area/style.scss"; // In order to use mix-blend-mode, this element needs to have an explicitly set background-color // We scope it to .wp-toolbar to be wp-admin only, to prevent bleed into other implementations From d15c484ce0172bba6f373ff5c62224cb71b9b208 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 22 May 2019 16:46:04 -0400 Subject: [PATCH 164/664] Framework: Update "Maintaining Changelogs" section to current recommendations (#15740) * Framework: Update "Maintaining Changelogs" section to current recommendations * project -> package --- packages/README.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/README.md b/packages/README.md index e4fb5723f3e693..3c595fc8f8c25b 100644 --- a/packages/README.md +++ b/packages/README.md @@ -50,35 +50,33 @@ When creating a new package, you need to provide at least the following: ### Maintaining Changelogs -Maintaining dozens of npm packages is difficult—it can be tough to keep track of changes. That's why we use `CHANGELOG.md` files for each package to simplify the release process. All packages should follow the [Semantic Versioning (`semver`) specification](https://semver.org/). +In maintaining dozens of npm packages, it can be tough to keep track of changes. To simplify the release process, each package includes a `CHANGELOG.md` file which details all published releases and the unreleased ("Master") changes, if any exist. -The developer who proposes a change (pull request) is responsible for choosing the correct version increment (`major`, `minor`, or `patch`) according to the following guidelines: - -- Major version X (X.y.z | X > 0) should be changed with any backward incompatible/"breaking" change. This will usually occur at the final stage of deprecating and removing of a feature. -- Minor version Y (x.Y.z | x > 0) should be changed when you add functionality or change functionality in a backward compatible manner. It must be incremented if any public API functionality is marked as deprecated. -- Patch version Z (x.y.Z | x > 0) should be incremented when you make backward compatible bug fixes. - -When in doubt, refer to [Semantic Versioning specification](https://semver.org/). +For each pull request, you should always include relevant changes in a "Master" heading at the top of the file. You should add the heading if it doesn't already exist. _Example:_ ```md -## v1.2.2 (Unreleased) +## Master ### Bug Fix -- ... -- ... +- Fixed an off-by-one error with the `sum` function. ``` -- If you need to add something considered a bug fix, you add the item to `Bug Fix` section and leave the version as 1.2.2. -- If it's a new feature, you add the item to `New Feature` section and change version to 1.3.0. -- If it's a breaking change you want to introduce, add the item to `Breaking Change` section and bump the version to 2.0.0. -- If you struggle to classify a change as one of the above, then it might be not necessary to include it. +There are a number of common release subsections you can follow. Each is intended to align to a specific meaning in the context of the [Semantic Versioning (`semver`) specification](https://semver.org/) the project adheres to. It is important that you describe your changes accurately, since this is used in the packages release process to help determine the version of the next release. + +- "Breaking Change" - A backwards-incompatible change which requires specific attention of the impacted developers to reconcile (requires a major version bump). +- "New Feature" - The addition of a new backwards-compatible function or feature to the existing public API (requires a minor verison bump). +- "Enhancement" - Backwards-compatible improvements to existing functionality (requires a minor version bump). +- "Bug Fix" - Resolutions to existing buggy behavior (requires a patch version bump). +- "Internal" - Changes which do not have an impact on the public interface or behavior of the module (requires a patch version bump). + +While other section naming can be used when appropriate, it's important that are expressed clearly to avoid confusion for both the packages releaser and third-party consumers. + +When in doubt, refer to [Semantic Versioning specification](https://semver.org/). -The version bump is only necessary if one of the following applies: - - There are no other unreleased changes. - - The type of change you're introducing is incompatible (more severe) than the other unreleased changes. +If you are publishing new versions of packages, note that there are versioning recommendations outlined in the [Gutenberg Release Process document](https://github.com/WordPress/gutenberg/blob/master/docs/contributors/release.md) which prescribe _minimum_ version bumps for specific types of releases. The chosen version should be the greater of the two between the semantic versioning and Gutenberg release minimum version bumps. ### Releasing Packages From c53f62193e354b14f62933e6a89dfd94ac0a6124 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 22 May 2019 22:35:44 +0100 Subject: [PATCH 165/664] Add: note in the save function documentation to discourage side effects (#15765) I noticed people are not aware side effects cannot be used in the save function, and people use the data module in the save function e.g: to query the post title. This may easily break the blocks. This commit updates the documentation to make people more aware of this problem. --- .../developers/block-api/block-edit-save.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/designers-developers/developers/block-api/block-edit-save.md b/docs/designers-developers/developers/block-api/block-edit-save.md index 362bc08e6bccef..5d0cc72b6c6b78 100644 --- a/docs/designers-developers/developers/block-api/block-edit-save.md +++ b/docs/designers-developers/developers/block-api/block-edit-save.md @@ -226,6 +226,13 @@ For most blocks, the return value of `save` should be an [instance of WordPress _Note:_ While it is possible to return a string value from `save`, it _will be escaped_. If the string includes HTML markup, the markup will be shown on the front of the site verbatim, not as the equivalent HTML node content. If you must return raw HTML from `save`, use `wp.element.RawHTML`. As the name implies, this is prone to [cross-site scripting](https://en.wikipedia.org/wiki/Cross-site_scripting) and therefore is discouraged in favor of a WordPress Element hierarchy whenever possible. +_Note:_ The save function should be a pure function that depends only on the attributes used to invoke it. +It can not have any side effect or retrieve information from another source, e.g. it is not possible to use the data module inside it `select( store ).selector( ... )`. +This is because if the external information changes, the block may be flagged as invalid when the post is later edited ([read more about Validation](#validation)). +If there is a need to have other information as part of the save, developers can consider one of these two alternatives: + - Use [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md) and dynamically retrieve the required information on the server. + - Store the external value as an attribute which is dynamically updated in the block's `edit` function as changes occur. + For [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), the return value of `save` could represent a cached copy of the block's content to be shown only in case the plugin implementing the block is ever disabled. If left unspecified, the default implementation will save no markup in post content for the dynamic block, instead deferring this to always be calculated when the block is shown on the front of the site. From 9a2012b85eeea0ce0ac88ccc02074d809988b35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 23 May 2019 09:55:13 +0200 Subject: [PATCH 166/664] Testing: Skip pretest for e2e tests on Travis (#15775) --- .travis.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5fa24d4faf998c..7ff3c9b0445020 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,7 +87,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (2/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -95,7 +95,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (3/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -103,7 +103,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Admin with plugins) (4/4) env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -111,7 +111,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (1/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -119,7 +119,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (2/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -127,7 +127,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (3/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -135,7 +135,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - name: E2E tests (Author without plugins) (4/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= @@ -143,7 +143,7 @@ jobs: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - - npm run test-e2e -- --ci --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) + - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) allow_failures: - name: PHP unit tests (PHP 5.3) From 99a853df4f90f1f126a72c1319d12701da8deba5 Mon Sep 17 00:00:00 2001 From: Tom J Nowell <contact@tomjn.com> Date: Thu, 23 May 2019 09:46:27 +0100 Subject: [PATCH 167/664] Fixeds a renamed function in the block-editor component readme (#15713) the import for registerCoreBlocks was changed but the function call hadn't --- packages/block-editor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 40f395faecf3cb..e2dcd26cd5095d 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -56,7 +56,7 @@ In the example above, there's no registered block type, in order to use the bloc ```js import { registerCoreBlocks } from '@wordpress/block-library'; -registerCoreBlockTypes(); +registerCoreBlocks(); // Make sure to load the block stylesheets too // import '@wordpress/block-library/build-style/style.css'; From e3afe626204ac43ac95b4b3a1d1be26e734caee9 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 23 May 2019 11:19:48 +0200 Subject: [PATCH 168/664] Move Picker and BottomSheet to the @wordpress/components package (#15750) --- .../src/components/index.native.js | 4 --- .../components/media-upload/index.native.js | 2 +- .../mobile/bottom-sheet/button.native.js | 32 ------------------- .../block-library/src/image/edit.native.js | 2 +- packages/components/src/index.native.js | 4 +++ .../src/mobile/bottom-sheet/button.native.js | 30 +++++++++++++++++ .../src}/mobile/bottom-sheet/cell.native.js | 2 +- .../bottom-sheet/cellStyles.android.scss | 0 .../mobile/bottom-sheet/cellStyles.ios.scss | 0 .../src}/mobile/bottom-sheet/index.native.js | 0 .../keyboard-avoiding-view.native.js | 0 .../mobile/bottom-sheet/picker-cell.native.js | 2 +- .../mobile/bottom-sheet/styles.native.scss | 0 .../mobile/bottom-sheet/switch-cell.native.js | 2 +- .../src}/mobile/picker/index.android.js | 6 +++- .../src}/mobile/picker/index.ios.js | 23 ++++++------- .../format-library/src/link/modal.native.js | 2 +- 17 files changed, 57 insertions(+), 54 deletions(-) delete mode 100644 packages/block-editor/src/components/mobile/bottom-sheet/button.native.js create mode 100644 packages/components/src/mobile/bottom-sheet/button.native.js rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/cell.native.js (98%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/cellStyles.android.scss (100%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/cellStyles.ios.scss (100%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/index.native.js (100%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/keyboard-avoiding-view.native.js (100%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/picker-cell.native.js (89%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/styles.native.scss (100%) rename packages/{block-editor/src/components => components/src}/mobile/bottom-sheet/switch-cell.native.js (95%) rename packages/{block-editor/src/components => components/src}/mobile/picker/index.android.js (94%) rename packages/{block-editor/src/components => components/src}/mobile/picker/index.ios.js (64%) diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 47683399491f17..ae73aa0e4fb316 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -21,7 +21,3 @@ export { default as DefaultBlockAppender } from './default-block-appender'; // State Related Components export { default as BlockEditorProvider } from './provider'; - -// Mobile Editor Related Components -export { default as BottomSheet } from './mobile/bottom-sheet'; -export { default as Picker } from './mobile/picker'; diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index 9efed29dc1e821..14ed5d8dbe6236 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -12,7 +12,7 @@ import { * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Picker } from '@wordpress/block-editor'; +import { Picker } from '@wordpress/components'; export const MEDIA_TYPE_IMAGE = 'image'; export const MEDIA_TYPE_VIDEO = 'video'; diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/button.native.js b/packages/block-editor/src/components/mobile/bottom-sheet/button.native.js deleted file mode 100644 index c8011f80511f83..00000000000000 --- a/packages/block-editor/src/components/mobile/bottom-sheet/button.native.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * External dependencies - */ -import { TouchableOpacity, View, Text } from 'react-native'; - -/** - * Internal dependencies - */ -import styles from './styles.scss'; - -export default function Button( props ) { - const { - onPress, - disabled, - text, - color, - } = props; - - return ( - <TouchableOpacity - accessible={ true } - onPress={ onPress } - disabled={ disabled } - > - <View style={ { flexDirection: 'row', justifyContent: 'center' } }> - <Text style={ { ...styles.buttonText, color } }> - { text } - </Text> - </View> - </TouchableOpacity> - ); -} diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index b2ca7452b8c8b8..f45c455f1a8abf 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -15,6 +15,7 @@ import { isEmpty } from 'lodash'; * WordPress dependencies */ import { + BottomSheet, Toolbar, ToolbarButton, } from '@wordpress/components'; @@ -25,7 +26,6 @@ import { RichText, BlockControls, InspectorControls, - BottomSheet, } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index b68e70efb00118..f3bd5ba6951b16 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -18,3 +18,7 @@ export { default as withFocusOutside } from './higher-order/with-focus-outside'; export { default as withFocusReturn } from './higher-order/with-focus-return'; export { default as withNotices } from './higher-order/with-notices'; export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; + +// Mobile Components +export { default as BottomSheet } from './mobile/bottom-sheet'; +export { default as Picker } from './mobile/picker'; diff --git a/packages/components/src/mobile/bottom-sheet/button.native.js b/packages/components/src/mobile/bottom-sheet/button.native.js new file mode 100644 index 00000000000000..cc9c5cdae22ab3 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/button.native.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { TouchableOpacity, View, Text } from 'react-native'; + +/** + * Internal dependencies + */ +import styles from './styles.scss'; + +const BottomSheetButton = ( { + onPress, + disabled, + text, + color, +} ) => ( + <TouchableOpacity + accessible={ true } + onPress={ onPress } + disabled={ disabled } + > + <View style={ { flexDirection: 'row', justifyContent: 'center' } }> + <Text style={ { ...styles.buttonText, color } }> + { text } + </Text> + </View> + </TouchableOpacity> +); + +export default BottomSheetButton; diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js b/packages/components/src/mobile/bottom-sheet/cell.native.js similarity index 98% rename from packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js rename to packages/components/src/mobile/bottom-sheet/cell.native.js index 689aeeb38dbec7..cebfc6a91c1826 100644 --- a/packages/block-editor/src/components/mobile/bottom-sheet/cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/cell.native.js @@ -17,7 +17,7 @@ import { __, _x, sprintf } from '@wordpress/i18n'; import styles from './styles.scss'; import platformStyles from './cellStyles.scss'; -export default class Cell extends Component { +export default class BottomSheetCell extends Component { constructor( props ) { super( ...arguments ); this.state = { diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/cellStyles.android.scss b/packages/components/src/mobile/bottom-sheet/cellStyles.android.scss similarity index 100% rename from packages/block-editor/src/components/mobile/bottom-sheet/cellStyles.android.scss rename to packages/components/src/mobile/bottom-sheet/cellStyles.android.scss diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/cellStyles.ios.scss b/packages/components/src/mobile/bottom-sheet/cellStyles.ios.scss similarity index 100% rename from packages/block-editor/src/components/mobile/bottom-sheet/cellStyles.ios.scss rename to packages/components/src/mobile/bottom-sheet/cellStyles.ios.scss diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js similarity index 100% rename from packages/block-editor/src/components/mobile/bottom-sheet/index.native.js rename to packages/components/src/mobile/bottom-sheet/index.native.js diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/keyboard-avoiding-view.native.js b/packages/components/src/mobile/bottom-sheet/keyboard-avoiding-view.native.js similarity index 100% rename from packages/block-editor/src/components/mobile/bottom-sheet/keyboard-avoiding-view.native.js rename to packages/components/src/mobile/bottom-sheet/keyboard-avoiding-view.native.js diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/picker-cell.native.js b/packages/components/src/mobile/bottom-sheet/picker-cell.native.js similarity index 89% rename from packages/block-editor/src/components/mobile/bottom-sheet/picker-cell.native.js rename to packages/components/src/mobile/bottom-sheet/picker-cell.native.js index 888acd3db54b22..50a775f25728b1 100644 --- a/packages/block-editor/src/components/mobile/bottom-sheet/picker-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/picker-cell.native.js @@ -4,7 +4,7 @@ import Cell from './cell'; import Picker from '../picker'; -export default function PickerCell( props ) { +export default function BottomSheetPickerCell( props ) { const { options, onChangeValue, diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss similarity index 100% rename from packages/block-editor/src/components/mobile/bottom-sheet/styles.native.scss rename to packages/components/src/mobile/bottom-sheet/styles.native.scss diff --git a/packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js similarity index 95% rename from packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js rename to packages/components/src/mobile/bottom-sheet/switch-cell.native.js index 2682b82668c60b..011f5798499008 100644 --- a/packages/block-editor/src/components/mobile/bottom-sheet/switch-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js @@ -12,7 +12,7 @@ import { __, _x, sprintf } from '@wordpress/i18n'; */ import Cell from './cell'; -export default function SwitchCell( props ) { +export default function BottomSheetSwitchCell( props ) { const { value, onValueChange, diff --git a/packages/block-editor/src/components/mobile/picker/index.android.js b/packages/components/src/mobile/picker/index.android.js similarity index 94% rename from packages/block-editor/src/components/mobile/picker/index.android.js rename to packages/components/src/mobile/picker/index.android.js index 50f264adaa55d7..3fad1cc2b23bf0 100644 --- a/packages/block-editor/src/components/mobile/picker/index.android.js +++ b/packages/components/src/mobile/picker/index.android.js @@ -9,7 +9,11 @@ import { View } from 'react-native'; */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { BottomSheet } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import BottomSheet from '../bottom-sheet'; export default class Picker extends Component { constructor() { diff --git a/packages/block-editor/src/components/mobile/picker/index.ios.js b/packages/components/src/mobile/picker/index.ios.js similarity index 64% rename from packages/block-editor/src/components/mobile/picker/index.ios.js rename to packages/components/src/mobile/picker/index.ios.js index 0a0b3d8a2dd410..2cf9798b28e9c0 100644 --- a/packages/block-editor/src/components/mobile/picker/index.ios.js +++ b/packages/components/src/mobile/picker/index.ios.js @@ -15,17 +15,18 @@ class Picker extends Component { const labels = options.map( ( { label } ) => label ); const fullOptions = [ __( 'Cancel' ) ].concat( labels ); - ActionSheetIOS.showActionSheetWithOptions( { - options: fullOptions, - cancelButtonIndex: 0, - }, - ( buttonIndex ) => { - if ( buttonIndex === 0 ) { - return; - } - const selected = options[ buttonIndex - 1 ]; - onChange( selected.value ); - }, + ActionSheetIOS.showActionSheetWithOptions( + { + options: fullOptions, + cancelButtonIndex: 0, + }, + ( buttonIndex ) => { + if ( buttonIndex === 0 ) { + return; + } + const selected = options[ buttonIndex - 1 ]; + onChange( selected.value ); + }, ); } diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index 954bd6f1da5d7a..d796dd2bc53a9d 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -9,9 +9,9 @@ import { Platform } from 'react-native'; */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { BottomSheet } from '@wordpress/block-editor'; import { prependHTTP } from '@wordpress/url'; import { + BottomSheet, withSpokenMessages, } from '@wordpress/components'; import { From 1a20e15a615ecb344cfb0dd8b614982910240909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Thu, 23 May 2019 10:31:39 +0100 Subject: [PATCH 169/664] Implement support for nested lists on GB-mobile. (#15566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add indent and outdent commands. * Add code for handling delete on nested lists. * Handle case when full removal of the content happens. * Update record creation and change propagation. * If already processed don’t go and delete anything more. * Remove unused/invalid method. * Update documentation. * No longer needed dedicate list-edit for native. --- .../src/components/rich-text/index.native.js | 94 ++++++++++++++++--- .../components/rich-text/list-edit.native.js | 55 ----------- 2 files changed, 79 insertions(+), 70 deletions(-) delete mode 100644 packages/block-editor/src/components/rich-text/list-edit.native.js diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index d47ace5c39144b..7431817743a6e9 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -26,9 +26,11 @@ import { split, toHTMLString, insert, + __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR, __unstableInsertLineSeparator as insertLineSeparator, __unstableIsEmptyLine as isEmptyLine, isCollapsed, + remove, } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { BACKSPACE } from '@wordpress/keycodes'; @@ -104,7 +106,6 @@ export class RichText extends Component { this.onBlur = this.onBlur.bind( this ); this.onTextUpdate = this.onTextUpdate.bind( this ); this.onContentSizeChange = this.onContentSizeChange.bind( this ); - this.onFormatChangeForceChild = this.onFormatChangeForceChild.bind( this ); this.onFormatChange = this.onFormatChange.bind( this ); this.formatToValue = memize( this.formatToValue.bind( this ), @@ -157,14 +158,9 @@ export class RichText extends Component { } /** - * Creates a RichText value "record" from native content and selection + * Creates a RichText value "record" from the current content and selection * information * - * @param {string} currentContent The content (usually an HTML string) from - * the native component. - * @param {number} selectionStart The start of the selection. - * @param {number} selectionEnd The end of the selection (same as start if - * cursor instead of selection). * * @return {Object} A RichText value with formats and selection. */ @@ -256,10 +252,6 @@ export class RichText extends Component { } ).map( ( name ) => gutenbergFormatNamesToAztec[ name ] ).filter( Boolean ); } - onFormatChangeForceChild( record ) { - this.onFormatChange( record, true ); - } - onFormatChange( record ) { const { start, end, activeFormats = [] } = record; const changeHandlers = pickBy( this.props, ( v, key ) => @@ -362,18 +354,18 @@ export class RichText extends Component { if ( event.shiftKey ) { this.needsSelectionUpdate = true; const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; - this.onFormatChangeForceChild( insertedLineBreak ); + this.onFormatChange( insertedLineBreak ); } else if ( this.onSplit && isEmptyLine( currentRecord ) ) { this.onSplit( currentRecord ); } else { this.needsSelectionUpdate = true; const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; - this.onFormatChange( insertedLineSeparator, ! this.firedAfterTextChanged ); + this.onFormatChange( insertedLineSeparator ); } } else if ( event.shiftKey || ! this.onSplit ) { this.needsSelectionUpdate = true; const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; - this.onFormatChangeForceChild( insertedLineBreak ); + this.onFormatChange( insertedLineBreak ); } else { this.onSplit( currentRecord ); } @@ -390,6 +382,78 @@ export class RichText extends Component { const keyCode = BACKSPACE; // TODO : should we differentiate BACKSPACE and DELETE? const isReverse = keyCode === BACKSPACE; + this.lastEventCount = event.nativeEvent.eventCount; + this.comesFromAztec = true; + this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; + const value = this.createRecord(); + const { replacements, text, start, end } = value; + let newValue; + + // Always handle full content deletion ourselves. + if ( start === 0 && end !== 0 && end >= value.text.length ) { + newValue = remove( value, start, end ); + this.props.onChange( newValue ); + this.forceSelectionUpdate( 0, 0 ); + return; + } + + if ( this.multilineTag ) { + if ( keyCode === BACKSPACE ) { + const index = start - 1; + + if ( text[ index ] === LINE_SEPARATOR ) { + const collapsed = isCollapsed( value ); + + // If the line separator that is about te be removed + // contains wrappers, remove the wrappers first. + if ( collapsed && replacements[ index ] && replacements[ index ].length ) { + const newReplacements = replacements.slice(); + + newReplacements[ index ] = replacements[ index ].slice( 0, -1 ); + newValue = { + ...value, + replacements: newReplacements, + }; + } else { + newValue = remove( + value, + // Only remove the line if the selection is + // collapsed, otherwise remove the selection. + collapsed ? start - 1 : start, + end + ); + } + } + } else if ( text[ end ] === LINE_SEPARATOR ) { + const collapsed = isCollapsed( value ); + + // If the line separator that is about te be removed + // contains wrappers, remove the wrappers first. + if ( collapsed && replacements[ end ] && replacements[ end ].length ) { + const newReplacements = replacements.slice(); + + newReplacements[ end ] = replacements[ end ].slice( 0, -1 ); + newValue = { + ...value, + replacements: newReplacements, + }; + } else { + newValue = remove( + value, + start, + // Only remove the line if the selection is + // collapsed, otherwise remove the selection. + collapsed ? end + 1 : end, + ); + } + } + + if ( newValue ) { + this.onFormatChange( newValue ); + return; + } + } + const empty = this.isEmpty(); if ( onMerge ) { @@ -764,7 +828,7 @@ export class RichText extends Component { onTagNameChange={ onTagNameChange } tagName={ tagName } value={ record } - onChange={ this.onFormatChangeForceChild } + onChange={ this.onFormatChange } /> ) } { isSelected && ( diff --git a/packages/block-editor/src/components/rich-text/list-edit.native.js b/packages/block-editor/src/components/rich-text/list-edit.native.js deleted file mode 100644 index 3f0cdf0fa6e349..00000000000000 --- a/packages/block-editor/src/components/rich-text/list-edit.native.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * WordPress dependencies - */ - -import { Toolbar } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { - __unstableChangeListType as changeListType, - __unstableIsListRootSelected as isListRootSelected, - __unstableIsActiveListType as isActiveListType, -} from '@wordpress/rich-text'; - -/** - * Internal dependencies - */ - -import BlockFormatControls from '../block-format-controls'; - -export const ListEdit = ( { - onTagNameChange, - tagName, - value, - onChange, -} ) => ( - <BlockFormatControls> - <Toolbar - controls={ [ - onTagNameChange && { - icon: 'editor-ul', - title: __( 'Convert to unordered list' ), - isActive: isActiveListType( value, 'ul', tagName ), - onClick() { - onChange( changeListType( value, { type: 'ul' } ) ); - - if ( isListRootSelected( value ) ) { - onTagNameChange( 'ul' ); - } - }, - }, - onTagNameChange && { - icon: 'editor-ol', - title: __( 'Convert to ordered list' ), - isActive: isActiveListType( value, 'ol', tagName ), - onClick() { - onChange( changeListType( value, { type: 'ol' } ) ); - - if ( isListRootSelected( value ) ) { - onTagNameChange( 'ol' ); - } - }, - }, - ].filter( Boolean ) } - /> - </BlockFormatControls> -); From ce29311512c12934a7b8f92a46d4bc3e887ae080 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Thu, 23 May 2019 13:38:52 +0200 Subject: [PATCH 170/664] Close block settings menu after removing block (#15543) * Close block settings menu after removing block * Undo change to BlockToolbar component * Also close settings menu after other items See #15313 * Do not show BlockSettingsMenu if block is invalid * Use flow() * Block Editor: Select previous / next only when exists * Remove now unneeded isValid check --- .../src/components/block-settings-menu/index.js | 10 +++++----- packages/block-editor/src/store/actions.js | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index ca9df214fae228..21899126bae2fa 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { castArray } from 'lodash'; +import { castArray, flow } from 'lodash'; /** * WordPress dependencies @@ -70,7 +70,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { { ! isLocked && canDuplicate && ( <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ onDuplicate } + onClick={ flow( onClose, onDuplicate ) } icon="admin-page" shortcut={ shortcuts.duplicate.display } > @@ -81,7 +81,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { <> <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ onInsertBefore } + onClick={ flow( onClose, onInsertBefore ) } icon="insert-before" shortcut={ shortcuts.insertBefore.display } > @@ -89,7 +89,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { </MenuItem> <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ onInsertAfter } + onClick={ flow( onClose, onInsertAfter ) } icon="insert-after" shortcut={ shortcuts.insertAfter.display } > @@ -108,7 +108,7 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { { ! isLocked && ( <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ onRemove } + onClick={ flow( onClose, onRemove ) } icon="trash" shortcut={ shortcuts.removeBlock.display } > diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 466a9cbeb96c26..876083194ef8b7 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -131,7 +131,9 @@ export function* selectPreviousBlock( clientId ) { clientId ); - yield selectBlock( previousBlockClientId, -1 ); + if ( previousBlockClientId ) { + yield selectBlock( previousBlockClientId, -1 ); + } } /** @@ -147,7 +149,9 @@ export function* selectNextBlock( clientId ) { clientId ); - yield selectBlock( nextBlockClientId ); + if ( nextBlockClientId ) { + yield selectBlock( nextBlockClientId ); + } } /** From 6b024aff2e7edc0690e51d56276ba7ad57867d68 Mon Sep 17 00:00:00 2001 From: Ian Dunn <ian@iandunn.name> Date: Thu, 23 May 2019 04:39:24 -0700 Subject: [PATCH 171/664] Docs: Clarify class naming convention. (#15764) The paragraph description is difficult to parse, even if you already understand the intent behind it. A straight-forward example using generic labels is a much clearer way to communicate. --- docs/contributors/coding-guidelines.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/contributors/coding-guidelines.md b/docs/contributors/coding-guidelines.md index ab3258f7f8b512..dac400e51f663e 100644 --- a/docs/contributors/coding-guidelines.md +++ b/docs/contributors/coding-guidelines.md @@ -8,7 +8,12 @@ This living document serves to prescribe coding guidelines specific to the Guten To avoid class name collisions, class names **must** adhere to the following guidelines, which are loosely inspired by the [BEM (Block, Element, Modifier) methodology](https://en.bem.info/methodology/). -All class names assigned to an element must be prefixed with the name of the package, followed by the name of the directory in which the component resides. Any descendent of the component's root element must append a dash-delimited descriptor, separated from the base by two consecutive underscores `__`. The root element is considered to be the highest ancestor element returned by the default export in the `index.js`. Notably, if your folder contains multiple files, each with their own default exported component, only the element rendered by that of `index.js` can be considered the root. All others should be treated as descendents. +All class names assigned to an element must be prefixed with the name of the package, followed by a dash and the name of the directory in which the component resides. Any descendent of the component's root element must append a dash-delimited descriptor, separated from the base by two consecutive underscores `__`. + +* Root element: `package-directory` +* Child elements: `package-directory__descriptor-foo-bar` + +The root element is considered to be the highest ancestor element returned by the default export in the `index.js`. Notably, if your folder contains multiple files, each with their own default exported component, only the element rendered by that of `index.js` can be considered the root. All others should be treated as descendents. **Example:** From 9f04d785bc2055897d186e9287dfc9f6458c3819 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 23 May 2019 07:40:27 -0400 Subject: [PATCH 172/664] Block Editor: Avoid default block insertion if no default block type (#15786) * Block Editor: Select previous / next only when exists * Block Editor: Avoid default block insertion if no default block type * Testing: Verify graceful behavior of no-default block deletion --- packages/block-editor/src/store/actions.js | 8 ++++- .../e2e-tests/specs/block-deletion.test.js | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 876083194ef8b7..4dafaa291fcf31 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -637,7 +637,13 @@ export function selectionChange( clientId, attributeKey, startOffset, endOffset * @return {Object} Action object */ export function insertDefaultBlock( attributes, rootClientId, index ) { - const block = createBlock( getDefaultBlockName(), attributes ); + // Abort if there is no default block type (if it has been unregistered). + const defaultBlockName = getDefaultBlockName(); + if ( ! defaultBlockName ) { + return; + } + + const block = createBlock( defaultBlockName, attributes ); return insertBlock( block, index, rootClientId ); } diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index fb975fb61e88ce..6adcb01681bb2a 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -7,6 +7,7 @@ import { createNewPost, isInDefaultBlock, pressKeyWithModifier, + insertBlock, } from '@wordpress/e2e-test-utils'; const addThreeParagraphsToNewPost = async () => { @@ -130,4 +131,34 @@ describe( 'deleting all blocks', () => { // And focus is retained: expect( await isInDefaultBlock() ).toBe( true ); } ); + + it( 'gracefully removes blocks when the default block is not available', async () => { + // Regression Test: Previously, removing a block would not clear + // selection state when there were no other blocks to select. + // + // See: https://github.com/WordPress/gutenberg/issues/15458 + // See: https://github.com/WordPress/gutenberg/pull/15543 + await createNewPost(); + + // Unregister default block type. This may happen if the editor is + // configured to not allow the default (paragraph) block type, either + // by plugin editor settings filtering or user block preferences. + await page.evaluate( () => { + const defaultBlockName = wp.data.select( 'core/blocks' ).getDefaultBlockName(); + wp.data.dispatch( 'core/blocks' ).removeBlockTypes( defaultBlockName ); + } ); + + // Add and remove a block. + await insertBlock( 'Image' ); + await page.keyboard.press( 'Backspace' ); + + // Verify there is no selected block. + // TODO: There should be expectations around where focus is placed in + // this scenario. Currently, a focus loss occurs (not acceptable). + const selectedBlocksCount = await page.evaluate( () => { + return wp.data.select( 'core/block-editor' ).getSelectedBlockClientIds().length; + } ); + + expect( selectedBlocksCount ).toBe( 0 ); + } ); } ); From 392df98bfeedfe8615657448f62439c888649444 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 23 May 2019 14:08:39 +0200 Subject: [PATCH 173/664] [RN mobile] Accessibility: Handle the iOS z-gesture to exit modals and block selection (#15153) * Close the modal on z-gesture on iOS * Disable cancel and retry dialog if image is already uploaded * Fix long press disabled by TouchableWithoutFeedback wrapper * Updated image label for double tap and hold gesture * Remove long press gesture from image, since edit image function is not reachable anymore * Solve merge conflict * Adding long press gesture to image block on selected state * Fix lint issues --- .../block-library/src/image/edit.native.js | 59 ++++++++++--------- .../src/mobile/bottom-sheet/index.native.js | 1 + 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index f45c455f1a8abf..7ef23a0b0f9b89 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -211,22 +211,16 @@ class ImageEdit extends React.Component { this.setState( { showSettings: false } ); }; - const toolbarEditButton = ( - <MediaUpload mediaType={ MEDIA_TYPE_IMAGE } - onSelectURL={ this.onSelectMediaUploadOption } - render={ ( { open, getMediaOptions } ) => { - return ( - <Toolbar> - { getMediaOptions() } - <ToolbarButton - title={ __( 'Edit image' ) } - icon="edit" - onClick={ open } - /> - </Toolbar> - ); - } } > - </MediaUpload> + const getToolbarEditButton = ( open ) => ( + <BlockControls> + <Toolbar> + <ToolbarButton + title={ __( 'Edit image' ) } + icon="edit" + onClick={ open } + /> + </Toolbar> + </BlockControls> ); const getInspectorControls = () => ( @@ -276,11 +270,9 @@ class ImageEdit extends React.Component { } const imageContainerHeight = Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO; - - return ( + const getImageComponent = ( openMediaOptions, getMediaOptions ) => ( <TouchableWithoutFeedback accessible={ ! isSelected } - accessibilityLabel={ sprintf( /* translators: accessibility text. 1: image alt text. 2: image caption. */ __( 'Image block. %1$s. %2$s' ), @@ -289,14 +281,14 @@ class ImageEdit extends React.Component { ) } accessibilityRole={ 'button' } onPress={ this.onImagePressed } + onLongPress={ openMediaOptions } disabled={ ! isSelected } > <View style={ { flex: 1 } }> { getInspectorControls() } + { getMediaOptions() } { ( ! this.state.isCaptionSelected ) && - <BlockControls> - { toolbarEditButton } - </BlockControls> + getToolbarEditButton( openMediaOptions ) } <InspectorControls> <ToolbarButton @@ -326,16 +318,20 @@ class ImageEdit extends React.Component { return ( <View style={ { flex: 1 } } > - { ! imageWidthWithinContainer && <View style={ [ styles.imageContainer, { height: imageContainerHeight } ] } > - { this.getIcon( false ) } - </View> } + { ! imageWidthWithinContainer && + <View style={ [ styles.imageContainer, { height: imageContainerHeight } ] } > + { this.getIcon( false ) } + </View> } <ImageBackground + accessible={ true } + disabled={ ! isSelected } + accessibilityLabel={ alt } + accessibilityHint={ __( 'Double tap and hold to edit' ) } + accessibilityRole={ 'imagebutton' } style={ { width: finalWidth, height: finalHeight, opacity } } resizeMethod="scale" source={ { uri: url } } key={ url } - accessible={ true } - accessibilityLabel={ alt } > { isUploadFailed && <View style={ [ styles.imageContainer, { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)' } ] } > @@ -383,6 +379,15 @@ class ImageEdit extends React.Component { </View> </TouchableWithoutFeedback> ); + + return ( + <MediaUpload mediaType={ MEDIA_TYPE_IMAGE } + onSelectURL={ this.onSelectMediaUploadOption } + render={ ( { open, getMediaOptions } ) => { + return getImageComponent( open, getMediaOptions ); + } } + /> + ); } } diff --git a/packages/components/src/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js index 6caed0b1da4854..9106709cea25ef 100644 --- a/packages/components/src/mobile/bottom-sheet/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/index.native.js @@ -135,6 +135,7 @@ class BottomSheet extends Component { swipeDirection="down" onMoveShouldSetResponder={ panResponder.panHandlers.onMoveShouldSetResponder } onMoveShouldSetResponderCapture={ panResponder.panHandlers.onMoveShouldSetResponderCapture } + onAccessibilityEscape={ this.props.onClose } > <KeyboardAvoidingView behavior={ Platform.OS === 'ios' && 'padding' } From 514e61298d5c056ef6ec4d3859b959dc46735dec Mon Sep 17 00:00:00 2001 From: Ryan Welcher <me@ryanwelcher.com> Date: Thu, 23 May 2019 12:22:50 -0400 Subject: [PATCH 174/664] Add SlotFill documentation to the handbook (#15738) --- ...in-block-settings-menu-item-screenshot.png | Bin 0 -> 88235 bytes .../assets/plugin-more-menu-item.png | Bin 0 -> 44489 bytes .../assets/plugin-post-publish-panel.png | Bin 0 -> 16635 bytes .../plugin-post-status-info-location.png | Bin 0 -> 20919 bytes .../assets/plugin-pre-publish-panel.png | Bin 0 -> 21254 bytes .../assets/plugin-sidebar-closed-state.png | Bin 0 -> 6773 bytes .../assets/plugin-sidebar-more-menu-item.gif | Bin 0 -> 52542 bytes .../assets/plugin-sidebar-open-state.png | Bin 0 -> 10854 bytes .../developers/slotfills/README.md | 112 ++++++++++++++++++ .../plugin-block-settings-menu-item.md | 27 +++++ .../slotfills/plugin-more-menu-item.md | 25 ++++ .../slotfills/plugin-post-publish-panel.md | 26 ++++ .../slotfills/plugin-post-status-info.md | 26 ++++ .../slotfills/plugin-pre-publish-panel.md | 26 ++++ .../plugin-sidebar-more-menu-item.md | 41 +++++++ .../developers/slotfills/plugin-sidebar.md | 35 ++++++ docs/manifest-devhub.json | 48 ++++++++ docs/toc.json | 9 ++ 18 files changed, 375 insertions(+) create mode 100644 docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png create mode 100644 docs/designers-developers/assets/plugin-more-menu-item.png create mode 100644 docs/designers-developers/assets/plugin-post-publish-panel.png create mode 100644 docs/designers-developers/assets/plugin-post-status-info-location.png create mode 100644 docs/designers-developers/assets/plugin-pre-publish-panel.png create mode 100644 docs/designers-developers/assets/plugin-sidebar-closed-state.png create mode 100644 docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif create mode 100644 docs/designers-developers/assets/plugin-sidebar-open-state.png create mode 100644 docs/designers-developers/developers/slotfills/README.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-more-menu-item.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-post-status-info.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md create mode 100644 docs/designers-developers/developers/slotfills/plugin-sidebar.md diff --git a/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png b/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..bc33b4fd205a63e08e4dbb3533226a656a53bd81 GIT binary patch literal 88235 zcmaI8V{~QR5-uFu?%1|%TOHd@IyO4CZQC|GwvCQ$bUL`Z`<(Z^=Z^7>@5dh5SZh|T za@Blh!WHEu;9;;~fPjGDr6fg_fq+1Jfq;OEp+ErNbSo}@0sH|o7m^bK0;-RNeKQ0H zd?qxORF(q*@+1cW@(%(6dIfyte*y&L!UzO(W&i}lodyJi>5$c-#0&TV+Cfs&2?z)l z_0J0!C?g9C2uKh}N>oVI9rz*}RtIhAqfa56{)Ze?f?QH4ltx(PFKT$W59##z;B#1& z0ZBPIRQ(m%JF%Z^Dv1fK1nCi^`MPqfDBW0CWLD!9l!(a^1(Dx7DcdJRK6=yF(zAUU z`tCGdrXXEsvPQ1oe0Nx!jyTL`vOEe}T2h)e)qLqpm%{4WLMV~4vL4gb&A*Oq1`@!- z#;RW~<ik@mP;?XKeP8l`)fKl;M|0x#i-OJmx?k8~ksLXr7A+}kBySq~tq@#UDuTfL zDI>cq-qPgkvHD5}L$84Nx-Qb5lc4~=U+G-_BI{rnqpWe8n<b9h;glWz?7G`X;-J^N zD@d(_*5q-}p41;K4hTgML=p(P=_`mrOO;`^{>{dXy6@Ru^&W8ddAU^A=Y@Olt?3np zW#{w|jbl(GJ~Bp3A@N(&g0%FRQmN~=`*379xQrT^4Vuc5=*yK#^_6%-*cH1^CN4gX zUQwlL8j@im;D71l(V^5?z>BvdVzq;PputFKDjC{s9||z!^5;6z9!leN6ni(Fzh$yO z-VEnHVk(Y~yO4Hh;9v<g*SD_FJ977$94QuBEz)nKr3s5Ud0oo;_7{#m8NVj=WT8O% z*__?@pAJgAM&A)oHyIV);6M}pOW8JFu<|A+*#I2GP@Uriht<XeOH;H0J)t*(zLcU` zx9g(;|Dp2df;j?KIfW9RR%t%^nMc$r%dRbsl9o9}Z(7?a_nWhxUa;_7dngG*f#!FR z)>;M8&L~E!im|bw%bV7uxSNy0`;jL*5a}r+X0`AC3YIXS1M0+?`kZ=^_Y?ypKQeRH zbovH5ztr!=>h0$xcLiN(h{@E3IxG}6i%l8Af|gLyZgAh*f^LeZ!q4Hfb5&|?Z|8^f zlQ)-uqb*98_3$tMXpAu6Njfk1m=7m-tDy0@DD1$cpPQKiLn=DUxsoMw&k_R9S=Hk8 z7b^PD1G5X|b{}QH#4Nm@X)YIbt5L@{<2OYGofph8GWaUQ$p6)l3#!FOz4?!|z}r-G zknPo?@4S2l%8Gaj@Ey<B3SmbH*g<H?D@ggs;!qcz?#KmPprpN`(qW1Uuu?UaW}#7o zUN|`>`4TA}60ljX2-(nn$VG4bY)raY9v>zh`QfK?Udl(mA@4I*yDlXz<Ns@X06p~~ zGY;Gy++~pFjYcBq`zoub-sJhuY{H)r*VQf3g8c>-$&PxM<L2?S_L@m8^qqh`tQH)p z%;&!D&2-^yv9Q%WCJ9^?UCCopXj+4ylRT!6(YxVTw4bN7GV-)Dn_mvQ;k@jON)muY z@8?dvWYOMN`u49?0zoT+!Fg861xU{R*oe$pd}#eCu!@_;JJa`||08xI%F|n}b{r!n zkUYLx3)S8HT85xecx2o;AxT8bL+I&&f9r9%k9_EO`+c&TrqjG+$8Dzeojd%-m&l(2 z^LNy{1Ds#5Eo{JV|33K8ts*`NYq}{S7L`sV+y7wzi+uKCo2C94do5FE8F#t6%QvC- zqOn#0&arg~Tb7<7LYvPipWfVce)L1D{c)X#&;U7HyEeUx&*pNriR{CQMvL-JMR<VS z`Y7m~%QpmaG@aOd<{%ozaY1R*y8x}&V{W4ve_`?-yIcMIn_vLRCIa3<7oOEpV*6An zo}$XXoa8MbTLhFsK!SJGq7EKj62PNA!f1AVj1)agq}Cqjbvm-MS)Ny<>+<<v3|o3` zL<RYER~$W_p-qUvghEUoxVi_NFy;n#&QGM9KO3+MpW$O(Gt$||QP(*Y44<_1?VIAw z%-d$O811DLO|0C}=L+O%4_A(PfB%lJSJ>1n2d(dQ5;4pYle%A!Vl{Zj>?t1YN{1Er zKhC5koA+xZC%li2(bdyEY<ZMi<kV!-?1d#{^(HKETQ_QK^r+q;tKIr_nGp|tgf0`U zHEOEl+UvDOi9W-!s~p}+>YEbFenA;ltwJn$=+doVk=B@61Bn9;PdM%Z_Vo_8`NJ}o z?;bJ<pQpySJtm%Zi=H(Mk7-{KhDV_+jZml+z*heoHElBlVWap!XZB>|PjDe>AgyXg zYt7-yBz(CmXZ@691$|(deg!6Co~Gf`T~sq+S5s|D<@!~p*Ee6niu*V^wAmFa7Zmbm z@X>ka<F9-%3!Q;uybh=7xE$8_hXNojBh52!vqTBhj&iR%K&~_Vlr^%*j?_P~((g~3 z`k5xxp$zu}oFY=-e!iL0`M9T_i95?mq+yc0`rf}b)>qBX){7!5n$gVF{hiDh`hS*f z;tjyK%jv(F?pA+G&h8=sNinRMIcg=k8oKOW$aa?HVzSGIjGQ)JHvMd}>xfVGlzYAS z`8d;M^Jt10wdQFgb?%VIW=3jlU6qq>r{iEB=SOW<a!bcbMa*_y=B9p`!36QY(Ko0M zwYj-Dn)T4EUq3p@{|CVFE%?AZIaj*rjnC&W*o6M1Ab@0NC&8TI_SgjjmtEVzLwlOb z7lA6Kbinf2SR6<yt&d#$GYv^Lz#IP)uLL{EW*u2L*6AEsasP*yWS<Rm-m^`MTxbNz zzq$e}&&mV*kdukWr55&o%?X&)papE!i+!U+<PT4OGa-Ntf3S>fAkUep7+f0B|8n+M z=ZxAQCw!T<3;93)!|s0#VM-pT``An(2A%kS>7~&D=uNepD*kVvAq+61Fvw^v#wu*7 z>f+t&m53qBE~~0wjuW^QtO`(-6$X)$&g<xSotcyo6Kk!i=6ODSFPQC-NmN@A9blP$ zZ>Mn;fI@^#=X5vtB(DJf@ef6dT(FEWuby=M7pVh3lS9OH3e%=_S}3&luU%&E<S!;o zmy7lLkk_HnH#lY(<M+v%4aUz~Ud2fFk1<;(eD`ApSpSuv;65~K=re8dcuI}d{cist zFvFwI@#Q!AX?YAhZm=j=;LG%QGvBsb@-I{Y<*MdpiLrj?m6dDMKNZiLFi8UXqTjw3 z(=78=mAN0K`*l5?#a(Q6ZJu)!_HJMOyf`^pAM8TnyB$J}8c=8ezMm&(7W+r@8Gq=) z-Lv(%V0d`cM7+3Jn+0@yQ4rks?dzFvVhHcr_BL)C!<$-thkPR7EXrS9A64k$)t?Qs znY3H;IUS)Xpr-zCJ=><UI-EDRt+KYZUXk#7KR?}h9wHO<ZD?Z}$n3qCEYIJ${0)4T z&1@-rzY@dtulP2=G6uaqE@K;kzRd?QRl^jIjrhQ8tk}4)X^UYGr)+jVPmHNhmG}DK zHy4yvMv7Y*yMB&x)+*r+hj{g_%8)d~ckXz1Lh6{0Tp{FE>pe{hd;rfIygXGa)doIi zC3<zo1ITkuF84v>OvU0^|HrBVT!r(vy3EaB%n;2>T?d-p(|(=N1cGdP<6ZD&`yQPz zEZ*ns+GQ+zZFk)Y+>q`B%|6r6&`?0e`gpzd%Eq*olt6epht|vt#ytgyg#T#0rq#SL z3`M}RL*EJL=+orzh^9pP*D)CNL8kWj1kBB8sM<62HB9b^28SJ1(!2}=RRbZNH^k8) zMpxI?2L{mMfVG}hZxb0e_Ux!0>oP1`oiLgET_Dm$AP^a=O~ls&L(v_=zMpq7eC*ci z%MX+_s;cRI{rY&heN7Y?#$MO+sJ61$#bWEK*q7OF44QwTy}tu2`dqBsYq*U%0^V;s z+%VewEIlDTarMdb7N{NyS5xaf8T+qKHlYO^-Q@K5gIs^nNo1o*WgnjYw0XH}-C^N` z-gWZsa-eUG48S0DI354$kSzp^)z8+BJ$V4H!HbZ?!ItmvA_W#QS$lZ!<a-<ZSrx(Q z&WWsJ9w%VEo+RP;E5WqVw862=SqtmY-P)&>#r)G-YVlZ9ntGp4Tf-UX`fZNn*M=d> z2w2RA*>Mg&y?m}u2VCA)HEgZE`kqJTU!R;_zRx}GPzvd|Z{vC7?S~d=Mj9~xcn%KX zEbg|G<jv}Xz;pzId3D%pmbOzN@WpmIXUsL<pdDzCgBUh%!K73f%Pe~Zqrs8ApGluG z`fkX;7P84X#T^U`mL;JdXhL5vuhW$RC8x<v9|YQp`hk_}Rm9Tyhwnq#`Taop!uI>A z#r)n7=_v2}7Lvx7qaEbRBIY%Lswy=G{kx>%Vt%66pZmTq$j-joJ+N&P8?Kk&Tx@Ky zL@44JJkIWSeSWcT87Wr+Z){<}&~mrDeJPhaLb`<B=YImhxixu|#6KR%_}5^bv$oz4 z&3$i|mhUl{_i7Czgv+nCaQl{ay%APz>nLe0P(XGInDnqE8=IPfz-hoh0;EWtgd1uO z`@YlF{f;;-vEA$;(QG#3Yn!f99qm|}@Z*XHxxNjc-aWbVcuWQ-sP!k~tg6b<t9M3z zS<WIKhBTOceU0{h2_*K@>telFQcr7eZJBX(Zy59`zxZZb9V~{L<Eg+q)+2(ZUd7V0 z#z^r`+}?*`-6PAY1)9`&e0`04heUjTn~)m3#y&`$M+o#weBB^G+Id0d0ZmMh<2S3S ztud>F3QO;zj~nA-gA%C@jd^=psMkM$`LU_F(1cUd&(v$471%(oV-7kTC=jj#d(J(m zWA2cxqVS0j`BMlz0GQ<Sn(ex2=i}sXo5kTl5@7$Gyyh#6&AEPhP}dq?uFmgYZuea> zjmV?mWnZopCXrzaAMK-Vg2|*kIe9*Itoh*+_06fQespM99SwI9^}Co2!~L6B1iPVF z6QAL@13<w9A_oZybNNFX<S%i!3=8FdrS-dmY^nq@HowEjhKzRJ{!Vuyf6AFr+`gXx z{q1BA#H7cU&DH9M341TQE4De=w2#;MEYzZ85BIsEU5G)a`{n(Yl|!Km1}i@bL?h;O z_ThG-=g4>@{uLu+M@NU**xeEyj~3j?N~<|yDx!H;tFhbW=>wd58>92Qdm=*ud?TB~ zK*;T7FxNjhb1_oz-ecxbc|y=kg-&r6&A9$`(XOQ<qng-OhG(`B+~ASpvIt@X4ejH{ zWEuNjk$IXQ;;U>X^OqbbFk5~6#hrSuR=*fe<Gm3Y19`Dy^Bl{%7M=d;x%0Lg3WBN7 zoA{t^9XK!p`h|pGf;(fzsDGnH8Z7Yrea=*{jn`~rQx%-rhWpJ8Q5Vv&mOOsi#nq9O z@L;+nPBym-_D#a5s!_V;;^aDEUD-WF<Z<9a*Gr;gn%o}WfvHNgL#DKL(*Lb9o7h1v zSG?GL&m{jF8xb@eGSV@nm_2r-+MWlunc{XvNz>?gJ<cGbGGWCG>W}lnuLn@<^{e%b zi@-qF^iB?4KfYs?STJPpz<Qgy3*Ho(<_<Uo-9+?1@2~{iK~&hakm}UetSK6c{kQT# zfNs*iZpUMHMNNM@NK_aAb-m)#-P85IX*eHV0Z}RAT1ihds~E$%?&8B=?)PWgkBGE? zc69sw*t(NXuc3O?s31`9vm8=6T?N`^l&#4@K>gpCTAd~M`!NTHx4_0bvL%|@d3~d( zq@N{Bj)DMx4))XFb!Ef#P_Gx-&mcQ8kz`WXP6n3nXM_tkqE^&DwT}VHUui(|qZ!43 z8E|nq%rcnk2CAF$aJgRZv??Ul^lZL`IsE82GXE!|3jQX#0lWWxIE>}pxLb=qy@f;4 ze_*PeFHPP$cL0K%F&3X8YQWMe!=i4EB1#GNPXu*BG1!q>&T=u*{nrit7G-_B(7TiH z_ck1h9eAh~Z`WcFy8$=k$@Y0xvQ{%-4)vo?o39o;D2zOR$}`=>{DI3zppM4N%I=NI z=JjWr6NT0lW8h115HmrbZgUn3Qq_7qlv!8ubs{j<c0SZyd0^+ljeA4?7LtHvDL^T= zyhdO4m4~d)1MYIsmlX%v0(n;+#<{S0)c_0-YUo4&m!#~)G!p~EgJebKA=f&!>ZQ9# z&tgI+WXiYoNHg_hqcYCZnZ?MGu(UcAS?~jHl9LA9ME)akAYO0_Eb3M*dQ8PlE)5jA zNg4z@wAJtv%jZOGEu;($#X=uYkJ{;9MlZLpgJAoD>hqC9^QQv|gcGo5jn7!hrqxt0 zr)8x<LbHbW17oK9NuEzYcs!7?iPO+V{_zUN5P!?<6eC$Vz$JtL2E7>qa#eTSeb<IV zd3eJo`Z#ikr7fU<eGtC+?BghNqyQ@Kyl-GxxOmtUeOc0(1{YsynlE9}x!4%a)+3-# zoa3WoO=uU=2@;AL9ABF1uQnrq^Q<nj{rPjT+D>Gd`5U?u#eT;NHlk9}QrkUoYc~j^ zy3f#+^Djq(O(y!w*Q%2!8)L#P#mrd(5`Z$*A>UvK0_z~KfHo2E<$n5oY&$7E#6zg$ z2fcCp^r)HzZAB1RoS-(b|F)C1m0Z&gUMPng;L;lLs6AcE;>nvK)GRQ1!S?9}$d(p% zBo$YE2$i(CfTLXlK;K$cOupvC&*XMsJ#?~RIvJp>6QX8LNd0#`(m(6jd~GU6N8Nn` zm6!01>R~h~N#Jx#N4Q~Ona5gLyVAsYDKxT@I`yG45rNFu3(T#K2HJnDz*jMSH@I3= zGn;j6VB0hutwml~rxo)7NZLZ?kEUd*!&{nML9!J3sK#UGzLXGuDg6b#HS^+FoK(|T z9gW9#1NT)hFJ&<$B_DL^c39AwsLw5uE1OXHRH|{C5qCRFyNY2F?YPttgz0ZLVF3fM zVGi)GN#S5o#C=>nahb4{;C=SX*k(t}f>dc8z<x+1g%mccYy7{nE*sxE%5|{<=sOW> zTG{wTM<oLHQb+{3hTitmvb*|+l**%U<T|G-BwrO*PGwuOs5DprrU;n2c+OnbFU=ic z5anIbm6i9Am4h$7n-wDhAD-yfr{7Hq;<Zhi&fgth<OB<_wLc)>X?oQ@@JESgyA6Nk zY=SnFZA1ZjPUV^Yh0X%Wl3MYQzgBNfIq)*2<FB6rjUzcf9mVv=e*+*;1+UxtPwEI! zEYN_b17?wd`gsvW(gR#Gz90tDfP67MJbW%jEK@S)(e{B6LP_dT*Mi7h&d!}nh2M`C zj6~YyH7nEI2o1qHy4B#kV8gYbFA-ZDmj2T`w*HZQt6Xdd2MusfFD$xKCpNHED!${# z`7+-ABkH=F$8t%G&>{WInwEw}-{y(Lo$@}Ju{`PQPp}R*uN=qnHBH<QJPXdiG&s5% zc4~m=1#!p$MuKCf!#ZUH<-pJCm*RwYd{x2*#CyS=ZL_c(HXkQQ0=-H5B6Etg;Fe4X z|GqCy{Nqm}$eIJxlhAq#rv!phMgTmmegq=xajE&`F)^||UJ!?@FN<mtdLVr?n_tIY zX5eo;%aa0hsGCLZN}MQpP*^=3G<=|DS}fP7Yk<bKFPne8G@PF^M1jMnm=XU^Xf)sj zbdu5LfOP8*4dsyPDrfKSBO4b{>SKYb8bS=<oDirK%uRhvK+gTSST=;+fF=X*K>nIY zg4b)b8PLf#=U^{R+#4O$76d3|48UeePNq~(CgrtA|0%l}(E-cLiJ=@40&jtx;i9WV zSI&VLq=wJYAsELC@{sfikff;;PNNo0!(GA=WdOpAfB2sc+bE9`0UAq0a0=RBWWG!? ztI-gSe{ul<y6?0<8}ET?@S=ivg9w%N3&0XE{x~#jz=+ib8L*GKr!Q~Nug=Q^RP1Ol zRWR0RC?E6C9`|HIxW9e9tSBHzqXvjFN#+TGwaM@U3gl(D2pB9(5WhHZfp|c0Ft{lz zMcpGcoI$2nbfcOK_#5}w0w}O|Aki_<dAPXKw3w1Y!PcRGKd6R-miX6PK+7awN;&G@ z<fY1cT8sS$liUegqGyFz;(&Yz%C6%=T8nw@?IGiK;lD4+fTb&#rHV_4&3A56;K`Yq zfV_kQtb+x~!oG$uhL2-VDEKR2rVD+;@VG0CLzy=-!>9<B7$;~=ZtxO!l#B4TP{gJk z)B$*u*_J<AZrQ-Bwl)NLgY5<WdV*{&g3r=RdvDAq05K@pR|SpbCyC=%g5`0R&c>k% zP<BAdA4$o?k9cn20bfqEi^J`Iu@JQ~X698a%_cC86QrR~JSM8XU!3JvX)@Mo-z&oh zxU?A{yO^`sa;kRHp)y{8VF@U%q<qP@wGH#%6Is7Goeg2kqXHOe8a#00a}N5CpF=8K z+#VAVPDAD_Lzs>xOWX@MSXAwCDJf(Nni>gY)Bf0vq%hfI@KLH^i3v{9F6cEWhhh2R z-{kA<e4LY+h~<f-crr?GGGvK+;(*ztO-fknf8oqQ!AvCt`&>G7Q&&3W)#%MLvY=Re z1A*r(qDsZig|)d&5dn`A^dd7D-fk4l!szsk5FzNLs$^!>ZMTxT@a9?J7kLx!Ipr{t zoTaaaUT@VR=g)4t<rA1e7GgRk%>a>M9=N>V^?BP4!uH`c{*i%gdBkC+520ssfhGXi zJBGT@gzGrArooecjwDbvXD^xi<tL0W>2f^e6$i?*&4beo985G-+1zASJ;mnq(t`a^ zredMF;&&=q(y6Vm4EE;Lo?!>A?wnwNs8@{04Sz`H<%6BMNLpvnVwWn5eC>1m8`C_I z>dGXx)smaJ&Fl3QiK~?bR}FymTdl^#^UaeejXwE*XD1FOLVs&$IVk0zt!hiZz~l_1 z!eN}%l#`Uo*ohQ6*tdV;F;&QFMoy@b>t{Po{UOwtM2>J)ZdP!xC_YoK1lw|Xo!sm+ zOwOoeVfh^!ee6DTl2b^V-AIR_8AfW|vp4S(s+X%xz-wYCI<7PnIxfM?UbI=wIv?*c zl5fa@6D;Jbb)R6iVL)00m{y9Q52EE+k`jUYa*U;BWSuKuwzbX)eBzAVj*hl%RTLD0 zk$RP80IPj+Wz!SHlKXpo9m1TyXusm%F4WQ2s&HVQrC&9;#jEzlRGLF1=^BBGL<>Q? zka`a@<)b~YhLJUsK_x?4vyDzgb5EqbD?-0aX>ZatUo{#PWs!O^Pj>URlzBQy(mJyn z87H`OJDA|U1>3W-q?OvUvZ_K^Rc#y{=BzA!f`HHS;d#eTT~(FW{bpC(H@;_FJT|+E zL5k<&m)-Pat}DtgVSt)&Ump=X1xOZxgK}RvFa?xTNgov`1wt18<ZzbC<gXt*d-$y= z=Zf&8v-nU=Sv0H6&QtD35JV}<YFf|i4N=jb$f#J{rU|rQAT*Ccs?{iuVRa42R);CN zVY&#F@1egmSfoH!o0!>H2kr=@%e{er@-Z=@lI<?(jl)!ffuO?i(pc9uwd}2kg_=TQ zsE{MvsbZT+7ftPiX&p5uK>0!%XcTipp2>fv*a-@+pYFgF8KIz9_=33L&kyI~P1W)) z1167xJfsH8+l?_#)SUH*M@&ezwOSI}A)->vFvXX^GYwx_&$6;<s8BQQJJY+;ayaZF z+Ofqgw-%!<l2EES6ZfPFl&qIy5OdJ#ZNPcAzVQ1InU8dVN79>_(QCck28AcC*l!N~ z;2_LnJT-2ESIh&CY%<@Vk-k#nwL^IdK|CcdM+yEr^uiYEa}G>-R2NpGj;ArWP%7gK zqNRx+tqd<tj%JI(wLZ}xN*p~<%#7}iE{=)UEXL;^_Nzp1V5SOduE(@OSXQGUB~vys zw6rv>u8t~DB@UK2prs977WL_2`I?%gc_mzGT^=VTeW)trMJM)!hL+aWrkX98D2ORn z1G;%6Ao-}j&r1T2oZ5uhGB^v$Cdf6TT$%<fg?(9zjrk~0){E#guehQ?8fQMBG7BC{ za9qpqr?`o=-OLUI#F?)=ch9f$YM|o{-OjyO1*(rw(3_DVo7Fa{#gTKDPvkeaHwQbb zgaPZK)M{#a4n8R(p2gq6(<2z2veDYkx3yf@y@P1@uP2A3y>HbdY_Yt_aO|o%Zp?hK z&Uq}$NckH;46W0uS}xndsmQfC<M{h(t#E27--^d0W5~#}r|6J1csygVPA^Sssr7Q> z_&3f;26gVKrE6@W<;dwjoQ5Bk?Q#;VPrYJIk$yhax=D1b3#aPpy3<n;vBj=F`~B7* z#682FW^|Cuq+wVD>{14>%jJm;=NLk_Wc#|h*5K#l`0(mxU$B#-JH(l4hN}>sigAxE z^<TY%Ry5;LI~ob3TO1$26z|RmoiLgrs>i@kN;{#%4lCzp{sb+ZPlzRtkU5!ON;tTj zxC}yw(tz9>I>@9h=aS$MMj^WwQ&%(_N33|fMr*g62)x9#;3Ha^Hk5=T(`_DWNQlKP zafS6$kl|0xa!=C5DHQEORdvbx8vNZC2eu8S(lq?Ml(O1=Zb`@jGxJAre3nW*s^&dk z{(!n!8FipyGx4z~vn?PwxDdiy@fPcBi4e={T|uJHvw3pL>g1N7P@~#=$2JduT4f~+ z%E%OsF-qzNyNYb9b0`tX$nXlE6UzT4<D#Qtl9R3FFha6SWG%oBbL}=Z$h(?~ukFQT zFP7(SH0!bNa|vNNeJDyVjr=MBJ(QKr=mh8q3D@2~G2|jnYJK!e%czKciC1TpQSuWo z+nPygU_ElsP41|(vR%Oxvfyev_-XG{$0(bge&q10U`ilcpLa@4jKt!0-6-Y+P@{R= z>PieZ4%7I0kC?8o9p><Fz)M==NG+MJu#b?(X5ez4r5{X%udrrHD#6v<-_$35a;ZM; z7<;H%Y3d1AT;wKF^cvnDKVy~+4&RvV#<A-lsr((3(24j9<W?E(dCH@wl1~=m%fNmx ze|^1~j-N>%xe;zmKi<YSoBy3(-g?mdb$&m-Yh>lBm|4?@1m$X0q1LoiF}9(9n3s*R zw;^@y(C^i5q9)mFy10<N)I{dm)Gm*J^&lrgB?V?fXv{mU8yn3yS14&dc3f!A^frCb z*yHQ`BEu=g*=i|7qZ<qMEltzbH!=Dt*}OvG*kj3S9j<~VM!jDa#!q(Ak|!;yWr{gY zU;~>hVdg!m8EH4icolR-Yng*sdy^`GcVO;Xica@aNh@Z<3*_>vSw0FG*=+%132}qG z2NXLpnO!8&3F+BT<c>3h+lKg^O^>?SRQb#@PBAoeCOOzu<I9HmmwI)xK=bP*7NRhr zns#sIX>bB;qkPOSH+v$33h>wvx7JqJ&qah_A8f7z<D*MAW3jSwJhqBq-a9zcJ9?%u z1muNp24VLHKJ6Vca}@Ud0bgc%zlG_GyP0ubI6VvO*!D7;CtvJJyVu;(oSjsSXKzk< z<Z5-ln%w+6r~Z1g^JB&>^PAK#T0B8<g|<)Vse`+bs!)$-b2B8HQW<kg>g@aGBTmmt zVVnoj@4Yp6{VIW+iDPw_=bx|e=4EMjmFFBfC3Ty<UB1^}Uurs>%-%yH-zHV9eAti5 zg*d4e@o|SrA<hSp?ui4;2o)97>%OpBty5=sU1-L;7(nnD@bWB<tV?4ty5c%55SFTA zs-cCQQg!3YKQS5(u73uK6rfloK*0Iny|^JIlSc41d1l3Z?9C3kt#br86TcJPz)>Zm zeRj>~uH)3xSJu6VPe&Iq#%2*L&jvOl6ZPOPyB5@=3agqzQE^JBpX`c`HQl8-IRGoo zvQax8#T1dVM)H4fIerhz&@nW;WT7GUS6?-r|4^1%?m$*e`drVAj4a14^-Lhyd+@*Q zvi!JO7M}6{7L^c%CrZRGO(%SOaRQ7Evk8hYg~G%_6)>6k5Og$JB(kqp;D_G&l=gnh z(FFs(pCA)ii(f!TdY;e_0c*e67Een2ldX{w71JB*r=j`ftvHZzepdv21*IfEFmU{A za>6N-=Q;@1cA<)+uSQpz^qqh+r7&2ZVLqSRXBdM!xt9AyYLXe5*!&tOf-X2<MM^j- z#;_(T?07XGfg~_E#Kp_VjhXmYR>vQ)kU!OFBmEVioNnuX_Z$xlgO?2kuE8W0bks;2 zK^T*zhl+G`I=g(k)kpWMDGPH}IvSH(-$wtrcbz?zI<c0(-Al(Edy^?5spQe@>FDxn zjE?0C(MRO-ff}XCL~Svx38@=mq|6fTqUu4Z!sS&1dwURr%&i;r?VVHUaY!E*$Jroi zedl+Z-6(RfA|c<7l=Kk`F{*QK0pbIa)GPYRm~6eJEz>8{24~UQnyo^|N@#Phz5KG^ z&IDf_o(y`P*Ady4;m%|A=pyi#sMp(B*U$KKSj$e*V@pL<T>>WbTQ-}ey@-PD9dggu z?2iT#miGax-LBJGDU{w%5%8UX;r^T=0v@+ZD_4A?AL{mKx>F3~WFG4GHQAe~b^?<D zA-1E^gP~3N327mJdM68L5EoZ#ay98$TSZZe(ZbYWc(q);$}INH_+Q(rJNn;FHiG&2 zyp@%`DdDAAj833Y8Zp7kQdQJvK*-<beM^&@;j2W2YFmiBJ#-}V9A>?YT`*0kH<{;R zMg5+yk<53(-g}Z{(>c>MEj{*D``@@1ed|hg`zLLvbqVY8F*fp2H;|^np*Y_yw8FH6 zl99OmCQTIXe&h!ydV8y}U?fZ$PbP)00W*}dn)>mZ$!H@5&>0gUYpnSPG^y<KUhO08 z6$%jrs)Db91Z7vz=FE>UyxzDGZBHW?PdaO8-eIf{X%byH5;+seu%Q~xx5w#Ph+0f( zSzuYBvKEZv!XX#fCs4XOdh?QmeC{+o542%HeZE?+hbMjprsc+v4iJFZeJ0PC?n2Ub z#tjtkhPz;5?H9(_^=}oR32zrQP*UHGUfZdjxGML~?G0jzl|nqG7TMtUUclsEcafH# zhsteqkzbuSP&nS5&wb?FMgEEm%HW&Y8rUGq4ln2;wLdSKDqO~M35DZ@e05HuH0jiz zoI|bEO1{WovD9D?d(vOv*^xVCBc8ODcRow{NO<7er}#yg&@iZ%=Um@}7WYPqd7VBe zz}GgUZ>!Z<x1#9yizGGO&A9Zp$D;%4dI!9@ouH*<BL7zM*)gTm8ozH5<(_cQ^X%yu z57@_o<Xw3l{gd6ngHph=MZ^BdDrSd&NPE>~M8;j4Da%;tH}mQB+zmW_uZIx+U%Bmx zUBL{|(f5&({CJd<vR~MtyKdqS8R->swiy_`$6^Zg5icEGcO-~NFiVf@i!W>+wI2fu zu3HIciFEbqi$XcDf~~_IVfmz$5wFOMcYJ@uHw+d`Y+OfodtbVa@!jNjC8cbVBYuz{ z7>;E-c5;loJ{j3pJZw~6$1UrFsQkq~(MjdC10W#H7X6rW$oc#rnnLUS*(%$a{jGYl z#z;|7%9)(x7I~FBZ`qSL0Eia|6CT~2d(KDAc_3c-ckktbZ*x21fh&}OW3wBD+dcv* z`m2LHL2Kyt(AU>im_2;y9wN;L)V#o;+F_w2iF&aLbF1K;y<s<}ME;nkmv7GFaO>CR z6R_UolyXt&1P9a)8|g!`jcRB)#d2_HeEQcD6_0IVb8{2ISv-9kNQk1{0wPnwu(5F- zvWGu!`aWEJ_r@lzhtU(@ItGB^AuBVl?Bx)9=!@j$!aCTjYwN`k7TY^JEs^eS99F{S zI<3VD2F0IoSYJ%-b5pw^0q;ENIOi%h7v;?Dq<M+rd5*z5IypT<eK2^kk<PkQ-dcQE z(or$lt6Ei_w-b-9iB0auc%uWMB5DcPp@v@JaPTl7oyu)J8mPW_XtyS^NokKfxS)2x z>!Ltv7NFEpIVSG8RgMmk1zhRkb&rS_;m6>J?$C<t?I74bJ?C&a-G{H%0aLS`Djowb zFyP1JNckc_<8CP|R-TW>#^}<7CT*(5wOHKp_+D*ob?v01%8xD|@XoJoPry%mNHB14 z7@8A1pI+{P=h|J-XGzGf$wNh%`?ZkUl&Pq<GTpCu#J|Y+QK5Mp?@L}E;4O6PTx^Ac z>u-XKs?LJp8I1V8ROz-(4Y@!ZqQ{O_8jr+$mi|`wZRgm^zSPi_TjPTF`LwE@OR@<= zioqW9uJ`3FgJU!G;m+5z?m15{2+n=V>!uGA4_sWwB#O6)>Za}Qz)1@jn1q`>(Rz&c z?RZ__<XzPqeh*_B(4O1#tI6XdU2F0V4bn5I3ndMvW4A|dnc{yq;hk^<K6~dh)CKz8 zE+vHt9_n2rgx3sEp5=2#6KNP%Djyl+f80O7N|T+5wBM-p#UpD{^ca>(gkdV+8$C-A zAL=YVYIF#po76erceUYchm<+YPM-8uahhreF{|P7%#FDGQd&xk&uEX{;0S86#Gd2q zUKgV$fjo0`bYycdF~`}u`nB6-HaN=qJGHw%(xR;|$$0cP)oAy%q+@CgQp~|rVi^@Y zJw1J(YTyp9slnN-`LC**i=vYgOOl_f6!&E|yA}g-*!v~IWsUEY)Zbmj-c<$a+7PkM zmVzBS_i!V%dq0>@FW(!E;6Zhxpdi6&Ry`kFeV=}ye%sUiSsIg9Vu%ESaQo^@pOmAk z>s_F!kjj+EmXbdDBW&))<nf%);rp7RK_d7Nr?MV%H}qC_-)GVH7Lh~_1}${<4t0gJ zcH|Zsa0rVj<HiOzXBSszM;E8#D?E!IUheH&E-ub4PEAJkj}tJ=&vu(ld)jGGsbDl6 zHZSbq&=+zajZtEjeP$$xmY^t%(0>M`vsqvnTVC{2X(z0t-ZFRJZK;ZjJ4v3I8ll|D zwV_7n)^#)Py&h<>hVxohcVOGtwi@iE(j~idFNz~42zfde_LB3`Pz&sp8>D-~Hhaz4 zQ$@im40$D+b~om6{BSeqjFj2Ubkw1eU|Yjs#}^xL8WZb%e#X`BjTL}Xsmr)AL~i3% zXiC1!w`~;{_Y%aYskM<2X%i{d^tiPtj>F$sq%1V*)2)5Ez-AQ?XT8p(m9jiIyI!OV zQU<|7U`XENd<bSHGmF)j6Ka3eGh(mOFt8}xM18DAD2iKKj4spj^V(V9P#kgZ2>4Xp z&d7e#A8)zj;nLW#*#Ksmv{T&GmNldQN&-}q2bhgBriwU$)>7t<kJ<#UBqZJE+(U<R zj(2SRYOOU_@Dw^H1Q|)#;_l5myqjMDOVbZ1(}B?>O8jCatF)w%lS`4W%W{II_U7Px zoJB(vXr_1cel>)~jnm3SpPiFxLxj}B=sZ@sOOZ;M2LmoD?kN9u`SzBL$|<LcPvAbZ z2}MJq_&ae5z5`v#a2li=U9WSJ`(TL10s&}jX8eU}-PUq7Y~k_xYZHxaPud7jq)TGD zR^=(iPgZx;rgMYwzE+XJ?+~cW580;+gWp3?m^p1cPXrS>&Yy|t3w!)~M6~pM{{v5G zq768D%5jPr?mGeFEobMCbHyu;;;7P*;WVES?NpdAbLNSSTl(!bQ}BH;xHS}!#kJ$x z*Mg`B6+09&WiTEHu=)gAPi~MXUn&l<yVoD8imWbKVXw*RYFdJqT60EwhN6C*WhzO( zEiSJB+p!eKZrs0L7hc2Nh8IA}nH|uRnR&XJQ;LJJxhWluQWVKwL!&X3K-pnDn7$@4 zN~<0BeRsAjRt1yAmf|Yy@VNOhk%pD*9&Y|qWV!k93TB+N3Lyz;6^(1`VbIUyZ1vmX z#k>``ZN8(Bg_vDcwk{eOc#bDr^jl)5N8QcMXBzuAm8i61htcGpU-*-}$LNM#_LjZa zqmo#KNR+L1hmje16)~ATP*R<KR49x@!G5rw3EV)_*gM?jUk5oD?t@;`m(lLAazY)U zGDORyCZ+1juk-q{-2V>Go0&8~X?_{G7dt#WG)EM3x#T*sX;&jljdZ+^gt?D!tkn%L zG?r|s$3U7UL*<mu0Gm0Z6UbIq_T{i&8K_Sk%i6O8dCu>@;oX7kEW>tMcz(FUuaDI8 zM9Es2e(IK>8`BU@c=NJNDckYmC)4hA5MGTv^~h8BbxG5?Jb)X$t;>xh?bqOa*?*6u z>;AzQC1^iYgkBuMAdsOsz%^BgKrS_|iKuBBF&=CF6O7(>?_gYjuTVax*YS1tHMP{% z{AW4YO-JWYHUp7@_J(u*$n=trQu63Bjxr@a>RIq=tdt!3ihNZE%!$zEMNVV$8B~bo z8o#lrHK=Y~aNoGv>Q;2pS4y0spI1L3J-ByTjb7r+D*OMU;sN_~vzyy2)~%x6QpBC< zE#B-pV+N=c1pG+9Mb_^@K}S>aqQ}eon5F2G+1n7^y9dfCmgl};&b&wHjkRgTw`A*~ zf#MfrG<zQ_d9Lj%e>)>zZ|zFY)~)buMm;t`n{bpLnNCv>poVnHi^b=3IE$g2^dpHv z9Xvo<H9=Xwet141Cv@eXvo7zyK1lXuDz$9QFEB9g%Zxha-cLLXe`pwKsA4r^IvBXX z4fhNl!yH0MC$&{rj6zMa(Lj3bL#($Hn}LiX6e1Wpm_?|(z^Bt>ozxVp>MmuTaHGX% z;)vF>Qdi1iHwA0Hf=p}kK9sk*JhLK}moar7#V~JFcRSn?nKrGjVm<B~|I!q^_<a_# zg`kZD?N?&--g)AeOVS@n;l%b<KM-?IbLR$skNI9-nxqucb9OJ-lhJ()MICM=Qbksn zT2dsP^F=l;Zc%JW!e(XGAX{R*7`o?O`B9a=g!z8%Pp)Y91St5L+=VB;>0UBkha|~0 z5z8y67z#mpnMG;tVx)D@5Zm1r`Q{LR`ZSO&c(G`11rnq3KW5;aEKI08%y6CJAY5ZF zrlS|ULEJLRtsR@{=NgiTGw_-cII$Y5s#^^}WSylXnNwjklN%2fP3-$XK6_dY&A)Mt z8NIfPq{d?+`l+==ReW0wI%HYF(_!{Ufro4)W0M%*=RH56Op8KJp01s-_7tDQ9a%#a z_vi11h}O#x;M=<hb;yN&)qf*$w0_YD^W$Q~Et)bZdc>+5ik-ygb3fbWjpK9JBW~d9 z$~~T(fXTT;14Ad323Fh{@Rj0U{0fFzrLOBL8x|SlrL8Eli;;wc+{Nfn7b#O~n7CXU zE_1&OHn7tA7@T`SNGY||VX5+vnpwzUyZ+`=5sdD}p<r#jV62+k)va&4o)ve6Cn_Le zvHz0c!;QA~=(J6FjXyahtZDc<_L;1G(s6{vU!+Zdya)vtG(O4iLTez^#>2rxMr7mS zMWGe6dGs0X!qGQc1W9^-zlAWgZ4;r&AG#95Li!FLB3Awq*BrVm_Z$$3YLl=~pA!(( zDtGNfK>QguM}%Bh#PC{HTq8r3LydBPtIJQy8ZrS}t1eTrr5P5^nZ?%b8G5`Lty%%4 z=38i`<MuLBzCHeH^~=WKjt+`UafN*W@`rt}I7AXX0dp?4nHh(Y^tTAfJjtvXzi3#_ z+DT^bI2q>?E0|xy^cJXJzgIIJ%oe-wBQ%9CC9L`>!0i-*4MY_8yh{WHRfWY(`!ea! zF?F=DiYjI|>Wq3t!}bcJ#JgDE#n|OE1=B9{x%k<o!+dZzqn8FhL(dx$A^(ExL2vgR zf^!q_4K8S=qFN9OiKo9AW%3}I@5nfW{CqPSu*wbUqema~zU9A8k@n(rtF4`q>Lm@8 z%eIp(_wnleEg+z8?6@3XnjvrgM&hgHbNj~Y5>g2*TW_G2qI76VI@se{Tp_DP6kO0H z47{J8&BGGPpP6qszdCQVvqUCvC3g!2^>ijICX-O`Do0{|N0NobCkG;0-Lu@R@-C@l zOoZgDYFX?K7Fz|?=HV7x%KmP}@+;ey74K^ew=f6n$C;7&ebB3Tn{Ef|ML)-9BJ%jq zq7pDU0QGDD1M=tyE~jJ&J7HmWozAST7Wb_jY4}@EO*b!YRXMW0dpm3Ww-82~8`pE; za8c@bWNV5Qy=iRwEVWU`h6Xb9MNeCw-|n03apdRZC`{nxuym8^B|8^Wh$6upKYnU6 z6W2Z6AoXvwnEn7~)E}B3|1#hl-0)o+e4hDB*O$)JQph*|$sM#}@HGyY@&UDJV#J9k zRHI`eep#V3lKe7cMMUWSC8c*}eZpx<T6HKv0|G>ASmJi6=oc++ZV@cUaTAm=y6_?$ zGu!jU36;|k$K-LlBa@v3{|STr&6U9l!prYE4-sM1P&ict%K`UfC+lBqD@}jG8l`{N zfaMdt62@Yrr!4SQ^X-A`W@YbE=q$<;Bu@T{WZ0^Q&u7tS>G2R+KuFVBCrgf`!Q&NS z6T!la&-Ht@Q6~+CtEecXCVP2^aUL+7Ozts5FW@zHH3rxM{9TS=-gF<sR<2Kvp~{2$ z&2T@{Crm`}kKBFKv{wp<*ib3Fo$F6S{mti3Jx;%V?`CZ|HHkMnIAF0&o^FhiIHiSS z&aLe(w)m(B{(7e>ZDZ1w{Due?oK`5|;|H&p(}_h|76-|~=PGd+H^6=}8DJy0Tf3y2 zsFH(_b~Xl@HTYSFLf$=jmyy7p9A~o46y#<R8`VD*xrco+6neij99osekwtl0*`Bu5 znc#wBbgHYwA`L4fqitmA@GxR}(fj1p_JnUG7V}6PgibWCyZt+!T8++{<YL1e<nr<i z7FE%cxA^6$N?q3iD#8|H6mAq3L=<n+x<*Sc!B}qd+@)8);8qc|G2H6;1)NM76^90Y z6f~U{V=QzVCk+L8z2?n^GgwYjos;wV!bqh;E@p=%t&weq^rZ4Ussaox&*{Qu9}!)0 zvNoUX*wmr{2&a<>ffS&gqZ7;1t1*zFvK;Vqc?&ZWK3y-s9~lugxol^_gMb@mbYwij zn&ppJ_lOFx#923KBQn1vjS>Q>4ifA}I8`fPsQ>@~1ii@f&VL=39Nda>jI29-xq(hL zz~aJkf@3|rwMc0ID310~yg1$;AH-eq95`?eu$SAU3_^FiXh0(tU_}JHfNBx%F;l_; zN=Ez#tZ<*B4aKV#33eut*FEKBcU6D<+6(~@o1|bQ5W=D*Q9sl@xqoPrM=OC<EZ8~$ z06(#zpIP|BOdg0LJi`OD;57s6CT^MZ`|m$=>;Q<K)~hF>%Q$}wVBE3=;qS;tazKcd z_jUq{_|Q!Pe}3*|mx!Fo0<4|>|9dx9nep)14JvdK8Hknyw>Y5n@dwkQ0@iUVOlF6x zSYn6T&A_oO#G8u%zzD4En$SF|FrgPCfoj^77<AGAK#c!ApU^}C&_E#`8+%5?pCRBs zXCG!I0sL^F5Mum;1O5*zN#YOSMj|@8<R8fV-|JZu09c}Eu(1F4@WeXuAAu+t7nc9o zGXIxm^dA%!39!-sdmh3S0QWd{U0k)G{+ES6XdIG1*a=C0jsNFlM;;zB08#Y+#WF@T zZS3DuCD~vg!@*dJwErBZ_=kfB<i2`yACvz8YW_ab-v_Wj5xmw+gt`L6%vm%rYMP1+ zVcKyUY7jQW2vWohY0mM#=M&OcfV0{Sr>jFdNPy`;CWE4f<tEEIUeC*|9p{mQ)W61? z<3hk+P&Ca~jD>}bcMTe+WWB%rUgCp|iPl8naCquS_C*j+8l<I@7tHbC{WT7H_x-u- z10e-_eNlB&>Ni<;TZXgi>GP|>ihPi2$~LF(oe>w2@JxP>-i(_+NVDJZ@pi?9AUMIw zqQ*yIWMClt!4Ul4z83-tASDQFWH~{Q5;8>>dOJ7oa_J+rc_#O@4!2iVC9yA<6`nd$ zVNDGQ&L|ylmq)~oO-wae%<5wDIu4}avDu4+E8Tiu4bAuiDrm+=YU1<;^+_oF_?G*h zt9zanv(Z}7%}X%doF9FDP+va$n4ph&zZ_e7h&)8_@y4T(&z531V;Tm>uIl(X@IxlH zjafA>@>;;BN#|QLZrfipEh8*Q$!!j7WW>~fmMRJ~D@(xox>k)i`Q#_50xxi<D%|}K z-+`Ll^3t~NQv_vIgupbdQxBfl6*Z$g%@$)ZFA4&fmwGF${dN~DIWmRnD=SFjrim-9 zNJsXh0To#)63o|~U{Tjwzo1b#RlmI~BKP04>ecPzo>X-DA&2I7<Kc39YU;dpg35lL z3s#K6=3F7PeC3<jjN`3xtUr@1(+o%%{A81sOzrf$pUIs$%9ZY6zN+4S#0&U1uibDc zx#6(GoJK2yNMPmv0w&9gI;;S2xHx}k=(IjBSI41TBe_*#$_hE;y#MBio8Ro+mhLl1 zZmp{0Db{=TG#op0IF%q67a#0o0^EK4{NApI7in(179-rpH#|%x_Re`<zuJjv3|QYo z;OTb$=04f5Ra~sN>3It<8@+kT2(UK(IDBG_H}h%#dB5LUW;yEv%CGTyrFb(&W3Q{z zYv=PyH@@0qFRT5MbN!yX66BW(-r2t&^XTK|qUsN;>oE0=duG-xl^*`4$Z_9H8ieiF zsl-e&Cs@=aVhxLp+SYH#E?E=>{L7bNZNBPqOROO0i{<+<>(kPj2d6eoIc}YqPI@xN zND<%5h`xdsZjbquko(5di-(a$zE?=z6pEag9;K%N*p?{VuN8ZW&sVy?YFG|Yoqjt@ z4a*$)F@vOI4Gu7&|Fu;hke5XR&^$wA(%ES!&TecQi3qz-cryXo>I(l;tVuOZC6CJQ z2x`e=jXgRz91eRCbk_V>8lLP%>j==9kz<OYgos3JKWIv;x%AfUPcvMU2q4VJeet^R zbPjrZ;q`On#l>uOo_`JWRrX%2W)QhN__`f-zC)e1I!;BTZ*L`m=ibF?b#hy^_`SMW ztCj33=vjr~bgLxi`jWoaj^K4{EUDfHxp*H@-MvgQNYqRu&G^9lj(s>?D6T4kTi2tf zp-V{;9^_9*PX6llh1<I*&|f_T^Wx&?c6uBnV<JAsU&}~GW$a&VOh*-b1nl^hCH5^8 z5V7A<(5uP1DNR&-;Jls7^thW7!f|%iZfbnF>5mzjNF=W?_isR7QOG92Q@4XAc!49r z-+y*?K^FK<tS}Q5>k}I%A6rS?`5M#0<Ne_Ht&}K-81c-FE<ThetbFv>B`%SZ0ItGR zzYC7Qv+P|ek6+~t&dzQ-sa41>0#aGR3Y3^gHn%kM^pT{lo>_@bTt$}vJ>N=gukpwT z8@i*BTr7=%;pvN!fP^r!-&=jj=j?~8vq2pq7@RGh4Q?*+%7-VTWZ5sQf_&xwZWj6c z$pk*O-?51T)FgK8Z=o$qS6(gKDztiM8>jCYxB8mr8-?=e)vA7*oHLE4jz7D-Zp&Gt zlzU)L9!W-;iBs(*Cd>4|`F+Z;Bc2WiGXiQ=yxh|@)GN!drEY7Empg9${ka9P(Olbn zO1?}_@@IZNiuZ%qm9$CK-LV`{J)ic{oKhL<A`X1yl`DtFAe=vWqp#}^B60gNbe-hS zj%$1(E&~N9A7w8j?ix;OQIKz*q9aSLMu!88^UtnR7)xX#&~`B2Jd~`x&ZVIXvw!{< zKphNZH$Hwj?O3m#-M!Iq<SsWPtqzA16de0!H_O4h{#P+9Ol&i8k-YtJ-Oe&0>s6}? zYJ9iFo1?G6jEhhpvDU1xE4(E+a0e~ty`LE}p54Df-VgQXtsq_={9;T#64amrN~L}I zv!85IneZ9K0@vJ~K(CK<0?9aSKKt>=Z1CRzB?bVehXbs)V`f)T*aP^$iX0%XRzxXq z?5TIPlvOo!y$s19N9bC3)B{mtJ0ib0^Dx3}FBR!HzcQF)s-YXO-B&69=6riQ@x!#W zp`|7)<kxs@uOy#l*=C<^2}h+~;A=ufSaD}<ihq#G9)b7WHE8?fu4B56*3<4)J3;S9 zZ5HL0QH84G&vqmCIT*AHsR}haPH)}UR?_Q>@!h&zTg+tP+lfUm>dJ$nk`k07$`#e6 zx@QI(GPXO&gL3+(8P^hDp*qjulE&uhWbo6QqQgeri+)?*qKq{i4+Uk<-lhk;v1$PY z0+}A?*2a<99QTny$Duz%m&sJ17`{yVjr=3YymBb#2GaDMYDD#jRu3QLmqn&Pi`G)( zz6+|-l&csh0q~rMSt9xsr=;}0xyu8yMwdgQbp=<=CZ^4L&~k5-EUWd<tHoQ*Q6Mk8 zEp^e*+E>cg9RgC3F?qcJZ43lryh;P$Xf`J6d7_F;3bymX1jq4ePdIEVH=FDA9}+tn z9p)X!8PNo^x;HK_O<*0nWpvlifk1ZT6gLM83K+!{BjpELJmmvOXS*Mc*OMxi-)0{$ z5bWn^*4n!d65QFju4sv7p6(1*S{g}XSrk4q#*&)ru6Pzp-Q12sLD|0jH#hQtEIKxt zm9kPtflUSj{ioxEP_H`Q+w2B5%lN4q=%Nl=n2^nV=li{O9LC2h`g2|Py3Hks!_mzM zvIrikV3^I2G?M~!+gh@H*So%B{G76k24|R!E`VCYvv_!ZT$wFjgs8TRTx~PS74@qh z;3x*YEBhvli!2b}<P@?l8X!drf<o`EI)U~ZGLg<^Kr`|;wS+`_1-44(l*DRF;SV%m zIj$4254b=GWuOpN82=yYzOl`+W$Uuiwr#W0wr$(CZM)L8ZKKk*ZQJfVr~7u__b2qH z{XDS)Gh(lpdqm7J<`PBEsCJx}ubpB@?K=SGaA}-tokmd8I90EhI!G$u`^N?HjsHNZ zHse>rOR-#O_*#e9t=6e#EZcm?40@>O|A5R~=^w(XHR+GCdm`1ZOeEzzJh!0Wk05uX z-aiDzf6=Ik_^;&+`f(G%qJOzF|G{w@K|hF-CIE-(Kf=*}tJ6#VfYuD-hP=7|)cfC& zLV+J_iP{&F^glprD#;H#ovdG#Gyl(6|1|PPCHR4-k$S`b@XP;W=g&j=55hHBwJK%$ zufn(=a{A9Cg=-Cq{>OMfHg}meSg|N!{uk^O%N70yr4g>wDg5_%01ZEIxIvu-i!%Cu zz;D8T+$+L`8pVH)=coR&sH@Z&FiHP92>kzpsm&1nIXi&6R3iWPc)isB_`>AL5Q+bx ze{KZ#M}5q$fYASvvi6uNGqNu$3vRY?PoTCRT7|Yx7Mg8;w6eb>Mw^?fH+obq&@VdE zggW{;1ms%7#~Cvh=9qV0Ds}C7JljUswCp1OT<(d^8|^?aO_H|7ZTP|Vm^<;8Sx4|e zxlh+;uE>_GKwKvJ_9-U&*8i?*oi(B&-YW#!?Q;<vlHu~Kn6)u#5>T;#-INm-Y0b(i ziQUd1+J3mdjY45vyAf#-!~skPtrQ?tL_Z1mIn~dHV{PBxV<R9t%H+@00!EZQF$um& zm>>0f|GS=1U=)x8=A67~Cuc~FA1#3WLT4xS&}d6yRVoKsVlip5%bvwC9?TKV3)T6= zWUryA;R4IKGpPZXNvOqr3N+*FS?G`1S;t}F&MZOYHj2T}x++r2P-EuufHb?@+fq-C zIEdX8za3=9qW@y4n}?dD5D!^}Q^sN57(@jmIy%*DA7Ipxl}-Rc6-1m&7m9tf1XhGr zY^!3t-8Tj#CNTA&Z6o`BOn%Wop<7pCS-L4=EfVWN!G>_QwsX8(FSvFT_!ZFFNi699 zPDT4+37a2G0=dY@Ss8?68?;L@GZQ5K$hiF65#=_x=C<j~Zj+7Vw#D_#d&4+7sDX@| z{^V{E?2umNouOb>s&-gG4U)%ru^H`E9wrV6^`7ccQYRo~gaXT~x}_p=Js#?0ejI@> zIIEk(;?%|zlg)foCsQAFh41{OUP)p0EQE({e0m6%k{Y$YqoVlpH+&oY4>##W_ip#R z^MHYX(A0F9%t**+qE6vhIEe>#MF<45J%##2B)`FIJ67b7I+6lrs~A6Uz|fkt0ZP*# zGl-fn2zMqmZJa7n)-xR7vwab3CTV-Oxa*Qc5pwK-6M0)9AgqwJboi74$6Zr#sh>T8 zNckT({H2l1)PIgQhT!Pih6}647>YP4p}MwsWZK>sDNRkl!25fW+|9DLnh9cJy82*6 z8pVGJu`%S+8Ljd~;TLyVYcdp%jf<KZf1S;;0Zw|V`BxY10WI9%ol@<wkE!IZHi*rD zi~`!~7zIK%e1Of{hXpmmP}Wcq-)y&X-W3WlxujB7Cj}d!8Km@!S)|6+H!uNBV}P?j zs2#^kE+;`Z`xsZw^deXr7udT&c)VTfU{TbezH*+}YE&t_H5fqt?Ay!iU+ac1&xXGH zbHM|pw34d)UG`R8UG1wUlk;PU)K%unKv`BRKI@5UmBE-eKKE+23&ut6N_`sX3BNoi zSd4Kj>S-#o^!QTZqP86Yhdk`W19`<>)h$YEb{#O$;t*3s7uoiM=s+fXMqAf3$W_;H z?Xo9rEg@?GJ!cn$^EuReYp^Ub_|WOSn>C`UaV8bo@0h5s9wLraIdFNuvQ_&d&Z&cm z#yINqh!OR`#S-$lcpYhFAk6P{PoZ`EqAuz&XDsf@L6I$H32)}{I+$@#n?s7fS+45m z9z675q$PWIUN@1IHQ`NM94Fod#iS(;fQyES?BHRRFu(sKiS2~C{V;<KEvwjL+cXF$ z@;Wgksjw_9iQ4%CUw3bVIRW8JEd>@=`b5lzA%n=^Y{s}Z++Ay<u%RR+sVpt2r1|O< z!m%fi)F3ZUc6a6g!;2c?##N+OtBu2oQ)<YQ<QRLMHL7&-GSigu9UZa+NGXy6;q$s; zbZ--t;SCJQ7P3$C2By^eYLo~@DViTn^ra<hkvVRHR7Z#zEC_>&vTCnDXF<oslSd0t z902#pIf`m8#eh3a44A1$#@x611q11a70slbwsJ;ojz%uIl_rHGD+&^SON(nBT+LzN z2Iy1QUvGpjCcbqyx_-6Vj+E9oHRJIb0`=G5-3f~NOm+F(zfF3<&o+ccdQRi|1K6&q zx!U9kQHKC)1n_NH2X_E@ruUXT10y&oEeEzsl@8J$UnAk<%(c4oUOQ+1VNK|@U7^*f zQu20%c21f>aw(*ge<E&wqY_(OH9qaS!I-~1spnq>Ii2NKh0wuXKg9GbJ47LwnMOl6 z^C0*m)+fxO0f3{X#{2-GPurcmByf3Nf2cSEJ#i?WLf1z=F}mzHdyru)rFLvQXvZ%Y zisb5DxB>K)q#FN+<naa>@$JphknTYD3!46SB}6f%aBg!OtGhsIl|@D^!F{rpW-uoP zL|?g-l9}@d2CeSXbjE9VUp$0sc_PCBJA{*h8B0n>^Aev=8S(o_v$0gXF;b!elFf)E z-qz@cyDd3NUCN)ttXBTCz%R(?=_96;ZLzHLaoGp$A5HSgc05H*QlU+lT9*|rjg<f( z$z?JFsKkrN%<rh&&>#Nki$Va>S7*O+9zvWByNqPxV^+()+t@8pc$q7@Fi9FGq1%lY z>wzPvANXk6Vr5!XJ2E#55SgO+XwYIBqH&YMX%=HRCwkks+G`#dI^f*C?$jYx4+oE; zpypyVs#^BJ3`{)bWFS$jnDQV#@pM#4++Fid5Olh164(3menr{-7@C`71{Yb9rJ@eS zdY9^nlyZ}+1H-e30&<y&0jpq9l%+(s@VROkb$abtNqu^{#mYg;lkW#+Q9>5Z&wl`$ z!D#|J0iMk$-FHWR{@!kXC>$<VYrO7&#X{Wm_SZ-%Jcswg$|$BUeA<s#-C4Y{^QET% zjU>t0n9=<0lS(uOn>)cN^NrKt!^%Y*XRbo>>D!b=?Rl!2%K2cxr~pDq(iWG~<zwX* z>Be<WOP^V}mf^YuN%SPua5xr^)A3JCWtEQ`=iyGD_bpg#W?OacA=Q;OFQV*VW~)=i zsG^F~r^VKz)t9b|%x;&cNQIzr5R(3iY7cLxyVs>C70;I)gY#0<yH2qjIpkN1jk(dl zhU<=yhqbwvu(LBv7q^eyB^7s<*%5#;*dC|tYJHfoV}Ei{y*73ginse63@l6rr>l+` zz0(j9B0dkm+XO^jo_-#DpVK>N``B8!RWYlU_s)pP)*x)V#}$WiMalS__by}g`tF(X zlWilnD?=>ZzP3UDudMQ;lM`q^9tU04gz%E9z{!TYqdx?bn7~xl{(bH<OtFEPnS(N~ zZAbc*VOdBZO!dpNlC#P1d}-=9q0sqIe$R0U@hGG3Rv+W6OaJui801Qbp2ZPQ%H>i} zoIBs;n2jY?Cu=lHexk6ivT<NjH&TG$v3w|)5@sh>wV#ra9Xd9yl4_2f@>ECV2)S*E zeJIkkg+YbOVUlD6V+=i7>x|gb8`e-O(p%ciWS`wWc-#<*t=wrjC#Rb?B(|MnOVQnD z;RR3>CzjU%BuZ9W20MdSnp9dx**pqIozjgxd#uG@9g%?MXDj@kk1-vM#G_%ITav|9 zHvUs?D#p;uT(8N{)ft$dTxG(1*`uu&&5rx#bW-U#@Oj95&PLA+&pxA1dQz+eqf#r) zKfCOl4cCMr1jJW7Yu!Qo$bw^dONs}J<mO_NGAC-@XBeeTsZ9Lj7W*`dN;Fj}GiCa3 zopzR(d)J&)(JK~1DdoYBiXORqzxCb}P}`SW*;lz)L9?2HgAXZ=(l(UTs(8ccH4AvH z1$L`y-2Z4?zxU3Nuh{k^X48{B#dURhV>0kV3|PD9I>br>uUTPza!%t_C&?i%6N`FM zZp(lsP{co@g3|mf1zNA@IkQVsIjUo8L4GW?c%k0x-PMyVl80SI%6+;ml&rJ(E;}^Z z)I~*yG5$I8qemB`gxzAZIMSfVG0pvS?95r0{x*)^o%r;te1*Zq;b9PRGdw!dJGIO9 z;lLZNDTu4HQMP|J$9#syC-oEPVEe9z%5^Mbd3<@UQ(BmdlHK>}?<>?^RO9{*m@W+n zYwH_oC5bOTbi5Lxr>f%1dY?D1cB?a$sqaVM{X(?L;b0EzmE?F^k}m+{Rv~8V-QDl8 zw51nLhwx)=j6T@TWASxbF6s}%<B-_U-=A>%+}j*IzURje>PD8F*^k-dbmX41C%Smt zK9x(6D#kV0I~5$q-hrn$+e9r3iE-QGGJ4m$ecO}WU|AYfA1b7d(i*5a9>;lo)rb!F zSuERaoO{YR>DMWyCu2^o>K=4ntkE3-wT}*6>cw-cn?HQ~F_=%{136TThSM%5mC<`f z#k6sxMf`iS*h?K!$E@s*OE;I_o&d^w*%@(eT>Q*`5i`ed0nfG?WT-(seCP$zf9Y^) zMfOM~@pL*KI3A+VWnf4f`u94M5D@9YT9nkOdM*8^<&|~R6LEzvv!IP}ThlES6Sm}F zcxfVeiHbNGT2&Mz_-M>mQ(Vemf18C3;mTmDBDG__fNyz+yDl0U?q)cUK{L#<Kp0|5 z<dSrnYN%FqbS<t<peQGdVS867s!fZZTGJJT{2?iuh-YAp$8ZX=-yo8Lo;R+ACFFe4 zQuQ&PvOfyS=*T!*E@V$D!&pshc%;v;xAo?gHC{1c)bSj!H&IQm=WxUFc2PcL#Hpak z5Hfa49Su9=U|1Sdie@q$p8w=J=PnUpaG@J7(c+dEvOmHNoa7U>$4WY$g7rXVcU#A3 z)1vJHlOb7u7ov1Fp@b>AiY@yXd$(jk*)t2BOF5HmTX1jmPyFyIW>wlEuH~~jz6?e{ zK0%%;%<_jC%|b3xx*~KUi>D}YFk-JN`$ZKBR1uMuf?os^>asG?o9Y@w;Yk)<HmJ%h zM=TEFH5UI)d1_z0{jiyfy&12#4^aE+Go@8+_WNN3K*%9F;j^J#ZC_$>HPI{bN|mR$ z<z4qTYVfy^`}E?Xva+u?Z28J+u$QNBwHzN5kH^Ewb&oXtlh=1Qlbry$Tk<U!9OJ`F z4lgzrw77qt;@Fj9i<C!m1kN8a+qMPbtp>o(<GS`vQy;D<sv1A}d+A`0hd?usLvCBj z#T)=dOwpZLyqy=^X?>CSCL!hQSl(0xq0JU89yj`Z+@$lpzQE(Mr_!JR-;KP4m1u{` zXWi2CVPKY)p_VyMpBH$a<kjKK#i(a3b#-;+B$Q`Z=j?{9`!eMy&aMm6OxL7X{zMVF z@(UAZm-6xm=6Aeu(EyD^Z=&`1vvW%I5*jBg)*%YhOfF3qYEYXa<__w9G#>QtrFLvH zEq=Bd?bV*qq~+X2^M|x4i@`@9N=N6Avv#MyAOSd|aFbqYRMG|DRYybKt3KInI`qt9 zj4pD~AXuI-3eVZQ`32#FcGGk{`N*Z%J{@@<hwAJhf73fPm?0I%qhsk|LzLW&<+YLd z7C(dzNj_WOvXT=1JV4K<Omp|f$j<*BN6!g*tJ3_e&v$08*xoIaN$9vMS6iLp2I|S_ zol)1I{7BFRL&~y_j)x(fK|JT4XOs{!L`y{ddY$C*b@3S;B9Qq_AIo~Y0P7(g<JSUO z*APt;q}W44LlgY~FIOh@wIV0Wxw7L;<y~Bh=;XDWw%s1es2DvR*DbfTrhUK|md$2) zc(D+OcycgM-Z#;)&M<rZX9W<W?kReOH!Ho7=6zoeIw{_ZF}Q^TObWA@Xcf++p0%Uo zqZp6X<ZnbKPKH^BxRmb^34@CZ65PBw$waxunX!ICPB08(pCo>*{~$|cS=tidY-X6> z3`nQ|XvJ~y6Ef?3{>h57f4Nc)oNG#Qag-w(xkF-^(X<eWn*O(1H!`YNZ(Lt(b#U*L zVePYCUJ{`-6j74YsbEszBx52)$-I$(8arjJ)&x94$4X|?$@dAw?cf5F!wmN&ReLgm zBG`HZ$LhOWc@3_pG)-fzi%EJ}=-sMy8XHD3e}MI_T2fu@IFU?|$3u{)nm$rrqS;Cm zRna`{Px4EeyM0YymL$eYX=SUqOS!%28?2{XfKSq>S27~>rP}@EIxOC!oH*bsP9w(R zwR7HTh;5&r`#yRNv6U^yL8-F*Z6pEnI)HtiSKA|P()tUBJ8I(t17o9~0L=)Qr|$+g z=$_zSHE}gY!vi|OaKLb|5b@Eua;_lS_VOEo!!yDC#~-Hial0|#7tKGZAVqu?za@7c z*q}IqzEh!ocb4}z7t<$ve_bcATlz?af%^yOr_o<3NXF=Z(EF=JbZU=$O12qyKC6}1 z{u_hYdr(w$sNSd~eXc&%?#b5Hl7_6-ZcDH~`w7C#78g*`;V5L>)x?`zR8J|!@;GBa zL}X^orkv<a_#NkXUcDaYaw=R4qa<VuWx6cwt5D}2T&%{a2zXNsi0%Mf+VbRjytH>n zlmj<)9iI13RilP=ra2R@uC8lnC+|YxDUFXPeJfqNEX;@Xx^D~<5Zvbrg5JWLAI$b> z9vA&lV-w9>mxdC1aIANP(YoqkAu%9+H-lIQV_1eN#y#g(?13l>%y2n4VG=`<#NqDA zUC#}hi38Ea#;mj<lvFp@smZ34TM8T!o|*l*ou>#ROlzwTBiz0-T95cqi`aiTWoMB& zBoe;pE(H=D%V)V?yo04J+l0f}BL9htV@+K4iXMk-MTzFfToA8U)!9;}n+a{FhO89c z;vod{kC%D@)o;}YUchTION|0esdvC_vHHf(N&Pa+vkbW86B)HKUFxq}sKRu_1pe@H z*3_i}oJQ6_WZ&1;&?t-X$rc$pT7J)DIPdU~+r&Fe2yY2`-bTc&ckwk!<#qE+z)Ev+ zCdkL-K-oDpz+d4vcj;ZrX$gJ`ju(@Jl5}U*ZV4{Bb=b`|dR8mP3}>Ucy@?r&`mWNU z`z`}ihV-n{Oi4u9GZ*x4ic^fW`rAO4e8!CgC8VD0PRc;fab9uDvM2;wHB>51?acF7 zQZ6qDOMGJxls&_JTkR-Cqk`F$FOkYHz|%p~HrBaMI?yjq1xFWf*;^!)8DHcEcm|1C zTN*e8QhbhRCgd^n8X0Ff!7F+`4kd0nt;^+kC7?DU<n7-2GB(yz^NlFFm5SO`x8Byu z?78g2ee=FZw<Vj?w0rXtFqC<0X78|_T$1JW-F<haOC}Dj-q^g4rpS43LoT}bx|@Gh zneEVNsiFLHJUp!2eZQNJ*6!liQllZhr{W{3QVoH;=DKg859E3e8EGwUtS)}l@*52Y z2(=45)L8m<%-nLS?NzkOI~w^D6QRMRXtBNM`}t~2rV=~DCMj5Hu~~4)m&a%3^P!db zcTS?ZPn}cPV!!CzLu-Fc&pUr&WMGEz?HEP6gUXvZclktlz_u*ATf64Lw|jT*=5~^H zw5qW1if`fc{PLaT*iPm-F%`%%2JDk)eLcfDtzhUo8~V0snGg%dz+?V5`xDG&B+ue) z8R$cPZs?4s7x*f=<iKxncAh-kGTtC0`3KFbMr9G#q&vzJJvDfaYyMo)=J7q7%SWvj z%wNME8~G>paxotI7I*QshN<!}1t1vqrxyq}@6D@%Td{dY5lffFp?{bT@89?+mn8v= z;Iu?AcGKOJ-?35qxqJQ<u_#geNLa#=I9cda#CL)h=&o2~h``wa_Su|Zj~YVdF6VyO zR`|qeh!veoH$n1!eMqK%q#41(B~Yhf5yaspWJ5T?;Fb($TPyi|{$R^|oEN0?pgdRr zYbDxCwO*i`(fc3adM0a$Np<4ySDasPa-P#WvXUQlnXiVJxvrEOu&^`(`X~KTS@-ib znZHD)j9g&tDJNAP>Fas1kPymcAaofOQq@Ox2C8ydE30RvH<E@U4%AO{ekgvg=Bs)6 z(E!tXT%4jMmY0Oq8+rO>5ms7TCF<$RBY4c~BnqnXOJY%(LHRS6xZrd*&|-YBd81+Z z-~#E7w|kgo8DptPl#pMJh-ehj$jMGr?XAbn%`bxRq}^(e5}~;`7WpVsbbE(}*PJU# zP>WWjU9HcLa=-N7$Pnvl-LNVi;Ngf5$G`$Ku#yec0I@z>U~W{vXgpN=%9^GuZ)dT< z51Ax5rey+9se<+_)zpZl$gsc?!r7_q&TCt`A;ye1AV8r`YutzrtZgpuCfsY4NZim? zf4juQwc*8FpKnyvY`?Stjkaa=DBm8g@OnS`$~Ao_2m8};utR6Hnw`QYd~H&ky-|^6 zZK-jv>Y3@%q2)`$x<*$+3A{_SJa`NyZS<s4%{yQ4{2OA$v&})H4s-m@;}HyP*N8&{ zeSd+zJT@?0<Ei_)!P<;Ogo-Vr`pnxqcpXJdb2~;wth=IFLQpxIpQ)a!We=hA3K_R3 z8Pii%<jkwk-GLScx<)#0b@BjK_+oDgN5dO6{uCh<SuW91DUD)r@?KU}(A5%_xgbNj z)OrW&${CCCJRct$7$@1`SgxZGtt5urM1|P+T%P463Bj9=GbR~^pS5y%q~8q4c(p5M zMcULbtkeg*xJ=m9Dwa&rUZXeeE^Kw8>>)$!Mug|f-w9EIlx!MJ>*!hus5gUq;TAAd zELO1n^-w!6Hs56F<kR)Zogyc#M==Jnoh&mcy?)~(2hB|1{(Jw85Rd9869p|10SD4i z;y0SbkxWYD0U)!4FjT{C3x4ah^-l7Sz9K_1A>52#!YgPbn>cw;X0<<}Tmcr)NH+!X z3)4%ABgI2fm69s(?Z}TlK1w?jDsOdxtm0<;1k!S3Hc1$zq>@BaLmIjpPFP)F#vg;O zrb1o`MUKEw62nHqIc>6MdG;^h5-z=*ElpXl5){<_wD*%wg(8{RUZ@>*8;l0K`OMg* zWsEJDI19i$Z|Z9Z8F#jm%hLJ{QnbUn?^8D@Z8_!VcjB0w6-?jILy2x&s-Z%RvB9oH zU-biNRWi91qS2KEZAPhP^{YUTUNxxrXgZ`Oe)^bNeUy1jFnckT+F0sB6a`)NfZj?} z_!LOgx8qdX^Hi2Ave)R;Yww2R^tfa8J@+xswBt1QbDL`%Q7*~C@G%ZCf=?Fiq2Ssd ztcB@0NnoAIta`iIbWT;01P9v^S?>&`0p6B<JPI7VRLKN7A@Vn{0-#oCVEWslzO)#o zltCQ$R)Uu+fA3gt<wJ|tG|Q^3rt6Vdp>nUc-Ihku7%ko%|7~uz9ThNzE=LL54G&u3 zP5C&xGKtgxh9+?1pius^%8&P8QM*mlB-$m4%ykq+E-D_qymf=nl*+8)eDU1|SNz4O zq;uV9)dy=nst^=0juVnK?!_{1BJILIZ*nnXi6pdrcc-upTdYA`N{!$6f#3mRl2UnH zw5~+2niYsiFZXo~D#}w>H3HJ<fU2DAIO@5U(~UF|cwU7P{qB+RnUnz=r%dh}ye5;N z%b~VWP@X_}<{80s$>L=zgPuHD*vvIzIs*~KPH;$dERkAFfGM7r>bSs+t}g0TpEOy6 zLN;Q5M97Wv;$>&2h`aNWQR^3&d&itk?9wano|B1N<`vT6)e46PgHg<;glNHP4uy9x zWmk21|01zw!|yHB`LlE0vXH&@mQr7;nhQ+Mgc^)$mv)<F7?tp6lqr$(#ni{iyO97b zBBYEEk6ICC6n}_G*7M6o50<?d?=K+Q^>Ui)NtGdkDI|TRV(AaU1dwB~83LqE)k3nm z1O9v{Ppt*rn**%+AuLpnW+>rrIu124rLO74W>Ur^PGafm6#@olo=GYos?Ph|K66H| z$e;#FF^4RTQ>+Mqz>ZohmH-#W0U$_5AY{k<qArxYE_^Xic^ipJfnr#6%YF`U5T^>W zWD}qt2%#$@@5RETfgS@L&9yV}5csU!3vmZZDDYHjR~=uMGZ5)~3bXSx_X!BF0r@K$ zyVy_X#V-8e7J*)(&foI&M7V}lEYh<(T?o_%6D)Z&1ZLSAWy61|wmD>jPqWv|<3{(* zPG6=CYVpQl9L|VwHE`7xI&BmB_mg3}AE(b3Yis@H2pG(J7IxXDq8C$u*T+|(wwgrR zDKMsxDN+G=gO(R>^ZgY8`E9^Z-YypTgHH)NoE$gI!(gGeK=&-Fgbu{7o|Ev~?{rDB z&<tudm0Dt!chAfFYhI_Y)VhA>u3B)WpDJ7`WTQ}7d{5ewRft4Z7VaF`AQw==pol$! zIyM4MC;|<JkOg@CNsKB}9;%ozh+@CbDdZDN3y6Y9g7+$n#n8lK%A%@BQ9(V-MbsIS zwU~@{{#ccT`BWvr10?>%r=ON7ofscq5#)=OH8n&01{Dby0Jyxmh{7qXOU!3K8E{VL zBB)LXBEwuW=KL8MUJ6IxPt4y0W*QYxTgYg#H%rksT83CNgj<l*(!Q%o)55ScK4v^6 zb#`OmDluQ21;`xd+xhTi(x-GZMQ0H>#Ex)?X%x<fU;g9P+*F{@fHw05#TK#P@2nis zMKw`C4Wu^0m>bPwphLE#ns@Tz7B>@s+RjPcDm6ZgzpY;A``wA5Vs&|=om_=2>;yz+ z&+_LI@{xtr-eOwA02tum-)f+VH7h=WuYqnAaDk<(vNe?0Gbl0FtV}4E^H~@4CDx>x zx>4qg%SY?n_vf1)b`uAMh~+EqTreP`#&n}-3@;~A=Pb5T=Y99cg#`LXBI<pfT3?IW zEwoQRQprklDP$*m?<7%kq_=zd%H;bnu1lRenuMW{`bA2Hr4`$12lYf%s;UE=nr8Kw zR?*40@cf$G|5Ou`C5cTBb#XinDQGL6Y)IdWMF))RI6d0@S}I*nMB{QxeIpP3P?B7l z+H({{gp!DekdScH0gp;63-^SltB&+L*;cElKpKKw8qJy9tB{VC%GC7}HFB28We{O} zDw9+7igW_jvrc5bHpes*WiuS)AT&CM4nfT<^fIBHep?uMaAi_n^eE6tNrC6OAb^=V zl3D~Cp+A#s@_+&~#)p+Kk?ONyAh(7QwwIl$lgY}-o6RU}5blb;z&w+oaVr@Zmuo+r z1@nrHDikfX9U(V>@cJ0rgKPG^9ej%AU3+T!q)(V_y~#m<+W<@A`Its^RW?@{xOv}1 zv6HyA6EkP54)n$s_RQERU}=e)o_Kl5?GX8^oU7f~y=KIMx2cKxTrzsCk|y6Fs4S$= znEokk9iyaQAUyB7y@<!W<a__vmtXW9kPg85e2<0v`<JuPQ8FHL4zssvRvJCK{vtnE zzH47q+2elO^)<T<%d!%xsfr797NRuG1*5?1)eOQ|eE|$W{YO-H`P<WQbB>Ir562|j zy8F58Nr+~kO&iAc?aStYZv$6v4c(bz*ELNG=q5B)E+@9=vOD3*+KCHw;kS0{MP3^c z1Mf<g?{oXI!G3*M)cbVf#9thI;bA(j97NCC-F00@0%7Cw;j*U0w<D1%n-Y~S@pF7G zbLTs%$gE^xLVLx5l--?;jM7H*Xr(FSi>2ET^OB;xWU)LPVU#8zH;!=X=?J*=M?#lM z7VYG^7?+413}&CImPX9Qv@)Sz1e-EV9!>53`;<q4nRJsGe^PHDnkvL~gYoFf{{(Uq zoW{@VMVpNRCrP1BT^sRHAu4XosuKrD<+Q16ON47MLCxRWF}a*m8PP!Sl@>X03Pys| zLy2Dr-`#JdKZHspb7+fTYJ)FZjxQEr?H2Lz7;$A+R1leJ32qf4XQp@FO&=Ib95PCl znuvRq`7QI%D9_`O(HW4S%`*UEycbBNi99{!&c4cu>$)eP6Lf0qo@*aCAu<;S9wnJ| zMMa!}MrCW?1cYm=yExc`YbBP%;3{!6bF1$;{%NoA*R>m?BCyzOityk@x-THvhN-jB zmB2xRXq#OfAgkZ0krsnZmHNzTquM5Ue37J@tubMH_ow9qtzHHG<^uc<Mhrb;0rT1s zQ8MD&!<KfUA2t;sQ%rGL>Hp{yc(X>Ui2wc-A%?F>6Fe9a_ov77EeYNd#NLNODfTF% z_qemB#UpEnQH-2ec&m?<r!|SXV1$ExD5iMpja>959&L|P79Zo6t{#Hd^r~A(Dr4<= zGZZY_>)2=ro|~y7VH5KLe_2!aOttGw3CO#Yid{D)2)2dv@=Z+86Z;flHgRjm^dj5q z_<^Zp%CJV~HSZaf9KI=QVkj)z)5-du?X~G;zdhSE4i1i~ljZrEqMdC&zsA33I=5=e z9%rI<V#|WD&LjcD?bj@mSd7k$*V%2KH<9~fN$9<QwAabTkNJ@vYzIx9ol>z}yXi=$ zylWx?7hLw8x*=%*X`!Oo73~L)Rd7%fZsOl;Q7dB8m~6e7uj$UD`qB7;Ot?SS)<pK8 zC;M!j_NSU;p^T4w?0&IsE<Wu(h|~G#4ag&E%^@(X&e8yCHEbO<yz=TIS#_|@w|MOp zca?-`a*o&0y(V7j<}?v4eLecJC@(<r_+0+T*u}V)vbeQ-2(z_Z4j2UiW~Xlhj^o4W zCbb_Y)j!%29YbWXX<0cbJcfEbi3(Kd+(sXI0Ut#uA3Z3LUC1c2h#dp+`X#uJ@Am+K z7ete6gpx%E$y&(|PggACWFv_G;--XE+n<yvVFBw+XLr=QNk9SnQ#i?RwaaG(q^N1& z9aQmO{$f9EdZ>U!0(ly!DPkMFJb-I%3mMEh01N)xQM%8G$UQ6a%TtJi=Y6Mv?Fo+o z=+WpKht~2QbN3w@0ubZ{G}yIe1sLqD2^fIERRnyr=jQaF^0)isc~JI?IxAf&cvQXN zG(sz^m{#WMW`V|y^29z&F`m>5Daw{Dj4Hbq8Ae!t3Nws|9ZcwfPXzy%+^#wJHuVK7 zJrk1#TiV_e{|$j0Ptump?C|Z4yrNnKYBKL;(y;MP8m3Pz$RWErURo%d*LCBrP~dAK z2!Q0S#PIVTTV&mmUI2&{Z?J-XI=2z_#`*DidIE8^onG0x+ZbUFyHOk^GR~~BGkL)d zR>s_~>17L;=n@(RUg6TY+xyw(fGliR9n#&6rFl?q<$ZJ5ayZQV0b2gEoA!WfoV)-= zwkYadq*2=^IGkLkk81M_ak$zR`#V2)tc$0e+<WY0(Ia}oVxl)tblEg6VkbGXIKPf) zL&S!XK!EFn-RrBl;q+ci42GAOR5th-)cimJaJBTP5JS>b#;+cjrfAO2bpq&2AX_cV zz+JTh5XzU}^2Bas&WF5s`2}8_0Fzuau25jv+W|qEJW-p_1R1V8(WNO`v%sg)3RyI% z2HG3(Inj`PaOXZXrgYno7u)OBN0E;4SEGtH-;-~MD(w|}(ZgzEkc!-?YupI8ySkM; z!5dh}&*df*&6exU7Q8*ix#E}@_m{t694S28vtNvNr_0l{p~U&;@2}wCD~C@zz?tuV zQDO1kmo85D=WkrPrlYgRde>T1Ib4KR!##mHIM&vt-``%>Led+Woe6nsy6rdqI8BvV zzHEP7@>Y7d>^KXsy<RShPVmZ8cx<|MRB(cd1aE_)5afKL6qa7w+BlL3>kq3o(6w5f zd1h#elicXfsj7oTfjh5I3OQ~@NXVCfD+EVi<{P^KkVG)gs6Xv3H0<5uySOF0Yl|=e zx~;S2f{{?Ek}Ka-Ls9$V4PznOp4ar|!^&|Keh!}?KP+uMjvmaC%l^IE(QsCXk0fCM z`%>^nuK6JYB!n%{0hc5ftV~RPNBv>V{v}PT7zCpL3N?l)5%3gRCa4d90YTjnI@ojo zMwlE@E*LGBLIxWfd^?2lEoTPajbsy4qqc8aa<h36f;aIiSb$yWRDv5~lNdBS;aOk8 zV}Cc4Q3VSbfl@prSJ7q>6286k)Tji;I$gE=F;}@34EXa90tv25UmY#17o|R%E^b^` z=T^qBov?mk)Z^>W`6oEW2wdn|!esLd1<C|5rAu=b{s>KdUG>J$qle<9|JVO>`{lK$ zL^>M#)=!Xw(->SU3!F`JdPM@2+T4mx^3sGr=3U^@9MiOC3f!)#s89Y>k}{*Xbqn`I ztKgjiyD58L<$@;rWQpneCm@?ZVAn+O>)D%{9vB^P858hFyocM^sMYW|GRyInj{j1N z#g_ZM(n|JmSo=E(B;=Rij%xHTFJ3AVN*oe2vd8XIY5-I^mxf;WR!{`JrF#-wrQxoR zlvZ|;Yyy1zUn&k`g}}Q}I2eFEO28h6(fzs>$8;#$TB}g|jf@eoG+{`1FV7Z=T*{L& z+zvf7g{qoH1L%M$DE{M@j{Eg4SCGs@%%_XC2ffWO;HRhTYe2MHX59jnbtV{q&;TL5 zbkP3OM};Y7Jv@^tgdUC?M&e0TRZx2uPZv+9-cyC^N^){CBS~n;R&#BxR&$!t3>?t@ z<I%s?pEusa&bOpSOCRxYa6N#$z7K_>r|xdvlof?s^SZWi3*1nM_*~f)K`&jn{<Jvn zPWDMQUcXX?1l0|}xBddw+rEGQ)@Z!%DM72$e(d1$>0w-d_5uLcdCK2Fc-)68D=>k+ z?b!`+Je~+mKHYO(-y^Y|)^&+d3xxLH67^G`?uw--7=efXS+E9hkVtx9T0nr>Y+INI zv~9#f1QTvu;Dky@K|g|-NbuS$tS)d;qCt2)@PB@wpGp7v`NA%msJHUi(#uc%`Tv&n z{#(TZ0l<X&nJ_g9`KQ|dF8a4dFLTVVhU}VK0FzC_|L&<5{zMo!^>HIok3r%8bpD@K z%WQxX9^FG=sm`(g-P13@T(lp<9q9r_#eV-klm2O?wGU2z{)&mGx5WLgng8ht+FwWL zpIQI^G?5R1DpQ#rY0E~fmz74$CeCOyok?q$YVn#|iQ4)6_522>c9J<xjLcJq#yQST z2Fm}tct87qe(|iLyyBsWw7P}Uv~2P80Zsq;fdwxLmp}KK4jL>{yhT$pHM5%EOx*a7 z0?B<ZuW21VZ(Eep>`w-<b(O?)^$4X`=~;<x4Kn1qF`z@-u-0*g#k0l?$oUlW3-Gm+ zBaNsI7mLVZV>5XxW5vX?IX`Q2hBFs42h*dIqN;~n%cm?o(`tmcDcWTct>R{NYHyol z&-7;63T>(U!-vyJ6wO%dDozbmxJR&u9>*QOFT?%po0M&@-v??qA-zpOd?n&t`Es`1 zG+#A}O8zo974yY(3qe~okatbZOsN7x#e<^c*igF+?K5`4T;RGhnMFmrsq9aSm8JN@ z0T&xTT~wuNl+S_j%2Pg|N+nX@N+ajxRuB!VSBl9f3zM00HA-qKB+BP2(^OX~mAydb zO26F+YsKrCr=a>v8qR1lWIIdt4_D<b>=S~z)$?Pr6v1UcHs2)}CMh0d4Xwfr`t?N% z#?f&Hw9a=TW-0_IrD;;>ViFL$T(9KA#7Yzj?~`_P@U%vx5GuOPC}gW$>ioHquwP3P zpeoo0yS@BbpsO|gQ$*m4ANud6WTVY|6_JjR*+VkXd5Eq7l*@GY*@thHjU^{ly_4KG z^M`lm=X_6*OgQFiZt4^vw7)I^V5WRVdRizaE<%aN#wY_Ucr+GL-g;`bAjnGuV4-CM zTnpNMYm|7y@2<pTjI2c7-?RhQs$s^@9bVE@#i-g7JN74wN#}yl;qx~O3A&B$i{?^r zF~C$@mg{wm;OwfZ2<#k5`IT`Hpi)^Jc5K8$Hr&lV1B2ARTznVQ#FxQ8{<_!*8MolB zCkuD%o!eVDSd(f=ZR-WoAbBSZRt+ad4MCK9qU3`!;Smc<p?#4PlQ%atB_j%nrB0@% zGL7u9HmcCZ5Qz(-co(X7p{Fz(5P9BeH_Yn&ZUJAJq-`m}W^8p-NoXu6n*3eiI_;`- zrP0WQCv_YTCvwoqqFO*3+#{UkNQ8$C8~#1v2?qIK6B&gmxRVn6qY}?ys04Q+={+1< zxfNH(O1}$V@=GX_r)Wogr%hmZZG@(tw;GYf#KRh^H1&+bo~qU!G@?M~lBv<Ce?5Fz z3BT9Kz;EQ(vSHWT-F@gxN~IfFh)@o(((^j6tEwyfh=bo!Ky3_vQm{mVv-%oq;L^xd zC1G*!EpY}^^$OwPaTOm2e-+$E@j~-H6sit}Mywb&xQ}SyVJ;iuy@>EzX7ZJoab(wJ zkDoOZ{GcP5^P*x=@mOKTB0zNv_nW_PP#^m(&!YG6Zu|3os{6RvK5%FDqo<{|RV(!` z8w5E?AD6uW(=YM_<xMPDUt_$l61UBvL#pr3-cLc>Th|t&S$V}W;pYKZm@_h!9xa58 zMhg7B*}RrD4JN#akKv4WO?j=Oa<uEN+2kpMuIS#=_P~yA>r26)McZpkgxPOWr>)NG zhBL*m$_A$|?A$0fyI;xX)};MU({MR~IQ(#2cRRm2A9oLCYZ`Tzh@3vT3%=-?UPu?5 zpH5nay^-}@fx{X~3r<h!9DCXf{q`u)2k?y%1&Y8aKD5-Q0c*QiEY&z|>^zJOyT_6R zphiWa!iBF#x#bZ`KhI*9D-G(g$tU^9Z5)v46pR_{<F5=rwOPz;#J|D}yY~x#9S37u zJV7_7wjW192T<vz7)F)ra;4Q;5r3Y>rECB~domvFVMnlZYBA=+L-pGN0URi?a*C^1 zNl$kPrElCx$DiA=X}VjD0>=#64wJQHBy>eez!R;=A}1t(mWFXh?=*vnobR{4LGmt0 z)eXk>b~o>#9@c6smd!5Nym+_0-ub84XgR>XiA@mG9|-jvoSb>7o+g}6sFm*@@37Ax zv^*t~t+ce}S?f@LwYY2aHrJt+aZ(y@CYzn#HLo+X+YZ|JS@UDo4qkc|TdU~GhVs#3 z_<FExRe(V#zjS9{vXY4w!F+=}JI~)ofNYdEFoAu0VA_?xu<!&PFP3xgM1Vqsx6!;F z#=VR0!LxYV-pAi|-f_L`{I+*DLvLTSmZvp*)mrw)=K-;fzg*3ZQ$lIPn2Lj=s2sRr zCknO01?&bzu@olN7;Kp)DWf4q&HX~tU;(hEbVk?~PCn;Jt@ZTsyO#^at{KHB+LfeI z%In6(R8;|o9O*#QETrZtQ}(j(jMaq8s1*l3Nvy{P$T_X&BB|*Ww%}V6mKur%jVu__ zG^jHJxz2=QC}v<n8Ai5S;5tk7e*?s;v}n>{T13-|iSwT}qQ}mJ5+^9RHTd5g715UM z#-<RmO^8H}&UqJI<v~mtRrYiL?E><mwjnkk3w6wpTFDT_Q;iIJ(hkhnzY%vY?XhL0 zWASm?&>u(G($FHBcBGA=wwoh96>b?qv#+u#4mHXhAlGeObf>kKVH~!nZL5M>0Ev^X zLi`F=!7)X1`h&;zx2=LwZc)LMY4s@FcvCmD8;0}1=H#dr+`5BT#oHa8A?I};-Bs93 z<?##X3X;!*SNFit{0UQkezdE|{-GfjLUoZdx*2C3JWIHLn8(Am7Eh@dF*4l_m!r3y z7SbOJS>Mi)`C^M;_BY%UKiRkEKvZ?dsf}2hE%*oj(x%gDYe5xm-urbIqni6O%nUrV z#196=Dev_5qr}cLfm*u#yy_E5@1{p&Y&tLT_B@lf8=xN?)R?v|(d@Q0XLnzdr*;+1 zZoIu4Q)!8n3MYtA-n=@TQ-sFkt|&~n>J=onT`5!$Ed#rM#TahUb;CH30M}~l<!?m8 zzsD(z7tY}!Mo6CJC*ugY@*}dqrgp&LJm#xKGwSFv`1??nYF*c=l-o5!$h51YZ%+dQ zQ#g;BZ(q1_N1=n?$6Y(eP>svymI@&^TZIyIj~PV+OBB7H0P1NS;K(&eYc7p?{Tonq z$b&_>0tpDHA2c)+4gwAj8?_RAVpVOh>E!O{*l0ouLJ1gDR$*U#exPIk7<9-e0a-It zk|<A2O^pp+KBUju_4Eo<leDwIgD>M=V5hcJ38*r%722Bgvw804e)&L9V;%aE$z0VG zm@<IEwl6}+I*VqWByFxN-rv73HM(zCpTIx-UNV|xeMA}NS-KZQr>ThiHyfA3%R^^y zc&n?QteV}PK=kLM<tzfJ{Z-rzy|CWF0$@jSW_$5=A`%M+P6~x=bXr>cZ(gF$VCZ3M zFF**%Di&dEoL&|J(QU$mzB*xDCSHWx5<@ifKg;;SrITt*j3LLFQC4q%JDr5p&QO8Q zzFwL!pmf3@R3hey<h^@OVMPx{@~1rs;^-=a+7b;8;>=spAATc*FxcZf$(#M8lxv_T z`vggK^~b=2)Pd4oL;-hvi)NS03c#+04q-2@CbeRlrbW2f)n|uN{Dq|jRZe_xstMwe z1&-ZQIqBoG6IjrUO%&IJ>p|o)vrT2^-x!Lg`SSKx_3Jj};`s?2h4#)Z|J1XT9sfe+ zC8?St^0wpeo1n`4^W@DrGxn#Q{M(NUrp}fsm(^+L30Gn7ZlO-2HXk0G{w_TtmgahF za>2}D%aFZbW1uttN{5Tg@qZ|E4rPwaPJ?>79fr6Szh`=ssmj@?ujbjQGtKFSvMyzl za+vmEB0EKC!nm9U16}e~!A+?>L1vhKS5(kC8vdbFb*d}UXSi5vTn)P+AQSAkYw)jj zq@^CaS{ACX5sdn@2l`!K%Dd`(@AnD1y{@u9+~cjFUA|sa*!H|WVb?GDdQQAZ0||A1 z9gTG3yu-KOqgoKWZ+*SrWcYBYdDlZ_eKnUuY(9j7UE7f!aTnb`G=D5`UUi$Q4b;A0 zV{PyfzXaN{IXOJnFK>-}yhBB-qquUlK<LU#x3{2Ddsr{s_k3U4`hK_P(s9tFvnm1# zy19e27dIwrd3)PpF`=G~cxkJ1xlpUK6Dy+NY1HJGanAK0Sr#(DB`70Ol6J8Wg`AKQ z0wZoOtU~H$_V!(BG+r0pcl}DkgT_>pUHy$CHykwBi;BDwiinXRbD$$3Kj_<DAVq>n zMs4Ur7@x0CkpX-wh6V5INxLr0PBGgyMC0C>ih~Ub$z)tm_?X=SKGC77wu3C$z(MAA zQ>(9ksz(!q5Dm0q@^jKWb2|MkAfcu=W|0LR=m^YYBFrvhvr#dBwP4YFQrf^F%Lt&9 zAW0P_2R775!tSr%uke#nb8;6sUs?jKI0z<o0UPBgrDXLm9a{1d0#LQrgT-bH+?NX_ zvlZn$rm4AEr8@~2XC&h(w!GmEAEbn8*V}MM=P@ZXXzLdRKG}n4W_wwBllym_%6#2q zY5<G(MQtJ?rCjUW%Ff7axVxJ`D4Q}7MAlGh+)b+Z%013r&X~X7TnW`*q+?_w>ov~_ zHSz2vbEIP>aL0LO5;1Abx<@s|^SaNB-*{AWM1l$%Z$=L@LM6AiwKrr5rk3`c>6z0; zl3Qc4M4jd-MF%!WlcI|90xPZTAY#lFcNwI=ZdQDkq0R||1dpqoF{xOtc7%EH-DgVQ z^K%<FnxHIXbTLtrz-x`H-yp-5uhh!c;?WpWuj7SlV6m|oJ%g=2ePb8TA`J8Ry=6q& z@UPb%P6^^0^pO)(m(n%#&6=xImD{Poh&{nODK<(x_t%SMA;KrfmxoPkLavy-71<?= zq;YU`&M+0Loh+ne*fkV%pf;{((uDE(x>?6jO7j}A4aHTHy=+xBTm+${&3Bf0w3kVc z*(N`B(A{h`m~H8#6z1!a)MhOd@l-YA<}VNd$l#A@?se7ceT+%77tKVcuE9RVMWN01 z+qbtl@8B5kTKMXts1cYi=Dk8fb`03CIqo*m@G3Xk?^@r3)iq|;ZF{=CxzSDuJ*(e6 zs6@gWTXMMHM%zFADPG-cxLL8S)jn!$_0;Q(ryDo!I&yL}m0Ax5(H&?17O~w1v9LF+ z9M>flMe1gJrOM9!O<R}cvlynZ{0wmpMbhzLCnIv%DLrP)>4D38XF%(E+ACehd9nJo znRW@v_PHA^!Rvs#q#0(KjgIYnSWDdfoVhVgq4l_g>&I2+Fb#k^d)~-wd!>td;Kx}{ z`MTVc$U#HvY<A+0nBH<$Ouh=@=)J!kyGz$3Gonr9JgTnHi>wml^(83jD+gEe`GQQ& z(GK$IANuey)AMXNB^%*dTr;rvL?A?+AARceH28Mgh7m~8y!IwX|9n`5-CPnx9Rz~Y z-e|vfHLj7G+<X&S3Mu%0->#d6a$j2>4-8myP1%ErfUAB8w%>e6WM#6C45xp!t&>}D zxwv{n#qOwL$f;>2Bst8Y#DWl)>iNyhMOueXZdr4?LgyZ=_Mcn;TM6!Q1>S~Qg9zn< zv@$!tU?SgwYlZAuI5^I!sF5sHWg;gtQ2&HI;;43sM_c{Xw*3%L)u=YF9qXi0<cto4 zR53H>ZDkSn9Wa|V?jR@gFV1yrTbmm>z-Dq0r+KKbrwg-{!H&&t2~%#@3<i+~gNu<$ z`y<+}mL6(y*pn2@j41%3X!tiImg<t1%6Bk<IH1a8s<m_TNm8wl_yb)Mv1}-)wcE&L z?`0tIz+P|GBNJkz3I!N4gg%)KiP7Y~mVik#cF6hnF5IAAXj`tdoBOrC%c`o0X;(1c zvN3G2_ACIKkt)kQjVlwHOh^z2D3uI(|E-^2xbXpW2|5e0q+;a|g94#P2gLDQ?e!&a z0(xyEsY_}L9m$Vho5X1hOHz=bWw~*lLU(|mnw4~&Mee7RAYPW5I$xYGy1Z+$0?XFp zL)FDO{-}*S78GN$swLVhf`~7|FgD4yzITXNj)Gich!x@cOFKp)&Wyayzd$mF&<<MU zVvHbws7Jxyu^n@y6|o<6$%f;0Qc8vcF4o(szAR@Fw8hS+@jFmus`@w$3~}jiIGcRX z=K0H=7btbF5O~(5jN;#+hDAOPTy6V+VrEk?;^mpLdHvkXNNGU`&XCPz>m_;g{MS=p zX6cUHyc2gL4UTDAfEzyy%b?dK<7&!6JK)Z**J)y}``ZlS!}u)eEF9uSR#hHsbaZ(1 zmo0$tHpk<sZ0_}?C6}X#v>czeQ?*ZfO;$Q$7V9D2p{Sg~?{<|+;F24%Yys3v8Oa=) zVm9G5NHS>+`%;)WOI|T7M)OMyjlBu~l3`g5-@>Z#2N1cDZI{+06Q?~ZEr*Z#p(onv zuC9u%tESsO*`C*Ss_(mK!}o>cFjKWI6E98J+;u-((l4%UmG|A_A=$Cjd4YS&ZAnj$ zMg4B8lH0ahbFlT%RSd0*rfC@`=Vr$&?dbL782?JfC$4qYzA0L8f24OFjSl_gvK(%u zx7~gi%@MZGFrsP+hsn1lES9QGr=3bLhO34h0X0TkUDg_#>Od%9+CQ?#Tz6Mr^YoKz zoE%Ffagr8)Lz-n)E@S&!&dcaG+^$S_hBQPDrUR-v+gF+C+T+mR@ey|uXcI@|O-bz@ zPm}}3PR=Z<4?u_;IlO^#Y{+GH#?>rKKc<%u$^*bP98<#zBT?843IY<(EtYQTqg@6k z#w&~hrEm7S-B6nIi*p0U-vae>Y;+lYo7VGYsp_?@rivZQj?zUJq*YVe7&!HnVXRwO zH6HS7Cx$MaI*ZZnqg~RTXA^<ton6pT<0wR!a&bsMAeIy-l_p6l*G!u<CV5jkxTb+^ zRpLg}swbuP7BP5JdTG}zY6>@|X;q~f8g=-3#~GAuS5F9W_NN5ud9a@oPrSNo490pi zOKwQCs_)+-7fBG5&HOH{gapzl6`_8z17j2<B-NVXU<&A{TwZUj2J4!EbM-A1z-u60 z-Qz~dVpfE?>3433tawsS9Ss2G^_DhbP+l@{Yp9rI;*fYH4vGv&A`wc)Q}!trYCU?Q z%$>_4aq$EhD@D41sAuFP6L)S%hP|%|692PPS{{ly06U}6(0)&qj^~Y%g!_|Bff$wd zOh&GIUrmb>W!}qG(55JfYcLZiF;rb}|8c|8ol*lo*_ET0lGj_7h-@V%5GRdnrH`Nx zG`Jhzmc>Njs97AmOOWg9c5ZQI-{b@inN<`4hENX7kI)R3MxSbs{KZA<5VW%oGD=ij zDV3cCnR<XWJDLk!AS<B6{}*~+L!%p2<D_ao4D8C1>#GOuyTN#bJ5YNrh*~Tb-KS?< zOEvuSMF<bCwREbNZ389#)k^5-On6;YFvfR7_v>xXuy!4VRR6J&H(uCP!JnrvG>JNo zM`jRe?0>QMPtlbH?H=gev2EM7I<{@www-ir+qP}nw(WGBlYaO3_W1UDd9Kg>nxksg ztWj&NS-<CBhby5%R=g({yyZcYF}m4`7mnA16hY64sx6{C5JT(M!SHWs@P5Sj=Ycl- zX1tf1wNr_Dd6kXQ1W`D2@cz4ba8G4BKoc{*a_f@l=!wg3vb^8tBQjF*9e08fYMRdk zquYB0J|&H9OQJmC9XBGPoIb8Gt@z&lTD?f%T82kZaJeK{Cd83%+Q(!)9oL)!gJ0pt zXX_0TT1LoHkfOHMU+)d_@@q-*UJQlk$e}zBr&I+E{4GEvQwSfpe$jujn_Ptj4;vM7 z<vD2>nM++%8fnnR1P*ki6*42l!r`m!@@Tv|#ry}(R1W1BDFkb6k;Hd9Y%R+3!c<n< zj1s!G076v*I(H$JqR~kR=tMk)n}JyPWx_dAT>rKcGJqcSd`l>pJ$63L6)tU?&lU6s zv`D{&&i6%xV-NmS<{_5|?v0NPD{rtZwV=LqJIv9)A1VZcX20>1Bz*ug#TwiRDM{O% zf~;piC3IVjWfeOZ2k2y#pKUvKap6!krsxXvyHdEo@z^fA&7W#$F}X3?4K^<;jHS|} z)Nh#3N5U$#so}33KP-Rw@EsAfm(0@XD9G1?MUC4Il*>w13Q`ccBx&U<>W~&QqwL?C zi_?2?D5gY=QltQh$BjYc#GQah3mB_2(zI4+<!v2*zOo#GZ2(Z!^@=JVo}t8zk(Kty zD(<v%;fP-7O_NHTAWurjLCTB#YlvqR!2d_H(^>gmt~KWG)rI91@z&|Zx%Wu-cp-*Z z1GL4d{)J3CrMWl1id8GslJMyIr(3BcGwuE9IAnrDw)V4M#dVCJ8yO-m2C%hBa~;7i z8_6^K%NRr4oys+p&!fI5ysyyqtzV`ygvz&SVCuEjhFY6Q1_pnL1Y+J{d`Yz*pe>l# zmaIH|=gx)o|E5q@&ep8LoFhZxo}ZfabmP6XEu+|52y7f>a^7;J{*wJZssZv^->CU( zS+mexL9t=$<3J3T-DroU+k^A@XSM)JMH)|Hl8XLpozv@MmTkVCdK(GYs6`m2)raiT z2N&HLs!7$|g7@n6kqY@sZ}a<rIp;i7_?rMd>*uG??(FH#&ZdLe$#jiP2K&8<c*ic_ zO{ccAxDH!BQO+yYdObE|b!Jwr!*i_g4V~{--8BRsaYQ;hRMH9mRzTVTq)B;lr8dWr zeO`4F!r4`g8kos65mKf4pPdr?w(+pn3@qt`?b&|y>6hEsZ&u;gF<@1>`<Il5I>@ue zQ&&r>FKxY!Tl~9x5ZJE#pYn-rroJ8|b$)ZjxIbROHOcpAVS&#?A1*f|M9^JC8%2@e zQxu|oQ><cQg}QM(R>B_h`%mf#hWbaPEgFxxwU?`P5X8E|zWKHwuth_(#Mkzu;Fx_C zC`J#pdyCBltatPsndkLEI{dqkm!70v?@(;}UGq@X(wOe;+=QbzbH>ELAuuuGMYhRW z><ksbjD*$9?D4I;q`Rbh^<Zs1J(IGaJHL`%$IRikR54wX`!ihk7!F(@2>ni~U19h+ zy2C<?A+Ul#<m9UbE(40kl^<TPz0D1j)P_+DDgtQdJrq_zI7*0X68g$S=!k(g_rF^f z(fs7b+M{ec({vsGwCL&&h)mBL?_Ofm=lXe?&P@Mab%L52&^g&p2!CcJ;QiJ_R%?0x z=eUOZDG&3XmPi|3Pq*t^d8K_?#;tm6q%01SjH*n06Z8NT<8eF}HJsjqPH?d>aF9Mv zi4g2(xVe5gTdiu#HdyzxdW?wXVE=wn1iSBWoV+r7@AtiWtv*wd{0x~aG*t+fy%WZ9 zZ<g1qgW|`3*$%b&b%GU|v9a#MY5iIjMRb4a^^T$T2L`fz4NdpC1$f)(p^A;aFy}_P z=hGL!PE%Ym?CMc21SHDG*mr27e``ChZH5;y^R-To`%hGBG&yb#7@aM+ZOEyqY&bOX z_A6A>q=q`~g+`^E!G}jS(NSqigevJnSQ#95HjlF5J1|<k-$Om0u-fFRl8hHTA^d4t z@7>d1pfvIX!!fBqvh842&x6&DdhKy}JL=9gMqBC}B~CI1sW_k-?+xzi_~f43R?T4N zy+YdBpCyY=?5Q4Qv0=^GKg)0k<Xbic0iu%+B(ITw)*;$@VTs1)bDUw}%Dp6sCm?Up zdMNXZeJ2}=BKbUjH*^@~Qm(49(%PHK(px+|DmpY(E{krT$~~5B8F96BlvIe*8(w>5 z30+UR5UbCht(3rSzuf`Mcf^`~51g1rw~3R%!K)p$r*>7N6Te4Ijv$umVuJ0snsz7u z<c@o0CT7^i1s*A>4-m-RJrS=a0CE3W>Gov2<>GC(nAW&{Q;+oGy1GK3oGBI7@4Y_| z9Q-FYAxt|v=jZL?Oa}J@n~#s~M~lbyMMSA&cw1V;^YsQ?SS$$0Z%4p)60_U8J5irz z9|34+W8S0IGX__mL@us9Sw&rn(rC$v=n#eRIx3y;D)|%H*N2^X$BEG4q8{|K7_Ya! zW5`lM(4H>@BA~hSp@xz!I`!mh_WC0(W<~@ZU}S+DuAGQ7*T#Izkp5)wG#^))$j2J> zzb5yM^{h^Y{rQ)z9k8_NOkA%u4=Y}1JZv}WwOiP3sO0qr!UXso)`j=Qc$R+?ZRad_ z=4`%0z^{4y5%yYqIvNbNlFxa*U3X~ojCD?UKfb;GT2ra|TC|dUbZcH+$-Fm2=o6Wp zwknUrsM>fX_)fVjky;t@vrGC|GbLuOf0JLwgj%b5PCO_xdkd%eFDAi6hT|PcXi1iL z)X5s(*@o?;FIndO?z-g~quOGEQewKbTu@)TDG?WoI~9h&*1=|JM;(ur7MtdV(iG<( zF>Ri&+>XCeNYBr_6x!wTUGCZ2`a5O*aeJtYbL1hKfq%%aFTQs6*?N+;y9DVl6Z<E# zg3!h!r6QlY>I7Wty6I$gw1j*i%oZFsc(kpk+TU$IF*q^+g*xb*dOrcFAxtgY2No-a zy+I9-74dRO4<SU!ydR1Ce3?%m6fH1{6=AT%j#T2$LN`aoY${?+KjG5+x}Pl0#*q2^ ztXceMqfCMatcg$(hd9K{3g5L{m%Ql4?RpTDx^)x$n-ce<@^H6=(PTX!cT!{!3RRD} zK+tHOWg+}$++uv;y}Wv9IPLsl6kY7w)<Q}larH9Mv>KlW8kc(jC3v~rGO0qG&g>`A zaa=<u3L>1}s737M^{?NZku_GcibgmBJyw|-^&fE4<=Q?c!CVTM=&6AxqPp%%4$G?Z zSh#5h!2aP0G9>HX{k8Ay_<_6bgh8Evb6Ma#N0`7XyZyE6Vh3KgYQ$O4qe={5yoo{! zq;oj8`xA4lB@OHw!n@Hb?P6WAg`)^?o$t@{5VE|^Ip^CMJ`Y$gf#8_}ghuWKP6v)f zMUNFeK-pu<mDm$lWS{-2rII;{{j6bFc+wNgR&F-~aNB{_1a!lj;t<<+Vvy1wc7o>9 z4+yL0S8Vh-H3AX6AS`y*5+6sm@jXFM;&RZ6*;wusG$46<<~QalGJeHiygeU+ntNCW zU)|W{mJ&HbTCZEL`}+!dK#mHWu14UuA(w8+3PyJk0(|zmmkmfcdA_ncBQbIN+odnO zJUq`avLun637gxGSoHVe>Gy+OE(hQRT#sp{gdVqB<70Vwo-+E&V9`>W&%$|yNcFKh zQNtzIkHRGgxQX&V#}A9=I{4NV(lTBzm$tOJa1Wv#+!9GgOu+;-HXBHcZ8H<_i#UOs zE>3UzClp7<jNI@#IS2*g@A#<U>sI#q!<ovJWh&x7k*t)<LP*(v{0`r;4rkMk;5cFl z7NXf#8kwc4$3b$Ilgi{|r8gx@-nsXeS6{CY`Ym76c&e^1&Uq$)zfq~=Z@RMJr@+9b zz(Tfo{~M`Oou74@y+lOXx^s)j0;#=e{`0b4-mq?4c-KL5=62|Lis_HMVm#*W20m5o zqMD_O9Vuumo=8p;SQy29+;h{I4BhgI<)rBtcfthp;iEE)il&zde6LxaIQ4bDNP=pa z44$%I@d+wb*!(S>4#np{MrhUDN>JI;y@p;9+j60n=R32fVPFmn!Wm`DlHThVQOeS& z2iNn9X@iet3l%M;OI&5>ha1Vbk7=^t)x{iJ^-hyu5}F*&z&wChO^H#^Roh1IR-qZB zLdncG=5A!;nRNy@#shlzR-&IrIfu^zSPudSGz+yI<fI9W^+1Ba*`&gp@zrBWF-QoV zL{C#u8e1||9!Ruj=pTBzyVUV&@EGxGatcaH3XswCh9(yKxQ$mP%eb={)TG+tGL-Tt zyN3#0bxNuyyv8GlTCSpRm+_PmYfgNU;j>I17uH_JcWa#?l2MS;ZlYBiRWur@;M3&? z#T-5{ze>jhl<LUoKBc#H*jV=T6{7No%eCC`Y~(XAfNGVlmW>93OyO;q7Z}5vF}N3% zd6-hRA&{k_q<lpcm8;m!o!ue&25<>|bh1p)Y(jv&k2U)G8iX6@x#7r$eLfg5>SSbm zd-)`;Rv*U*C~J&eVcp}2Ke<iUN<6z?tTse*yw?C9OfLFQ#2sRMK91N*q$EbCQy==? zZ~F0sc99W1g_JbfWO;LZWM-Vi_etL7!yE0*u~+T-!Ww}ibOC|AylfwdxY&f&rzs3n zWC>$NatG5Yx{2n$oT${L+Sn+A)K~~U!3@SjkAElBp*{|+gY@{`JD1bxc)>h^Kt^Fj z4Sy!-Le+lootNXn2Ddw(kJeF?jU70+#tM;rwJw8B^*Hj^no!f0mov4|*BXseAE-E3 zM<lsljJ-5%&s!*&79*}jIdLtWFP)nNYw91V?~?y${^0w?(ZN>`;fe?e)e~S!LuSLA zejI>24`1XxzJxnaaH%Ylg!XW+tnrN6CUeM7EKBD@Nm&EgOQP`Y*sJDa;2~l~>FBAz z(6q&ZoFSn`t4c)Qo?=&x!Y;dlT6RU6&X^cM^H*BVZY}Mk^wqv7P$F9^waBPJhuP`6 z79?`>d&{yV<yi1V#K!WGH2D1?$e(?IuCUeLyIbmdEQUDpo4L~6`RhUz+r?nV+@(wq zCDiK`;Ew@2(NLuNKVF-2lOS8~R@P}v6%ov3a0EUFEo<`KFG41jrv|=rT^+8*zw%5S zs`P=mTaAyeTI80>sLFm+LbFbaT7>AL*^qEv&ZTl9H?P|K<q#MGtBup2Thla*Ei-HZ zZL-$Z+MwG1jnBb=NL~9TFU&U?RqPdj%r*qCl#5mK9+T+K%gG=G$V(vXG-#|X^ts~{ z^Y{(=#yyk{TISBmyDkEwnHCLv-cUdtW{0FN0$w`iI21JJZhC8y#aO_=DF7HfV}re~ z4UPn1m$|z)e5o~yJ!?Ormq3%o4Y~dRxdU<>RpPEnnzph8_ac|ro{8)bOi1>)On}&P ziMHaTefdpb8z6@d-=mKQX_9wjjYV5A2FFmo#iQ|32p}-54NO+S*7-z&*lbsUBBRd~ zr*-qqLlD)i5@{WreEosj?I&B-r&rjsvf%5i{o%ro<ez8^2Ja32l>!6<$14@%``(!S zS8c`q+P8xAkE$$RFVT$n<B-KH+A0kW#M}o6yY~tNA->dxxd-hNt?ACg73>NR?fEHO z^l~k3g<q%HxvBK6jR*yp7>E&{%>zeF|9Dn!-c%+7b;>DGZcZ$T2TL&Ga=5#~D`OVg zDsJ#q9#C|8z)dhW^1eEpafhR0u44uj$SV!BY#!_&1)j%hexJotZtXG+S|Kw|)oH!S zHU=%P2LOQaGHjrC-mf|H++IVp7b;|`STSZ7qEmNLy^#Q$Dtxkl1*#oKv;8=>7bt!+ zcZXm!W%Bw7!miL>XTEaPs=I5%z#43f$QPhA2i_rk<I-F7fErj=jbTO9*C~R)UjG3E zZRJo#O0Csg69x>~SIYN^J4c3szQqvpppc5u2}ywq?9jK-+8bQ83Z5BzZN@d{Llzza z5a+Sh3-&a*5U!b#zKxKtAds;`i6ThsKDBgAy^Ww6UGf`b5q#O0Vj_{9FNu!;yC0H_ z{^e1wg;0)3ehU$-XOD(kG$(>^OB`s^Zru;pSHb#`{Hqkw-Yp@cfdK+%k1IIi1oI!R zroKgmc--nKD?isVLte8BKwA&KUL7*8BAV*<xm|v?&f_;)0wimPyp<<M&tm;Kifx~s z6Xvaacp(#7#2Wm%qYSnN?PVK*vUPP}hDurl2pTUkLV;qXo0-}hEZo!5mcb1XIiTdO z$XL%VFR+AWR<2aRoUIiQ;p<P2`}V9J<AACTN()>+k|5@8MQx~F{H^-%7H5S+ki=!j zN&D?=^B6c0)I;BQOCIYuY(oVcG*yWY0ecg6ADYe=2AORTMUYW=E&sS)+tph@sNKr@ zuT$dS2A8+qm;NTOlI7(Pk%-BX11-5z93F3cfS{VE!XvHk+3y6Gq73`hqs^&!1zqf| z%KmT$U|J?XXPEB}<?HVSSu$B4-}<AHg)abt@mskyX94`pMz4pbI6QZ6`yUl8><tji z2dY6$x&V=I)(9fWR7&OatNJ>GwJmE9wf@f`@w{5%_DL8KW1Syy|I!(ZS!_8k0rWO2 z>}eRSoWXcjGU)wh!Nywx8Wf$(F1U5yhy)hj%;+wWL0~2byI12{q;rU^nQs_ufOXV$ zVp&&lZ#ZHEV;)1wNcO19`>_xS2wuGn`<x!AHvlJz7KIxELi&w(L{Nxku<&C8JqA1M za7#=sr?_x=K;1U#C9o%o)j{lXQFwL@NLscjpml!{N|1m36gMvUI%ZlFJKAbM-@ZEj z<Um-<+7Oy)sE)vH_>cJ<<Rx-UBI?aZf?a-4o_-8&AaimY+TIUq&H&B@O0E@)8ou%o z2gn5ce%>>xc-^lwt>Zw|2iF1u*Sp}D)um5<B|@ALhpS71rlXbcF&{A>XZh8yyavL5 zx>ggZC80v+8<A4a7Wqtv?LX0V^NI$GW|%{;ivTc|QHeX@K6Z%t>_*X7{~s;<56=ED zWr2T26%ydv(mdI>_3$HLzX{~mj`Wnw8>$2x*NFAkMUID9fU@a-kZcN~{{-E}zcvtg zMc&c=5nA6pAUBqV9gz=*L_^djCdG*6GZI?GYLjldShoL&W4`vVaVSXEv!In~y4dKd zR%(4H_xH`@euMuA(cN<X-TR5>iJg6AAXdY}b0NPg&u-3<BaPkh#s>Vmr}EMrxThX~ zEGD+gm;THm+Gxm5)OOM<AXlN!7s2yV48afE*1b)RhdkqUx2@(EJD)UQR;Gwx^Udp& zD^JupeS{D{D&W;_Q`J(fy~h9PyDGZD@`rNFKmPkhU;zCU|IcaDxj&-csN()$Vck~- zJ?wwBSmqx#mWize{=aq?AjA*rM+jenq9;%OUxnNYZzu4>djG#m{$Gt)Z&N_;>%$4! zN%z9C%k$$|&i1G`mP}1jcAd}vO7Gop0=wYK6B)@R)~uq3aOd}xIr@^FVwPr|@ot?y z`^(1D=2h7|l>(QNJB8<GG!ZPTqe~K7Z)Z8%=1d;%<4OO((A~#*^jREhoE~~R^p2k? z$Tx#Pk28C2LBn0LpotvG)#Txsbdou*Y0bdd;{g1obNuB<1Eb6--M0u$6JBK%uHbH# zb3m;^oH9Yn6uKhdh?-#2(tJDR6opGO$3g+*i8la+KC%s(XvuKBUPhB{k|9t`<gwM9 zzc{H-IBpxR^4!=$^A?a~(eV9;S2jYCeOgxL{n#psgM9HDG)?OG;v&L`anN3+0>y`8 zbHUN%!P-1cF_ZF{6>|Gje&IF4HilY<{rh=$BbQz1xVsq~`t-HI^!aRIgvdYImS^X+ z3gzhl%Gv2<o8vgn4)2{)ls7Zy>va_7wQl1?iuR4xAei$L_8pM>H<Nc)!|?}q3~ZNj zq2qY#$pg}tp+Wx)dIc|A`a8r&93al=@onqqh9zawh;2Kaja#i6l&FRU@4EKSsmD3k zm$Lg{v#BH49fqpeS^u67F8paD`gaA@yQ_o_hr$nJhE{ba_vENwm-5J}KJ8kZ$N8tb zA(gTnA@>3s)M9hAj>%WG?^aFf9*sf&P#LLV^5=3Tl%nPBQ|e$Qw+A97zIxzq{&T?8 z!sP#Pz-^%c)Dsv02V@N+(hISVKUKZlETmayM2lDUPRebj=Ce+P?7{S9G6$ET24=SM zu_YW!Q!Y^(sK(R_gi7>;c`U4>dGS)OVp@Z~&Sz=v0wEU56?b2Lb<j&*sX#4M@cCby zB+J`wNRYbS2foc-3O_H&RZR+mw=g6$gGVELWqg=?L5#RRN65JA=j(j6??BHGWp-CZ zvno%XU6?Fbh!$_I<q?3c(J}ofvb7U9o34tK>9#!!MBlc?t+q8^T;8?Hp1izf&IzS2 z3mEo_YbGL=m$($#yal9;Vtqc_VX8bqy<)l53^Z@l8yd}QMK}FU`@Yzko0QP1L}C~( z))07Hrr}R%0gG`ah{*03?ZyKG+cmG^lbCGMa0V)V3(**!Ae(jSE56m1)sEI5svRNI zsR;jgX*WuNI?b3WyuG4Rk_!|fFJ~gVv?^2ej}Vok^TAXOy)66+Ftce*Y2uYd$fDK5 zC%?o9>Z8GZL!gL$k(rQA9eOhD#@U}SmTw6aZ#+x<xq==}xfjtw*Vz7|E<dPtjVtzC zM|kE2<<MUcKHwFD%C(6H3ljm#w+_g7Psp9RY?D=Zs3q-ro_Ybm`Q=n8Ya1D>@>HMK zQ|<F-J7NK^TD$J;lk1Bv6?1mv9BZ3~I?QdCXY^j8vkPX(xScIP|J@1iFtAr>nDl(N z_0cQafOqjY$)yGA@xfK?>iO<Tu)A=`w4P-~k8`K;dxI)H4of2E>7Xq8sgJ4qtALvf zjF*sFqh+ajEy6YVu>k0th@s+D<MKcC8NUF-M^2hB@nn>u2f{xXu@I_P6H9+9pW4>( zxybknRY#?wyn$Mh#d{iMaEVSKSwn#{oA|Z<d=I&2@~42}>T)npi;TeD>z$2)PSK3v z*-yH9Euu6F(87SGWR~C9(!QeXRM~ej<2)q|0J#MXhrB#Itf<OVmc;UR(Q6`)^9Na` zecNt2)S&ZC++JWCa14Fkd7_~S*({oJ<5H38#DQN<`Qc#J${J1wTz2}Je11I4z_*ck z$+0zCyY2sd`Kou1n2;qnbi7HB-El%hSdFnjExPHk9q9Cr_<Hn!S)9%+=iig$O{LQQ z<nHeAn%l2GNOkFHZET|(h7MZ2>oR_DU|<Ql&aB7(G#?{{$vh@()tYzFhq+NNvGxpN zD~nO}mRbjK%=uU6l$!z{OXb18{)|Y+CnD!g3E~cU%t-Qh*CweUGKz<J<7}E`#b<T5 zPo1FUJd{KDj4KeSKu2T;Qfb-^=98`ZQ~t~#UaoUZP2>szf6Ex1k}Din_`q0FGJ;{i zLWclLr?|iCkG{4qB`NO1rGL>NmDbsP?;1Zyg;oUm7;g;HGJ2GC!WFtz;$b|j+jZr3 zyapop@DlcR(DFibUNo@s9i5A{fyJBM-1-~ep?|(kFh&)%`%@gO9Vf+>cOj^|1L>*b zVQ7dBZOR>q``e*^iyx?v)mqc~ZJ%+-&{yZK@cyOSIRa8gbxW7C-us_3ikq3qVjS=B zxXO1$qwRVRBvA;&ax=5#w*Lt7eQ$Fy8#)2aDIiNP?V5HxJD;@?9>9y({vI3Iz#{vN z11Oc+f{gRncG11#<qEH;zo#0^S3)6*;G=1r@Zwjh+11mku|+rqsciu~sb;jc#?p+e z9&0f^A!8z(z-lKZZ@l2KM?knISN!K)a=qv{Y3^H?NRMrT)=~C$-jU4I!oMnElvixe zfT*XaZ*;2|x?Z#3SVP*xpz{TcayN;lrf=S&-T@p{;_SY#v9o9hSk}(#^W(^e@$6}s zjo{%a)D^T4+*x42g@l3S7MqX)sAhp>LO}57k1d-VLHFO$k{61@>gC{9k@vSZuvND3 zjorv56XRAud(_UPqM91QQ51+EvzXYNfXb=;^ys{VhD=7m8f_q28kn14Y|-&A2eSoe zm-0#7z2hsvn*(s1TD3`Ql@y`Pe3;2bb*bBP@=7W(y~O)dlISY+cM1buHMuQ!*wGJ& z<^nv}rke92njojNzNLc<ldT%LU?^jKj|`t6q~9Zh?)XN8(@uw;I~@YH$1+W7l{_um zE=)NCO<sSyq^^CAG>Lav0GsbCd~<#P)9bsW&O`)=7vS92Qp9t_iN7IRW#;XcDc(Rx zGKw!A6xDR#&A1J3+~f6M1BhD1_bvMsZXhrDjjZuL*EKrC_h8@FJmm1^?@a`H3#Z=p zz_;%#9P#JG?_#|36*_iWYxm6vH?}|8w@;$)5hZJ>_-TVWEmS7HfSxj-zwZxsYg|#2 zaJ=4b5Uxb3hQo_{=shMj1D`?G*682?T79ftjkONgXz~IDYn6Tv`73o!$zQ5Vl!TlR zG~R=<sn%QCpVuYl)@td@WecTouil<JHb|W*NQ*(;-)kLLfIA_+WR9&KN**$yRw?-W z2G^9*It>8hCOq;V<}Q`r!K(El3s^6@cOVU+)sPIw>?@+QpNp?34AD5SmLREFqj`HI z2m>d1<pjr5m{Txq1f@VZgvbyZgX%QkKy5olIBmb|tJU}T6%^Kna(=|#A9`9}lP;`N z`BQtbZ#5dGqfA8VlJ;yYiyWR{D4?R=u#{|z*RX6w6Pb)Wv!y|YN{?49mTUwfMSC!o zYUw~G$rKN>Ty3Jg7T^w5EGy#|8DsziZ@yeLHa%WU0mZtD4BqQFL^<im*ZXJ%5eIUP z`B105KvGFuvo2h5+hYnnK_*!bglHwFHWgAH4pO+XCW!{We@Mo;VmBNelIH+(Jj0;( z^*TG(>75iLz}Nq5Xpk)zf;+>!Vx=v3H#Qx=%goSn45m4D9Z8gEQ@z&BH{TXi)L~WC zE}&`F+xcnDC>w-pY+E}8oM*1T*j{N2d~3wW`5x}5-x*J~ud;$YTy;e`JvE(A@w52~ zg19y94+~_YmWc=`IA5@&Ch|(jMM~_-4Ak1lk#4j~nY9f7QDbYh--|T-Iz2tzyo}^I zuDT}=1D*MPF7y&?HV^jJKz6~q^H}31@b=@Y{GD8542Na&g$eHO$a4DWJ~vvvss4Dm zHo`i<h;|3E3!T!@+&pfmFq+SY2U^3PHQ^OTrFE-ky6#we7*7^P*@Z8N7gkhM!BgTo zRIV=h<R3OMk3)ab3>vRju+bnPE~r>>UCwUy{hiO&F-`%e5D@fI7hhloVUja5<5leP zn0!&S;OgjhGn`L7+i06^M;nnPjB?3hsKjCNsak~!6<Ir53i+i4j`6_s^@iO|t)s2z zc)iJSfiaQi{d1y@7rqwzjPW#43e;y>tGzFhl|ujCVP7z79a0p?OXML){w~WB@^r%J z#ObC7Ws*<nn2YYjT{!5~0zvMPcp`-H=v?FyaNGb=L(w_4=D-Dl#=4hVhtPdM`(!l( zgwr(h%>uH*b<sVChmQIKTnG!36%Kf+mf1n<VzvB<#BP9DKW3IR4J3?M%=OX_o_Ido zwc@W6>X)R5dl|AEi$1O|a*6##+CfYy6+6b7bdox+&*IgTReF4i$+$VCo3z#&o-Nbp z<&an1R#8x~rq&R0+AW}wNPi=q!ts<yqV)itJFz3U{Yap?^;i^axs58_eyTml-hit^ zq!YiW0YVx~Mc^la;RvHk+A@Aw4s~3Qkt))^(J^V7PL-@6pJ0{Se=P;WA-nC|zrLiu zoefeSwywX-GAJ{&0=J5RNgsSyWcD*5y>NZE6yBi~AXDy}airt!BeAkGyNcb~gnlO^ z!UX5TWK|%o{^Yoe169}wYf5*%=qLh#R9af0u@$FxSxh;|pU$k(x_IquG*axr4niNZ z)5^Y%3{A2mZXG3oKEScGJ(339G>L@>aYB1#eCT_mnRup6#Y*QI0t<IV6D5}I-6SSs z`OsrAcka?MDM%k-TK5N~X$$}$r5IM3Skn@9$&eqzn9VFZPA%7i^lyQ)m=chv%29Uu zU%0^XIfDKsL|T!fh2V|m-M0b`a!ffsrkyGdB-fZ>8Db`QeJD8b`Fh>#dxE+%lOpE; zS#D^0M(`2<T%CG^V^mfCO-(#d7rM-Jq{bXaQ2((-D5lkZsmm-s>2Qg~<7y3zbg*RN z>H`>ZrX+fSF?sDp*3fa+P^35EbH{6-oWt>vsW&=bJoDkU9|7m!mm@3m?q~E<`z_O4 zzi3x7C^RkG$?7xzcvSJ)%o$}+u<DWBm|QTlcJuT1Uke<BW8pN3p$xv#^ug2aQ;asV zWyPBId)N9d-NSSzM(_8EGIeBhv-k|K104=M+=P@POxKh2whc2IUiGdskUDVLicv=e ziO$@h!Oz-<4@H8s0?%>HpSZSnp)ReIxeKKUa@E|4n9&>%mlEk5K%V2S9@p9F2BWQm z5G3lb2b$3up+i}{s7CdI6=h`M#HxD7>C|qd-ygk28)LC(!Y!Jjyrjuh`NYX@|4_S% z1T;oDcbU3zZ7E*i--1Yrx_p48d(my6^PQ|OX9sUo&vl_*E|m}_4(&%Qb}YustzO%- zEoYU%!5Viho+6LBdQ$`CPi4l9bdZ#5%{^IjScP!?@Z$-UN{>|HLC&I0w(rLgjrmuq zTl)CIP^B0;^c!$#3-gW8y6U6v_T%Hxs`oBEETq0`sW{V9B^Iq9|DYDCIlBDnnyj77 zN26*=GB=!mk`Y62&!$Cp*FN@cM62B$e^}|01h}i#Q$_8PL(?+>=iMmVla$t^zjj~n z^>4kVS{;Wvo$~v$?Qj+a+`~Q|fjB^kD-$l-mHs$f^aLH5&2XqtY*LAkLOZGayAYrs z9(=js-M(?YBhk;wqXJ)Vbb{!w8Jp^T0S&Z}YC}k9PIRv61%}{MOsf$?YvAf&|9$NS z$1i6tz0E5;54~^~{@6u;g_LTF4LOx!+JXr&uNA3|)5nZz^Wh1-E8g#qp6}VjctKPB ztF{G&jzo+1$NdA-4VoR_uJ?v57~V`S&w3MvrrZTcx@iwkti&&Q7hxm}Lfhl-i+7WS z{Z3c2{8Zx=hOfdZqw~Tx6tHe4bj!P)y+Bb7{F6w1qQ&&DZL}!fX6#D*iQxrpwHZU5 zjm;M#hD4D0DXu&_jo`gO48^TCwrnuMT4<?dKQfeS4*TM+s%Ivts(aBqK#umhZ0lpL zM8fArX5q%{j_wU+IJ4-qM<1$0%a*ju!mXmE5YBm)pW>9^WtR?@wK3)3pE=I^`KWm2 z_yS0{{1Y3#U7z~94#LZPCJYw*C7tn#L^yY`|7I^d(C*%ma##rH&!c2*J}}2I{%{8$ zPORlpb@E~Oun?bIPAPuTpCEg>@yxi#T+-r-F~-``%hNaTWvC~mk2{TlU@z8(81;e_ zD8vnkm6$>0Uk%Y{<?}vEmTm=39eS;j7|)d=!da<ZOV6{bT0^{c6UE`0648U)PCJ3g zn=OgaVY$%|WsvoJpq3I!F;I+aSx?aPkmJmps(fXVe!^<%Na(CqMRvkB`7Mvx&S`QH zIKIl@VlJ0D$I_IBIpL7UEd^tV<-|d^-D3VY?%#56Jhr1(ZeVn(B@+afG#Fe0Moi~L z`zmZ)9L6o8laex=uD+dtB_dzKLq3nGY}CmcA2&UsPch|{k^M%mS+Z}51Prf7H)K(e zSt}aUtS89!UXD_S9Fz|5x@!NtVWS5z6vDR5HC8!hgdE3ZHmN@oDlHLhfP5dlu)j7E z&0$ef_r08kYo!Fr=t(Fn0DR+a=e&eYOzCT?id00$PBYuIKgUquowy+i0$pTFQmy6W z*B~8pA+LZ3G8%_~7n?dRgR$}b<sxEGG76@t=o*jszCN~b-dznT#k4SS+vDmLs<li@ zKs2ia2I0^4{A`})vz6pD`zqcU^5t_tM+T5wMa_5a+-a<+>mlJ)X`T2T(Ae4wIc@CD zjn$^Q7*rx@=z;nb_J_btD^i}|=~yUXz*~N`pZ|cULLE}TLXSvHj9YJ;f~x|v#ipK? zSG?n%=r?>;+v(KV!dK<{e$R9pEXD5Ms^d?#g(0OKvN~1vqo5++y3YDH-PHyQy_ZqX z_p4Wg_qzXJ9`=CQD59K*)!KHe^_(V-p@~q)cMudJvfo|K+Z~RkJrT1wf43oUkG#BM za~w7wQ`cJ@O?`zdhKr0vH=<&x&@gz|^7;J9%Rp#23xDC^5L@;d&cLd*h~1^A-9!5H zXwv62!d_-_r&6qe;<V^3MF?K8Sv(A`?NRdge)Zo_LA3>5ru$kb0dpA80ukxxa+ph= zYXoi2W9rI4ru1=vDK)SE<u88Q=xp6O`N}j>B$)@KwRCUG-wJ1>(C4`pkbVhP@VBuU z*gA~ttQY)o$oRHdfl#m1c=uA&^Oo|y!Mh=1#h6J!5`36)#v|rC1Om!QPfD%UXl`as zUeuJov=tf$&@(EX%*G=yQ6%6n^*U^h5bhr2KpPWV1pYcWbykt&)ka)<F2!dJ2e93W zmFq|+bg!aibOI+$h7>yurwEc{A*MTUhL1P)oK`}lrPrq5gDa)Yvwu*61;w^X@GBRa zYLluM#_1vf2DEv+zHf%`DY5Hn!$zu=ks>QxZSNr=YGWUjtJ@-=H70{@MdJid>|r*5 zsDJ-ax0~V*=+@i31ysI|PkJaJ?~}QxkV^Mwp0E>nTh%`+FMI+~uiM*VWVc@2<7~XU ztiU%Z)%*+N4Uh8MKBA>s0v9r;^5Gv%Ep%z9!C^;iMQ&Mu$`}`}u1i1tQ&+yR30JIz zlJkAzrZ=xpvT|KYo*HOPedAA0h^n}S7b?+fQ$Hq=Nun=BO(Ar6M|X0d!bS78wI1$^ zw%T+KU~PdPHC5wn8sJM-FVI6ydV61jLoUFD=HtW#22ODSTZm)5<IX0n#x<y3`6nx+ z+?{47=6u;!rOf8C-?o_W1(U#M`T**tB%c<G!8PjE$qK|17t3L0x0TFb;HLRZ%)&@9 za3lLG3gWd0;#vIB>ekSAIa0>_8{<`s=aG%?wj0_Q^mlT#`(&9(&n`v8j%PIxZKRKa z?L<tmG4Gqf&q0TE_q!)4zU0}lqr0Zba%?jO_q&56=|XqVgls&jl#`4-w_Xl({tfRi z$Om?Cx$?5H%ldb^lz`9uC(Rv1@PHTUb`Co;&Mis@rYJEZ>bLHk((Zeg4(|rlmTT2! ztz^hGW~E^@W^nnF+0BoW#iVa<p^-z9lwR=YYV}pD6q|T0ODm8)@Lt|mNpV6RFN%Nd ztpO3(``5#n<?_~b`{)SfsMD|q0n}zj4U&_O4se%B)wY)rG7s?>G4pUnD{QgFX>2tV z<TwZjT*|?=4NHEj?Rq=i6bFscEvv`(E;I&$T<gCe5>n$jrv+h0e0jjPex)oa-<Jhl z*KL|mXXT*(EXtdU+8-m!gC5?3CutU{-I$_Ck0cF5N&e|XFCJmqvr26ixVmz>>F6lQ zbwlLK9YF|fyk4*Bus#?cw{)<plMXmRP@1F8YLQ6sIMJI3+9CpatGB6(8lr%(BwJY; zG*Mp36)Oo_i}j}xjDRTm42FsvB{Mhff*UJ4aC~t9$jFFjHdLF9=iF<b*uVIH8!U`Z z6@a;u8R!yHHc(jqaa#QN1Tr$>8=H;iar*l_lkLDZBgD0;O&sl(SKM|h`Q0wQsZt52 z)*BGgQsfcCPg8a_soJV$vAtnP;$sbNhuS@|U0h;sYHpyX=Qi1@4QmOY1Mlc5?1^No zbJ{+c{QmwhKM<4R3kw^hwcOq-<P$Xvd>y6*3TLrs>~6&3f94eyaA*V2vOBUPX_qND zOk%N}K-(cC%~-F>1gJXgah*LiVz|<$iyKm~-<2z>^pg-PNVuSCW?EHlWlb}!anT4+ zP(%22w6Aio{b630D0j9EiWt{7E>v;0dfYN^=D{C^JCXEI>It(TvGv7mTV#X<^SBVc zRc%H^ey~C`jx}ZE-JeVprd}QqfQPACE7(+D-~3cn`9$J%LoBQ%jB`EWeU{E<pF@HA zmp(d+^aXggH4UU3gUV96>PH!JHJyW6x3m7@M%1*&u(#1LQcI<$y|@drIr*g^G9vnv zvfh_Hx_bXM?skxhr`#nzd)&+j%Mhm@>f|#1voYo-+{+L6xHm;H)?N&S_)~yQG*V)z z+(6l5PNNg6n|YZZX&C>PYCVeFk$5r6k>3SF#x2fwE0;tas8|4nZb72o9ZDNp0T{v) ziX?5c0+S>d$0(JqlKZa>d}Co?(&T3^)l}}UZz#)I{dK33dROgb9LmZ`mf&8L+H7N| zqx#KVt1Av$)grV=tKL7vfS%ihfCY!77@H#Lm)ZTdhK8E~e;xW1?t|(my_gjl4rN_g zvGjS@yl`aTlt_(h=(0l!^r-AbC4hh&Z&B7%ag#Eik`@OODMa8Uu<dyxsqmS08Ut^C zaZamLRzce)+y(Hlo>I7(fN=>YM=44s2ho5@02xY{i@LYkQ4rkXAslrev1HRGpP0dl zM>a79a751?H`>N`QI%8NQBsr8R2{1H%}g9rizFy{Wl-LZ)FGrN3$bx~;)qU{2G^Jh zrZV#2-sD^#p2CFN@<IoAHtq>>Jl65a^pTGB`&RIf&<Agh<9DBNCgei8DPe(lYPxj9 zZ=<LW8bd^c5ejI!vVqZeWT9@MHkq^(nrB+z0mF!vt)1k}?#ad>f3~(PHm|Llt7o`f z^q7MI@5VquLm|IJH%3@NVO);Fzn~E5nR7^b%;@3)B0l8<_foXiV-OpfR9-bf=M}mg zMuIrjI{eA{&5r^sI=wD*Z$+p<Z4uGhkuQk^81b0tDQ{A+%VAQa5qXObr7VA!!q(kO zK4<e5pE$VWM8p;o5Xcgi5xW}P8GAToOn(irgmc!RVb!Vjs6n_Ozo>_{++fN=slXsk zb5Dws^s2sKfHN#XElY%!N7Cd-30xIoj?SZuq)<1$VXxS4$SsqJM5|DGC2zX09|ZWC zr{3SDmU5kT_alko?KS1!CLi;yahpQ;GI45PfN49PHqAMdB<cKd+BLS<8x~pQ%H-PR zEeVQC%2Z?(O>7A%7>_ok7e%Q5K+CA!RL`$qI%nJmfWax^HLa1!2pY`eF{+<CVVIv0 zOV-e+rr0cQvHpgwLQffyb&5CxpT=eoB7l+~%I|z+Yf8*7gTV4EmsdiZNcy{Hus)d@ zxwKxsl^&UT3vGaDQEWVGDRS7>=!J#?^TV;)u~#gZ(an2ehgQ`@PbcLsH1lxOy<8Z0 zM9Enw*$;4<e)b&A^bJ-|A6v~2K_I4;y#4U&1iLDt?}x4^=?GaKJpu~7786MhU3{NU z&)P|~LM-QtBB$|<nK9CY-%vFWBApDC6NXwln4L-E(wse>P@6o+n#{%R-|>Fvoi~+q zy%}opLDoL`Pblah?bR~-J?8&PH3jjijWU%Q6*j8P{3<V2OH}h_Jqx4#kMEi7zrDiB zMW4EC&a0~}1mq=XY@`e$s;ndDC_7dKA+E-j37_WT0^zaiCMN#rZPnli0qvB<q-l^S zzKl_I_amvts{)Y~!Ji7lgC@*+^y(plCK_{Xes`YJIi4reykF?~yBv6_p4S{R?$eIl zf45KEuiH*|*vM=(b>^BY@s$g0=5yxIq<$q(Muss++cx&ESiFc7KonSLl@$a&sm304 znIn;&Trr_PBMX<45@kTJ(@8F<t|jqI1K`(Q%A{w~liAN_LXJ&=+l2eB?*dT`Z?24D z3>_U>PDQyq4q=PO2M=AQM*L-)!NB}W8Q+6{lg1S?^KG@ix+gaG5H&K@^k?_w{4d-x zXUCrJOGx7P!BSUgI>|pha8OSgVwy~d|4eGyT`}^O-s|wevc=%>n#!ww_o!L=*wjTA z@0w*iP>PlQv99sSALtXK;yZZeeHPPSKv0o~Zwae%;2a9G4i^s1(z&8CPsPd38l^QC z7Cw6jG`BZkjDrbFSo*Nyk(u7yDN9)@P(v|8#E2fHLVmAW;}dh<b4Os$0pjisBCOsJ zK!|pl{|!bXfV_m`may1$Ow&FkTpE+k3DpPG?JRtG>EDTHWvVP&O^V8VU4#vF)>=n+ z#$ZK?c%Sq1X26!T-hqJ<37PV&#=09~=04GW7Dk>>JA^hs)fivlg5NyKi6z#vOynMS zCRQ+lW|X`7%ewQ<5~+CZf|rHx{X(4(Q_(PPTP&&hz8ic~BBdEIvWCA!QWJnxX<nrR z&Jk&O-m}R#*~D@AaYz_MIHDwGB`$+uGV$}#5!i#J|4%_Ya5h1m&n}ybX%)Jb6uA_Q zcW96*5+q=Pm{__-AUv~lsrVB@={x~2HBk^I;w92e?n{cm3&9^r5%Bot{a-iiN*LAx ztW1vP$liWq8ZrVuXD+5W$ucn!I9k5md8wwc48in+v4DH)>D3i19ZDdJ5!Q1|R{P%( zfX1MLvHnz5t!~Om39vv(8IHARXkcjt96~kKMrEZdIRF~aEQ|^jlB)C*V}Z4}aKoIA zh>~ZN1uX8bDEtO~@W8gDgW%_cAd2b*yE0+Z67b&>%MaxbXZ7M2Ata9NOR=|t?;I<* zc!9Y9VRe%1)|=n;XeI`~%QiG6J?eLL^vVK)*LJ3hlT<uAiog_TrD~yN*>}|x!pC{v zGI+O4#tSx^NuguIddRA>UtJGn^97-cT<`YTQ1=nO%O2`h4<VSfh4=g83^$W1LK_u| zR9w>I-g|9_?Rz84Am^dCOC(Txf`Nz49TT`ThnJwT$nb}MZw0<?fk23ph`LB4EQIV~ z8FKfrGoQ)4axeNPx>PK6b8=8gmWf}mq|G}G<Pi_TiaKbWxPQR5@(ep_hyvMMstR^Y zl~2wd5rXo>j9e<OJsGq&gwo)hSRi~&W|ACszc?+2(r3^Q96m!{fQUXKGAQ!>w!@fY zCK*b1E1-9>kV@Ky;&m`GR3rN}Z=(be5Q%M0q>utyEA#-U{}o>^z%riFI(t(B|94IV zNK!V7-ozk&px!YFo*lyQRP!g2KLCBDP8Y9V3zAJgUPlPUz(gnXiF>xm>~BKaL%M^K zhf{lAS_Ivc-M!>>U`f)$7GUzwir>85SJ)9-3ih3_-HVmrIAxF_aHg6xrvQq6i1)F( z6Zy_66)>p9<AMUBltZ?11EG~zG^iC*+mjR_(Lp!4oEQSIHm5NK+u}*DxqW$<C8T(W zU>tY%a8ff5DaN<Ek+8AIeSc;<%N$=ejR|7dACf5*w`5(?dIZYiy)cyoWZSflUo$f8 za%JU#B-r~*g{4ia<lu9$3+`Lcqd6DhN{^;B3hpYi0#hoL$suF9wgD#?KR%L}3E__T zSvy@+ZZo&El-@>sW0F_+sSrfsCKYS_>DgBfzzqZ%qY6a0X2wt`pTI#z1=}(Ie^~&R z8q>K;sIJh8xg4v}b0I1FvKTZfTyzPc<Ue-^*9<g!n+TJG1wPA~X608X6kfh%!JH8+ zcu9ZBR8B}_H{yogJq@r}vj*F+a;St5D#b`I=9dcY7vdMqm^r^M9~(a~o^NYOL;uU6 zKh_c^U`QaLuLl!a(9AZFH}pU?(3mX`zdpM8xvwWb{1~jmI`zIq!G?MUz7+tWw9d=d zQ?A!l7yDQ=5{G-x_LoeF3ylZynfJA?E_bU-K-KU%qn_W#G6IpWE0`HdDVlu1?;WGM zk3%v`$4v$)3Y|h!L*Xsi-{c!{1<JVuRV=5k89P}L@|qsjAk22c8Pv}EC^RmMWt0Vp zHYNe9z<sdoSRE#Ex~QH^ba2oZWr#tdkfLhsp!a;5at%UQ2Rw2#xw9cl$Y)8Z(0%aW zg1?Gnto<F4v)f=N$<+iKq)|eO2l^h>rq}ppsFVzq^7Ee$k6AyiHN<BpSwtq(PeT*7 z^3gFuqH(0@*Pz2LB?#h^{lZYa;OM(YBO#~weXs)NWI3Q~^Z-ccSn7X(Y@?v&vK3qM zkua%|HF;G`%J5RbeVsI~Q0_|CiO(PXZ!E;8>;g8t=^P0azQ-Ph=lMTBF{z85!+$H7 zV+L09et&cn<E5wm-IN^p<Lt;y#Mf!M&m7US==t@K;k=QJ%x$vImY~Z>X#VAaOO#NB zhp$H~hTY>wSGAFsT*-3-c3>cQ8|-KIa8!|<v@;40PIt65ie6jC^rL-NC;;PA&Z`w6 z($v^M0h8-9@1UISG~t+5UWeIt%@Vu5mUp9otnqLuCXaIqsRrmy;&7fP{-fop;egHf z!hBE9M~U7q1Sv^}V8Zz_?rIe0QEj+8^&vf#Jb|(RCq29UVO>%}#x-F&92{H>*S9!0 zI-pNk?2`W{ywlnLm+$$kC>Co^jPdK?6itUr7(;M1)Lkk(8!jNw5?6Y<Lh6NA;7@Vj z^(!dXZWc@mZ;|&Ufc4<V96-<4Q;}rz!37LMh&O}26Ki|Ja^*G-fZ8NAYFy_8o#tQa z`~gO209)|Z8qaPo5BEpOXRvQo3_B**Gli|NnL}ACADEu(zGZIDftaL!fuE`%0LR?A zC~6prA;0+j+-EMO^uRw5fj7HMZ{rBcLBEDL2C<S_zv6MlImQ}v|APr<1Ho&Pu(-iV z4gV|1ban<oZ`+ZVx7W(;e*-lCC+U>?&##AUPk5=}@c#-Q%^~XTJn`}ITf6=5=u`Ng ze*MKyjYRps(WiX8qCdU-{P@+d_g{IXkN7_$f6Y&iLG^#hqrXq|Pvh^e044+f9hS-u z{4>7-^%zzDtGl1r{~CV)nhY8Fzew`u*6DBB!1|0T|Es%i9RC_W0ZWC6A=nM+y~t>8 zY;0^rsn`Dh>r4B;LFfQSCNcznL;~iaVj`x~aXEf}^I^ngh5p~qJ<<XKt#532wumlc z3GRXl@OgOud$J>aw3Gx`V>fWp&HCdzIsgiD40qb*EN-{Eg>5qygt(V}twnVfGkwE# zzRQ~&qgcupWrroYo_0gaYD-hJOWotze>gYll+R$79RlS>s;V}#$~{NQZEh;3l2&|6 zG?OdgcG~pgd!S-ewBIJV&sEuYIr@uMFQSF?s*^edk<kknT!xl0Y{=b>2~l;_sp{N$ z#~0PQd$`H<{4DnewSOzD239?${Nj^%LqNA8{~6ETr-0tx3t@Otu%8Dw+X3F#)a1PR zBS|dpVOvx{KWRS^YO*aaVz6ENCNSY^j?kb^ey@DYFQ44lJ2!cY&3=5=wQPS1DWv(D zKF&|g1c^&{Fm5>X4YRO#+F5<x>1IAvtHe0afpmk2m^t|tO(r$n6QWZY{7)LLKkmOt z!VPOK%VfofCF6hqSLTsUcd(L!Fl!#+1p`M~a`Rz*J1_jOw}J*kY0KU8K<|Lv^L8uu zKvbPjpTcoX8vVY6zmldA{h%DZ3bX&aOHt{X2qxry#MPoCkCt|WN238>_OH#6V#P?S z>si5T67oTqO-xA79r>NkF5xX4R|*~Z;@Qk^B$pCb3zczreDK?0?<1c*`SM&^0D~Gv z^o>OX3-^>#&*)LDV%NxM1y||O?2G3Vdy57v9+q;vsMx<j8HQDi8tFJbU*mE5kZW`a z6QUQ_Sr9y_wfozGxnwdG+X>IZ=VLmCix-WK^N#FKA#V!GqN-L6TRd{KH-Gj$f~%)G zuiGjm^$!vSKQ5zCd5BH+l$ts!t&=lVm)AllZ(`x%#@Mr4*wiLne>Xcl3qra|H1B`_ z<A%A>yI(iJMDaRFd45&Q^c)13ejmBIZ-mD%%gjUf9j07BtyHeQCc$*>otn$b_^zsQ zoE1DNjW5(F3-Ek$;Gi`he-;(^7%y}+?N=!@KK~Tp$*WU@6?_H8dc1d0?1rU|x$B;Z zZ@t<+Qk=b-&dS_gE~;juJ94qVjme(OjL#5vKaIhqSX+s2VzUKUR?$t8&N~bcmC$&9 z*$+u;O{^JOs@8Bjp*G<?oHo$S%mepl{c>eGslD`a9g@*Txr~fbZ)CpKIfRo`D57OM z+G13KN6vowd&mF~&)IS*-C~K`)^eMHN82i`xlzvA-EP2p?eS7DQ~eSVvI*7uM@Mqz z8<qjW&0sr!EzUKsGB#Z{g+?R3;$`CeQ4|RF%FQwO8GR8Nx7?lOXpP}87d+8wBL%)z z69(+5%`(AR#HJ}v2W2tu&E{ZgWa0_L-TeRI>mIu!3)i+$$F^<Tww;cxj%~AI8y(y1 z*tYGYW82QoTF=<ec=!8Z|9~2^W>w9*CXVyGz`Y(T=9Qbm5<6u`?VEAnLp$A%(C~NE z)0Qj6iEHhn1~@Y&CLTPU7anv~Eq#OAqwHd9h1u1~;@*<DaDlld)!jYn+P&%crop<0 z<5g6r!Kntmk<gKcCw1L{FUj8+a@4!X;LnF6gfcy+SEbGv;qIMNy^pLKg^iCLbQibJ z@b|sEW%^Yzu2QK8GTx3!A)Cf%$26VkIPtfc#1F^Gi9Y7SgL17fRWizwSB(3^+;%!! zo6WpypgI#jMACO>ho{;I?ff^IB-&TrUawfLt_CRyx>}qv{f_%oa2lUl)LMLQr|m(^ zIy+r1{Mqc}X809#UT83}5ZcdQ)uQfFs}6It(B2J1LlLdEa=-R;dd<fw5x4I|^@lTS zCvBz{1kUZqKeCeg%%>NX_01olPhSTJ<#ka#SJLM{VkR(O>^+X-L=Cfp<WHilU&qWs zn&$D`El8zsbDHe5cN9l@cl{0pCDHh}A1)7?!a@adniN=HE(BHTo&U8TZx`?H>Vjyz zu1?z%&S<k&ueEv)JvG&Cwey@S9do;txY&+&j1(Nx$FwIc9M?#Bx?LV+M_q~8wXl6U zwATp4TXX;OIJ?X;c9&qpVTWQug<@DK73|on%)2S3&1r9fcEx{B>bW{}K!~%rSuI_b z!!_n_^tGL?U+%4**(aw7`h;l8`XY1tY0HvKN)y1b`Sclpxr|sVuO#f8YQC?fRaUnD zc9<aJ#56dGwykvso6Kd%;nN5!jz%Edj^G?#Nsi71k|7`usS<S-3imo$=`?pFLr+YK znC^eHb9_lm8=m17B02%nq*UmJSh5JmV^4qINW~{EX>B_X=$16A&;E4EI!-s_j!$YL zz#Vi^;CG)GNoV$*sgsplRlMH=pHm!JaSvnv)l^u{!^q*nYyo_XmYa5alZMx-`VY#) z_lg9Uitt$z?n9gkl{Ae=NBLpX!}QmZNX%dgiUmdRkT}Ec+mU==8mN0p<c@1E=GUFv z$mEKhM}slQv|H5;8)o*K_n%~65*{Mc_v2+sNvaG*-y8Vn5%Nmok^c3=I{0%23C7LX zmlqoMC+3ss4u8(gIO}d%_Oq+Zwxfyy`+(Q8RpwrEu{R!uw-8R(e{z|Z^<-X1$E(?$ zMJS$OX*U0|n*fl|DnH1|%~|)Qz`OuW_q@zJhyUTUp5(J!P{I9)Y^sT9W+}JBayI)} z4Bet|wjU+d+s}NC&LP#`&h}MtYA|ohySw~2{uyrKXP`o-6^^!rqq~#(!enxP{gEVP zAq15k-G*Z{?t6dLDAP@-d1ri?&Yzy|195ji8!0txTs*`L?^APMuj+$credn^5WI{* zCLhLBy<pR8@!OjHfd)qy;_^Uv0Zdw66LX<RDowH}eG4%`-TTrNry!4y$z)@(mSQAq z_h%pR_Y}U7w79sm2UmD<u|Rl$m6hLPc;_s1l<<?nmeE@HpOUlq)>%P-EIUupb7QBN zRMQ>DS%m&Gh9pKqgLG32Rx<NVYdP)MNZti^AqDPIWhEx@ca!BjNlsGV{Y;KLO8jX$ z5g|ITBIaTbA%8Gy?FJuZ<67<4<4Mj{vpJ+Asgv>c{iDy~=fTUE-NRsPs_kf~<YDc2 z-T0Cce$DT$oDt~yS8$%=*ZQAcL+a|-XUz@HyXQH)QrMHlMcrf3uJOI<Z|`CJNMfW# z&%P+<n%2r|gtNZ;g=%TR*Nwg(;OeNWwRSqFNxtWPP7kv3jR)|Nms%BlP*$)ywWnn& zf?oLP;$Q80bQ_nXLTYxmB%yq1S@s92Mcn4E-eKHr#hXqdb<T~&wBRz==~F^)-V9>J z;BWP^m2tvG8hDG>NRz`wo%Jen_=M@?3vxi}kI+2`Oa_ps+;k*lp2-F6R=uB1Q5y5K zo&#y<X_ienE|2f6O=zHPH@QAZ0mcjsxfTt$yzTK}($1+>A<2cOll&Jw;Aw2c^ZeYg zd~SvJM`DG@nNDAJR9F#e{jFd3S`0@RFJ8(0Bf5{>bO-$2$UyvhPGbo#2!+hhB%ZE} zIfbQ~L|XaYf`@LC_N}dyrG!>|@M?qV3F&3_TTjxFAGjVnkL6}`fKA#fJuLJsPx<N_ zEPf`m+2~FupM@a09^0HIVTuu=wp*L46XL3VqiQg<=}S_mh<%6XTTZ`u`Ocnn&cEUF zw+U5FWfVho6pm>4BIh%1a6R_JqsAqX<BcUGp^wOJT|+_;<BiI7I2X|!NA?B)_mJ#n z`2(J97Ou<ot~`C_3f-Hyv?0FB)xIUZrWF+j1%f{2&E{agZt3U*2w=rq2W8tT4QE@^ z<<i>ccx}`*WI9*IUt>J?l;9h2Sj*h5nlIbi%TD5j=@A?b3*D?xZ+*!<(yQrva0Wkh zha8((V>bWfmz}2D+iv>jPwwPDGi?1DY|`g5=1yoa5gpedb!pJ<b&d7&e7xW9^X&SA z`Mr4@jx;}|tMUoXw;%QN0orPI6PjIK9I;Q5c6Kz!y_0$r74&Jex5+NepfbeYv(NQM zHI<AF)lRgJvrcE*sv)TzgDanWSD>m?v1ya#VOj^d%-_fIG&@YAvx(_2)?K|2`&_v1 zFVpQzQ;wic<-ezOozsmGi==E$#KaoSW<t^(2X1C>+dPhklh7AfeR_pj==thc9v_?Y z{;`cbIPe`lgMBhwj3!f%H0SyHoBVC<3%?l2P4o61GXvSbIV>9+Ts1K*oZ8ioZUlE^ zb11YGkEtFVTiW(b9Jkvv+}@}T_gd%c<x6Yjw2b|O+mEn{>scdv?8*yN)w>>*Z4fE< zQc@a$W}6k;R`yRE48K#LV>G7m%xjr-!~vJX2Xl^08i&V^`D(avM1kEG+iwMX#30+n z+xic>Sak1+-|O$FWLTW0w2`|kFADA+cB7*&;;%Xs$TG&8Ct`)PQmf9f$};cv``k14 zgH6Hqjhak^UYg`V1>{#<PzCd%mgo+9^rgC+d>TH%>UKK2K2J{dx63L`W%gXfxZg`J z3yKP3FS&kB#tTDwnzM9%(BQwVU*69D=*VsnX9r~>e<z!L1o=lVS`b8JuwT}sDqp$> zU1xA5FBI1@vaaZySE$oX7W;hoCVRUye3O1+Z-`HS*HB(T2+#)1yf&!W({If7Z@rHw z5lokXGU^|-^8Vb}7moF&eK+Q6xbkC)+<%~T!~45#^ADdP*3aum2Yp8ou{~v9;05c? zhJs(W@6QmRnRz|B=(sq2WuM_b@NEnvC`@E+$rwAJX<nP-@6Q1Z!tU1uQ2O{@=<&G8 zsS-S8muB8vILwN?ucN<;+e0Ze)O@<$+fM`eh#Gw9T-Qi0=c#y)rHU(l<~97UQ-3?z zEmC=IQiTteLGHdt2g012lMgo(Io79rRGMAXu-z5ms>HD$XZP`*#|mss&20p;KKT5f z5?AiJMbnTz-oKB-DO0Qb;wu$%+jb{SuMRpGB}V9YRaZVT12*hG5wW5KaVBGuUUGL> zEI<0*ivgEV%a%21g1rlXv4E8cA8N?lR+P^*i)SmMa4acLuW(|PBDU^Dgjv@-HZg`o zIMdaGm_kE#p7@cma?t%lvI1_*DAGlkCyn@`vAsqap@m^j<N?&OtMI;vl7(^saLZ0R z-XeG0<e+F#Ck?v#L5WQ(+vHY;-}ob$m374@HbmLE(7g@iBHjU1Z03-3?)*fv=iAP> z=KN|2Ep!YQfZP096LOkBQOS^y7zB8AF{>E$iM|3UZ4`_OsBzwty(-O^x5m-Do!QyC zIU}Qroz^CZwDaP*Kj^q<6(DwH98^?F?r6#7_^)eu*TD+MRl1mzF345k3u@3S+&nl) zEEJ3^;VjVb(Aq6C^D#k-7Qv1HAE^MPeBp$1x<DTXWVMU2C|CqsK@K)sEnOxQ7B<H; zY-algt29c8kRA2H0TI!_px+HNXCbMr-rdt>zM^@;Sqsrjs1hjs3!065%q|xmTkd5k zk7XPzZ1qq}7%_1&3OcE&e6&hp^PP;8QpnL{!QCQ3`>EE|%7;suz*xU;k%S!_kVt@P z<cF(9XAic*PqQFNkY~UnEH~gR2$c&Rx9HgH#yQBS?6eZN+(hP!|4AUIrwrMA@`Na# zlZY-%GDr!5|D-{@enpL-LmEeH#2Pjd(2Z;7r0~%)P*G7`io((1O-nS`>8Rh7z5iVH zPiK(hE$`$t>PdLV5OXV2LBq?Sjfiv{!EYpnh+<>l;Gwk<(OL`Nd?KEBxOCRfr;>{* z_Vq=W3Dr(%H}cWK%IqvgBtvbQE!oR<T*l@P4ER0sK3<1z-#!3vjF_gDNpm|{`Ni;W zdq>r1%@A?f&$hq2-51MdNA%yzZa+@(WeGH52;{*}k4hGhruBB8xAIwH7c8zIzN1Ta zzhUi5mr~PPS0^>Tu~eJfPWRSeHQ3@1A+K$j?sn{+lo%Q%H!=yckf(iB_@38>Ql>Zs z|N1<ly61umK#$S7$Um3#q+i@Z<hZa~I5?f{Z?0_-`Z>=Tuv^~CDy6K$H{|*<b9+Dk zIXO5sTDr?ap7|Dc?q>79YGCZHuYT(57|R6V6iiR$`gpi%W6h27{TbZY6Qg~v%f(6N zW9hP*pM?(1idp2p9Y|sKdBZeEb%pbOy%j!yo4bjf-iqN*e8}n0Y_!3BHdY)(fZxvQ zt<~QEt8OFsI5?q&xJNl)VZsKSYfLShO3tYFB=fc&H_Mw)nG1zI2rrStFJd_xj()C> z<@}bUq3_{n=XI6towJ@*0VoN&_Vc-)h|>|A%6`8H)YzUK2v3de!7uA`CydtThSSgJ zIQhBsO-qB%P5c<iH`k!^xg8tC<Rl=-Xtc^WI0O=o{A&F?3L6&PU^P1Z_}qQ|=YCzb zvm^ffk>)_%H`q7E&l(1Y?KpGdJ-q4syw%fX8>?P*-fOa&;3Nf0!2b1hTrV~Ntl_mx zP{H2`V!7QAdp4t#Z%Bk9=ztQmCl9s(n8a4kiOg#R(SQZN4og7?Z0E7%3-=*<R8{xO zfVLB{8RkC}%->MNcgb8|sG`F(53EwnHm$i@XRDKHBCD#BgOJXK>--y*CCkS~j1nCu zmJ2RZ8W2j%8?9>660UepP_tsI(n(EeCSsOCM&rXL%=Xd_R5feA_W)4enu=}>3B9|0 z;*KdSK#yF37IL!uo#gCl5e9BPA%~5v<h7YiJ9YJj8$}bb;y9j+EK+wuYh`d;FAOZ~ z-<XngvHaV&PugF<Tmp>&#tmxbB6%VAvq=3(6*QGn%^$j2z2eSn*mMO19GshVC9oe_ zgOF1b8>{RNt4a$Sib&Ww`ZWs`Q42~IS8S#SkCxc6WH+R{tQATBT@$WkRHfJ{u*rs& zkz1D&3Gi1zY;(_`J!NakPr0c>pOHNHi#uV9i6gT-xYrC#p)LD<+w@lr1NH6GOlKeh zW&NkNu%v5=3=T30O7QP%iCb6%oPLth@1R(l6pwfSz`A=7pF7eVW)YBa@zF0&9qeeB zXO;x7B6Um02W-J5qx_qF`isqk($$_mUgHQg9%Z}|+4N8$dVW!)f82;d6l!u1Yn=vG z`rjIQv$kG|L_eDw%@vWU;EjuF8d805%;AY47Y+<_Gyou-f>{@?_s)D74Zn!CFyJZN z3#@7FgbRj%z#6U!#nwFjnW;Rg34#hFp_|wd8Yn4)M_KlP)|Z|h@|p?u;((oeF)5Ey zH_m~Cg~ST{v?x<q*tMjjqa;^uhQX_i`*dtCllKmtLIj`8^}2&Xhm>#><L~pnk2tcd zFvKtL*f%thwt=S>_bVL)G$;@x$gIl$eXE%HH?m2|@d2j#N)GbyR+sHodczE54~ygY z!xj5E0{g%D{uWQmoytvEY6g%uSWIo7B{$XW<-KA?-IFyKrFPdqna#*xx12!{_;t70 zUoy)^Irf%MEEct-cjKj$CO6|RZ*;-10A1W#yK!&tLq4x;Fk1^7OG`^z+a}+b+G=c5 z*QvDE``m5_j=dmR!9o|CFvX;~zfM%#=lIa;R%Ujfx{sK0dHe|oBjxIn)vG1SMV9!> zjO8pDT67gDjGQQsh16d-m28=osA|{H6a_C}pFQP^CcGNy^2x)h!nDm9P>NN@O0@{W z;z<g)-4s?d`6%v|{p$iow8|3*tn>4~Md|AAOG)=b)|D3|f*<O*#89kw6WvH+R_!Y3 z74m6NT)k>g;25?j2LLM|%`ocvScW6NxrU0lg=H1DRHGzTe?`X_+xU@}b1H=i3n?z1 zZJ+Q%nvT2wML(#T2>A<@1Mvb~rekeNL7+(tvZW*iDXlm^S&fp!Pna&7$Rmh~lwIgU z+T_^FkcOu^9$;Go3&!`O@Ro*tICXbzNjw#D>oN^T!UFvUk%vqmr6i+=Bsq5)Yedw3 zzN8%Qm}jb3wjO|piK(d*MDH<Cp_~JimtLaAq+?%<At66&Q9~sKkRn|A2P#cof}5E9 z3e$1_*BCge8u~qKpO$+TtFPrt{jP}jD~?l>DeQoqJrfa?a+4S}>^AF(bzelO4Dqj< zc`{Mj7G-W*h`_qab9;J9P`UZt?SNgavlo(h9o!TA2qv(dkXn8CE^%%R1rt}bKKO@G zfQ~e;q&w_lqT#bP-4$0*Y#xN?Fq@X8yVTKZa@z?%Db6kuudtoHd(f8IwxU8NnQ(FQ zMmHJxU+a?ul;TIWU}aM6HuM%$G~en}ESA0t0G}O4=q#k!cR0MKJaY-<HtQtfM&&XR zA`kcX<PZkr3@_gDRMEliN~JC4*XOmr!QW@N`WwC;FJA<a2=Y)NLP;R+8RmCu1~yJk zgHDgRmoMca)WE%-H_p2pMy!A09RTcq-0KXYR&)y%F!9emr<x-ag`kJ=MdAc?<|JPh zd3nn=#p!=_{?<~QNR%^Jm12-FxiZeT#G)?tY^n;wBkf&_S|Y(zn1E%ITJco?#AD<) zS!R!`o5X01)Q+fzr#VmY+i4W%4!k(cvauLBudy{FhLg3kmbDpSzCqa63cB)CQ1%=G z5dW?>7NIgD=_W4zlDSMNE5>zErA$(4_ngUIfVGQYqGF>@jAD-7dMh5-t;-e-E9MLO z)3>M%#)V=b*al8gXwh_8dT&>r(m$BHLtBb+TtsUQQdgbadL1Yj&~AMCCcJ~w$zFy- z5NN)&v@fv$8Rw+tfktO`(2B%-)6e9}SH*{9er!7ar)P+kA!H=X+xHSpT{q9re6t9O z9Q#*8N@L7FJVNA}Kpg4P<RIM$lIejYf)VmXRSq^ja^kOfUuHYvq%L|-k%t=T5<h10 zx1D11{gJj*Snsd#c%!n0liPhXBBOY#P0^ddf1MuWUh`;OR)H^I82Zh&RvDCRg&QX$ zB)@30RG-C>48{5e^QpmXsvc8<SBbli0R%WliTwq^Odtqr<UKc=^svY3icSc`d&M6m zX;kR;O<n@%LB}!&)se|sO4CcDplZyO1x~3aZBydIi)A%k&7+ymr;7{vZY<a+e`o5* zxj+Qh=>FWV{t`befHJREbur)mY%B8llL&;%>_=9d7#ID^*^87C7MX~!OjS|j+8uhb zy1j`zoXaTcAzNIqO(+ig7O2YF>0IYlkoh(VZMUHWYGg2F-N5r$56?Zz`vY)847!Ve zVRiT{_Xg_z!Fp9gOqD}L({=|Yd`?t$pVDGZj}5Yd2@({xDFTl3Q&dVOiB9B~^k;uL zOmO|qXuCh$2>JVE1W1fXML`Rt*5QQ(I}7u0rxt7r(ronT@ep-Kx_uK5bIjJ`ImM%x z!ZZxR)%1%F^IYPw0X3mI-%@k&3>t3`cgk}l{!UZwkW#bK;lAM7xgz+A8XmyBc4t=c zw=J+W4`hX>J}m9>SPpSt|NG_85*azY*pR!kcse>w5|=Z|`1>~|_D1SpSau_eAZ847 z)Lkdp>0YT_L?12(s`n6itlRrn{z@!QsXH#3pCC)XU}xeO2+=A;WJ_PB^9m~KALpJx zIBhI7Qol^!JY8wJ39_aaJ&8jLCvB~8?yK>1ph|=(?^!Q+1^(;(YDURuha6TBe;e;h zWTdQm{bVt&4)W_8n7<xAVJh(>bn8f<BF$?tkdNf{$E|PvEK9yhn|Ayf#^4B}@OK(W z$2wPv*(8kF5H#V>a$yV_j6S}g;~Vs$ppZ_S^d*OS8)c<*levLXy9;vdTWwLdUet{8 zdXiGfVKseCiw)^T`dHyfQd9}ugJMmICq~9r%2-{`2kIUgV{;JAy~0@Uz%uN~`K)B! z4UkzuYT4Z?P+pW1=?8s=;yZg`)M`$AS!&%*v1ZS;K%@P?E8KJjD2Tr|Tb-PKkM33@ z3NxbooK4<QrF9lxmG-tU3#{Ed!Bi0xj<`=<KA%LPjV>|cZc9*BRkthq=N(M-wB^+O zrX9055Dm@SGy5BB`k$wp?qFc9k`t!xG1-XStQYwH_hXLsu{pQT^b=<LwfaG0n%|27 zzyfliN!2RcaBxt4ob%H>D_Lj6bu<<k!>!z}UsGeZm2M`J(f$9Xi7M2p9bPLsbdjSs zIM?y<4BWvgQ>4#$eKK{GEc0q738==j_I}p1a<Gt2F9S6nO`!Q>Q2ZO%u^B{e9b^?u zr;Zh23iG~$Ebz0XYuohCKZ9+O!ECNe9?%c{#B~WE*6it35gLf7B}8E3cD5aq5T3>E z^Pv@-IWQLccmY<pO=jCYZ5!BK8+o0weyEbME)Nx5I;g|F!Hxff1R+n|^KvJ4GUqNq zsFmQz7eWq>DrL<<E`9C%d?{e>FFt?iA{PG#1*9OZBkURnqU9$f9o>l0N^4RRhKqPh zRPUm-!_YyxXx@sASR|uVOxI5xk$7A(K75VMq_~%2o62s3LPcf48LVDZm0D{OA|X*F zy-w_OY~Ls*E*eu@|8mt3h(Ha`AaTblj@9QI0n7gAmHXJOcA?Pcbobc{<jz_jlD{tc zWUrP$z5gpbF!25Snnb^%inPovn)gk@^y{^yVR*_vTS~&C^QIOtwo^W=8b~hK#gL^2 zEMceYEvcA#$7o-*k&Hn#f!wn?-7AU2+>>Z>MyNjtZO|L_+{Yw*9tt0qyEKUNLw92w z6r@bx8S{aAxjS-1xdOok0QJg+jj2dT*eFN&5?(_Wyn<aj>+zT+bJY$12zvu5N)cqq zGMCX7H)Yz>@xTz1&FjR53(x=iz;ULZFFQ$Mw9R<CiM7u!VB!yjX92B)@y0nMKwyu* zMQgXxI|=ik?AX4%p2O6nTUL(-SjzL|f?i4m8<jBN1xol?F>Hiqj<<WY;%YrTa+OVO zFeF-WyUL)osj-||aKFn3;GWiuzOgr~D7{>XnVMrWuDBJSyHl8=I;Sg<Z%tg@CAgI6 z5Z|1L=FTYV&8mI5z22k@x%!aZ0e>SwpA22SxrelNK3aJ~?36Z!m9OUBTtSWPy>MHN zJZVO_!^I~Ug{7P7lhMWt$D7|JEn%1CLb1zkRSQq`XPU=x53pk)n27n&dyoZs*7D`J zx7oh3J>$-6lnt*H`@<Yw#G&9+<vd@rhm#;z1O)*%p4WBYB)bPg@YlU2y3%pBSvKKb zyBqRL20naoT*I5~FyYk17N9JTv<^qr>>k~oP(Cf9p`}4aA)49niiogm3_41dHq_Nt zUi`+A0QX|yYCxX1yt%KH&5$qitGs-eyM1229Ro*Ohm%x54GtrwQ8q7&e*sHO>ix6` zBln%^REzjk4puPk&S^2Fbi%40<|(;+mkD^1-pXIsn|Worv-xzOh2@6xirmW+QcDui z)Hbu9>aJ_8tkdK(94B}W1s{}_XwLIF$VzUzr(~=~kk*(M!@&;-qtfE33{+e;NVcWm z=s3jW)EZj;4{>ylUL{hov`|#u!$G9c{uCh|_Uo~}^XP({j9|5x7%4QOS<$-8ssnH> zuQVw4exv;<i*!bxA)_M|tAp*^STB;F4#KL6>LmS(`UL;s1+3x6q$Zbou!VbdSyCk@ zRln>6ILxI;QIt~eko;R^KFg6kLv97`r<vASu2)y-;5-g*C7WM432E?zm~oXW@-qpO zB|@{DD*@Tbj9a6jm1CR3dt2y<I2~X3op5@eOc7Hy+i7W?MBKH}t&1ok=F3L*2M;&F z3pzN@%6?zE`67#<EJIvAc^>ApxH0r+jt#p<o22>*IFVGHZDHe(G=*QGuQfC5GITLh z!GvZ+Fv)0z!@Km1>muQ9F|u5E#+fU%deLr64aSQWuen{&gu?!}H*Ib*+KC2^0rMiM z=1`lnP@~}d7mEjCTiG;OHB!9cb<oQ4247ztGlg`zdWI}t>wqyKReh8yLea5uydt(v z4jQUTIK87gWMu5O+GFUHK&zz`yo61s4caD;PyIzv3wX`JWHh;)VqS*yu2!O1Qt_?w z`Mgi9%!HCe>hdc+PI5^c_$keJtd=P##w8&OK2QW37+3|Fo;!Z*#q?X0OIU4;h$KxB z1Yo|GA)JLjEr|c<kw|uzNY^2@**u{X0~e(p*$D*ZBC{iiZ-{l$y-5D@{mk#Wp{2HC zp@1Mq>7plXMr1q!Yvd_~>;WM!$8K8MB|YIIETYi4a{v;M#zNB)vuz$cLW$iZ2f=$J zZ+D;wKvNe@@7`-&xKdHKapouf?_EawkM&^W*%YX&<ik7!Z=I|$IYmq;Z3TwGA;RRH znUY_=W?#b=_AJ&5$4FkH>FOQw)~{buIO@rJX^423Rl|i6`_wqO$ep(PE5W%6?g4H_ z)bzx1(Gp369rZYII>cBG>zyWaMM^z7IuC%|QkA#X2Ul_M^F5M!dRqa+yf3QB3vGCC zE$2EuBueFX;e`CqW89b$oW=3hJATkFLl`sSD6L24UG`(ZP8L7JzEZ<&9!9^&zaCWK zLC$n(TDyimDj)pe4?TRlf`zlm6H#IXzQ=W62^J)BuLh(<2HWCghuOT1;t-(ux@eCP zUENMk1qU1L?dHycz)mr7i#x!{%`S^ViFSL`$#d3i-`IK(`Y=s{(FzY#YSoZY(T}^X zSMal~{>l^Xk*tJNAy%o~ZbD+#6ZF)>25<hYql^RLJ+X0X@u`=|ZT+@a5Lhsz@_^C- zAnz0OY1nElwP0qGo>?Yf_I?M8pyO%4`Dr4Pclxp4Lb(L#&GH70ztS+UB@{rnHXA#A zM^#$XvB#7c*Yy<a2drqqr7Kx?;~s$$wsgY~WBejg1MqIs;5DbtNsck*3|HkPwgi6} z{7yt9U!Z>z6fZrEQ$2y17rA}23HJ2L!N#Q*vtiR%+?axl$}tF^X~NmBQFOa}+wA8g z;9Mzqn^ATt4evWTf3ou?L|@9`zDHfJAN<i+P*7Q1w)q-E<tpY5yW5D*gplA^EKiPh z6Ui%r<nBjqKM(G(W49%*XH9Oh9=X&IJabi%dcyu=%Dpx$(CETu1=q(HSqqo@KqXif zj9PyCVix1jfG)k=`Wy&}&|7$m!Lt5?b@-!ir$4N#O73sLF=rS>Nrvsf<=?#>&jsAI zD4ZOcF#U%sWjG65)zaeGqAB<1XPnxs7dLnxRy+yoc%SvZU>#{mzzM+c=AX<2i^LDe z`~Y#Sx;(DSrI9n=QNI%0j#)X)1@o?6@sGTWDjkB3T&;sCm-`GHCE5Mzd!f!yNTe{O z(s#Ah<@?Fr?}q1FXdUFvGf!P{=k*T76?$S0ke)PIX+z+DIPdSL9uhe>!}rTdo>6>a z%dg!mld4H9cKkO;;5iT7b)47BqKYkZ1JtVtbsR)T!I}$ar6A_8j%%p@=sB@3LP;h$ zGju&B)FbKG1KD_}YS!)JPGI+&ZBMbx<xr04S}rGx$^0&u?NkoqH0@Jg66(S31(BEr z9)Sf+@Gq}CMKx;~ak2$bU<uN>WS)#|D;bzDsW=Wwbohk{XCsePnHfO8U7kwN+C8fO zWX*DC@pp%HDGtg!mh-%jOM^4pCazf0JqGZ)l1LC<QcdxlB?6W%DB=K?bk@&Mio*6R zuyy2+<s!7gGB>;3NKjDXdVP8@@zj!6@6AvQu7+R-dgOlF-*;rhnnlLnIGBx~?CB!t zRvOlMM(=|P%I@<w>A%i##Chv(tZ}@`^F`u83S<{jr4N5oUAYls<ggLpr=s}bJk1+Q z{*H@x486q|ooz-3*L01tqQs9jqu2_PJ6gWmPYf3fZ!2jPh6)OIQwrRfqR<cmwfrb_ zPDv+QrO7HtWCfl^WiHuHOEk>t<&T)RMmaLSs&)Gl{8C#~stLfosG;)+LMt#pyY@W0 z_4h@+rVHF0ko4z!mL*x1HZt}M70Q(mihj;=-}`6@QJjMX*W(ffjCt$Kq{5^j#1!N- z9y+7_tvS@eoFY*h1FVH@J9b@_deK0xIOJmWg(~l|f{5I_>jq(MOy8OcT>fHBVOg1+ zVV5@EZV{pL5f+9KNwU3IEJ{SZ!3V`mD3;l`FHh3bv9Qo>?qV}3`-Q7RNA++Ar%eHs zsp_Y`s6WqI0J{GuwSEmgY{GjlD!c-FxVYD-VPy%`CRB<p#P)lThZzXZwp<#?Fj8J- zjGdbYNt6`AIAt8((U4R6fu=5Iib^6^-a)lJKTAji>8;>7d;!jEk#w6C91qOl&$H|$ z!b4{HkotV#zu(TF5x7!e(f9;x!3r~AXK0?>(0WLQyent`;92k5x4Rol)7#re)0`gY z{$2;9?<fItV#W%D149sHDu6Ztcoe__S2E^i%A`%Cm>@qu57q~ewtt0H5+KCV4&E05 zev_v=H3tno_J_iz-2ZxpsZAz|;eJ!TO!2E;ZXP`BpiYM-c13t5ayFsDkQ=fA_5@q& z$I4q!tQPnm*GABq7C*k~4=`&+qq_>;viJ{;&e3=vZ)gV{QG8sfiWswjouK8w_t`LG zv->KD1@Bd>QQUiWOd8ZcVVA@87xYWYlUIWw>DGye1^^7<m`VAljPjWL?L{B`#*G;M z1{pGkjw%1#1BuKhRU4R{)kX24FCPb!CHf!DFEvV#03=lRzapHkXCS36wPalN88t(< z1X9WdG~KN7mAAvE2nHd=JaA_a3XeEHsjrPPInlOZ%`jDbQ}W2yQ7LoF2Txjd__u(C zL)mb7*v=DB-o0vJSf)s1=6vjqY7!n!#t7JxS3^0DJP(zKS~Gvtcca)yUt1%qL~<;W zP(WIO5shc2mVdJM3vdb)Y$umJpS3-qPn1k?sA1rf*F@aq<quLM=4=sN3GxrA;e83y zXeRE(>(pBOs_YX(=UQKfM6~<PALEdF)uDrWH-g%1h5C;j)gxfqIZg!z{e*zSn!URR z>H&aqjlx@4zOEsP^Xy#4nB(AH#<(dEpzz$)Yy^j3+CKg83vW(ryK&%3JjS<W(4RiO zmcyzcw{3-5FC|Q*akEO~?vEId?J=~9gbHGUb@XVtm^BxSob@q~WMkDYi~*&vH(+*z zO@Jm+ao^ZM!F&LEa2^}lULb>wm~L{>Y&jp3Ns&xJ>IT=#e9dFk(pwU~T1bMa3xQ}q zI6fAms$vy7i?pGnuQnbR(yH4HP^^rR9-kMBT#h}{;z$^`Q6RQUoy(n$h(txoCbg(| zB@M}>WsXne?r(-42AsRzq}i7-CYnr|B6cszj|Z$N4EP&}Uw2xd{Um(NT$Txj*oW${ z!??=J^emGy?&(6d9Gs&Nm=qM^mQYm43fvv*v%L#xUDO4M<R~0)sY?sywdH;F3b<R> z>T`B;hJ{5a4d3PHlMaD=mW=8rkafHF@Du*-<lyj7*bjZ1_b6p~0l&~(5C{%<4LZ1W zj;h(wP2A2-{Gr8Mvs`oVYkfUGfLXK@67}(nIH2=559oki7Ue1DR^STDsV?OU#Zzi* zDJjPB>4zC|YcY3^4tPIF&(L6j_`(DKdv@_PC6h<zwy^YnP>GWX)Y>zmUTifHi`(qJ z4Q-03%kZ4QRAgRhtIfjUXDsCcUA(Wtmi%Aw%mnrZ0rGQwZ5TJe67SK_o^|Qb)IHiW zRipE%vAS^!65~#>tScN^OOw+%-#+vUpEP3M5!^>JNgxua%e3FV<9Uvl>F<9_fracz z19Cy1-AbzjLwlDb^RB&T?wA?(cgDqGNrKF(7liA=Ng?Pv{VJj4b!Tk3f6;$#-=XhQ z!$#R#{Y>$`uaCWq^sEo{1qIf0Fi=m1nBXA7mzrKLY4L!^pXY=aC^GpkFV?^W?%pob zOAleENXyD97K|K7(%llkb`Sm8{4C-!Kd4EkqsCR_@q8Au)8bquW*6Rl*}ZM__HfGM zianAA+hEMN(j<O0+zY$=nEET?K<gpz5&wbDLh(MJAIB^q;6TrBt=$|MKh+4zLo}e; z`zdtXRS%*PZ=nK&BAD8L%CWg21>${xw|QEE(8@4^cs35{gmAmTL5Ro)wDVF<gbK>k zW&)`-^oOdy;snmLUqSu{trOyjae##ieT(M#Zy^?py3ze#e4Yuc1_Q*`f9rlpB=pww ze;hUN|9(fUKThA=U2Ik0|EuwVM0+uR;QLbu9PR&fEdL*n{{QbKpXb%W28AYvZ~%dD zATivNAZ6Bs|E+cYKVk&Fn1CPc{-n2?egFp`5DwT9eS(UPhW?KzZ}@KrA&PQ;`2QmM zx&H|v_zSe#*!&-F=07E`{89f2Az&yD$NXO*1i}A=5Waj|)>i*7)$fG?{IqO1vp3et z4CVz25H)X|v1*;T?3%+o4<crIp}30p-jmoGj{l#K0~~Ph&j!{!dy!6zp|W}g<-h+} zw{4CLn(aMVf(d-2zMriL@PknB{r4k0VFp_rk0A(3Wd05HfB+(>Yf|HUO*G}@NODsP zyLp(K-9h(1*#kQhu+`1`7@vo=)prh%maWOIn}5FEP;zhQgaEjKM@G`5`2X(C0}aT^ zqPHDtsF!>(Qn29$14%FLbIN;6obp(Ql;T*G<fGYIYj~X<$GJY`cKNGU;r0b)wUTG; zRYP&ho1^nj5W*bMzyR5&4RD?dd$Dm5su*)7NqWF<6mX#iVVub$;>-SsYtN3y^qk!5 zd-!1N!~#~M2<9bXtJ^nvaa0haHFoaWcp7}-NjfanRmVi~0^{Ln*}rj_)ua0)3o&x< z21%JV(dm$ecFP``Rp7)Rg#8s(7S0q>G)zBnzQVAqr(BUD%H>@+L=*MB!I#p53%}NM zzHFx96v-Pp3Yhm;M9FUFvC^dc?P0p8WQ0p}5D)39GPhA38uYdqY{F$zDSNJF^-o#) zwPkI}04wsJf^JKuN-YgNyox)Srs9fL48-Tx=0OUNLRY?i;T^rBqWk2_e-42aS`eSY zwobWRPEPq5@swrBGH_W(^3cK<PC~{N%!6X|lLS8%1N2X`T25SqlX^@OY(4O9Z6I0n zm!-3yp9AH=77VOuF0-&~PBJ!z)k#-E*s@dNv<}PpNPe0-Pencx6L}N#D#ldf?sEp+ z@oZ_SxS`{e0EVhDJt=4e6A=7faU9}4IN(G?Y28J@E_=M_+~Sjky&t4q;zg$2RBLey zvKwmT<1#E(Je%u0kvmUWGnxO6dSv?trx0fz^bZ%W5V!ubjKgo>QiXpyZ}VI$6|qln z*8LRr6ktE$THg5ake<ihlG}xq1ha;3a}!DY2M@hWPsan3K#zw>M|`QxeSX%^|K>F! zfeh~EU$`N|T@<PvOhbcesj&r(5O#~>U-hzy@87@!dr^N&@Q8Qwbm^4?T4m;$n&ANF zsDN#M>)9Mu*tTidf0Y@WO7zcZwEZM3F2zcCD<0d+q*%98>VqQ|j^VVT?Gw-Spba}! z^0Bd*<C5ZKOG>xSm<(CCwwrvEE`ZqQIU#HUc1HH^oAbFM0rN}X4$kpN{J`@``l|JG zWI=L~Q{TvSoqdM1wOC^1!lg<mfi^J{4z=qEq9s54Z8zyxS^`;17Js-A%whx`<iwm0 z{IYTT3Vr^3N-Y-spFR9}E+_QEWXoO9m4=MbIOpbc(|6-z!YwPdWgj+9kO-3~d(arm zdwY^KG}dL&Jv2_R;eP@+VtHv>*s<0uR(<YAt;|+qJdpm6oo$;3h07s`(lE6T7F2{f z?d^_P9{}n|T7oQ_gZV)C{ff^LF&C1!y?oE&;`^Tos%GawBg2Ps<@zu2_V_gI{li0V z8Eeb~x{zx&lX`J;fKvYf35Ztpr_CHXD?PtqbM?pMu#O28f>~&yKlzaUZ3VPb?X+?$ zC!Yr-M|#%1Wk2f_z%MQ!7w>#Unix1Rz13-8S8-`?G+#sB+!+tOl@lR44CFdSL5uKb zg$)&|o#97J4l@j=LC7rr72}=na!uG-WfDK{!pw+Ukke&hG$~rMdG;tQ>?kplZghRf z@;LYs!Z_q&y{*APXEgy<b5K-F%1zGf<?N=`s-!*9SSP><qigPv@4m9uF5tdxHU$GS ze$T=;T}Madg!?o~o=trfmD6J3Cia}B67}NBN9jNGLC#W7_ur+jbsUO~_tkyIfop@? zIab}g+y1}@3!$X<eR$VcSziUr>!0^gIArSm=1U`DmVP#!CjT@kPEgmFRdvfcIgCOi zdU+E^1VMIVVuNbB=iGZEZj$7PV^+VShm@qWZ%UF?$I#}<c;_0zohI(3!t}gACQTz` zD*~mK=RSkpLj^sx2R*>?(A!K&WN@TLel)_5pY-TC*s@q{&uYQXg9d1;7}tBw*{H5w z^UL}f+;KS2M6(=^<hdCKQU8cne7O{Aa@~W4#~=XKr4Am5Y!@Eax^QXz!1y(c(*KXZ zD?45LaJ7#SR44SP!=~O}$}Szn^h+R|&P5Qt6`d|ZC}>oon|mLW*<nx16DK+$g&*n( z4-bzToFFAC<CE<nHXZBNMJV>u@4;~t>+zT8=MOkf6pUw)GKapygo`ipmFl*fw!I)Q zfbwU$tG^KM)<OBGLv8mXbSIu=-&wl0vis%Uf&)=^4$P(sZ`-L{tn<<giYT>(4I9!= z`tTDJ@U*QEyO$1_W@GQTs~QjmlRVtkjY*<n^0u#rjcH<Wt|;PR%0D=V$edbsn4a4{ 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 AhX4Qo literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-more-menu-item.png b/docs/designers-developers/assets/plugin-more-menu-item.png new file mode 100644 index 0000000000000000000000000000000000000000..23fa73db266a2fa3647d9e477be0cbbc1b2c67da GIT binary patch literal 44489 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%0DAnKn)kJ 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 K@oJD!@P7mKLWmOp literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-post-publish-panel.png b/docs/designers-developers/assets/plugin-post-publish-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cd9318b68aac73cb6554508c86f6217138d29c GIT binary patch literal 16635 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-post-status-info-location.png b/docs/designers-developers/assets/plugin-post-status-info-location.png new file mode 100644 index 0000000000000000000000000000000000000000..fa35405e7a09966ef4ef39351d9bfa616de542c5 GIT binary patch literal 20919 zcmd43Q;=kB6fIcnvTeJ&s>@cEZQHhO+qP}nwv8^^#?(J|Vj|{YCSvAc?tM6sU+!<` z&Xb7TYp=CVhRI5cz(Hd}0|5cSiHQoz0|5b}{~RHZAU}~ew6m|D8!!`oDSjZJx)_)b zJ@B9Vga)GWQb0iN<Ul~afj?P4A>R`qAg8}TKxeu@Kpd$+Kp3`}?Q&c{8&I~QY7Rg^ zFev{Wz(DDlm_R^$Kw^UYimt$yUND*pB2V9qOu&5*=1AaWayP(tC>H>&hH3!KI`N<J z@%rxYa0E{@jk!CBiWfqOr1gCmYW*Vh-$VJ9apk7`7In(y`bdiS@<@nsg5cuFbKhwm z39)?sL?GmQlOhRij3(2W_gSyoX>GmSblhHViMjwVL|`c-SG9%URd^&mFiMb`&6j(0 zVn0Yi5NwhgWXl6zp)QX!^GXmLVvsB>;LTB|qdAeC4Itn`SsdR`Y7Rya(Y$G{w-XN> zLSIyKP((Q)6Ja2##WWEDFB?#>Sb!A}arwz4Bw-N8{Yk;Kl`oKdDIA|5+E1xMSrdG( zhb}loKIkUk(9(QH5?|D_$%2^f7T}+nEI>#~j>bO)vfLi#o>};QYW^b351)?wDZm)R z^S1r#r{+44pyFH_5+C&c{Yl+~3v&dvo${(6(=I5Jl5SPv;Z=EHn-_}9;BZrOI8}#Y z{zYU)Y~Uxk{N+?o&4L=V+VlKO($m9P%4}=4EH0C0MblD#rF0qkYCZ<&(dNd{!IgLF zarbz*DuOwBI@^+%ie}1>tv!htnvkJ-`un^v7_-x$^n0P6MsJQ`<axB@d*f+4_o-P@ z)!VGEp82jzQZ=V_Dq~+kEcB+%GfLfUO1-S8y#f02>y2-_>7xmUwMwHa+z9Cgg7#WT zY3M3Kr_Hl0z1Orc&SPj6c5!2RyUE38b32QGk<nmgWSdtLgZg1T*LKM@ue?(ZKu<wO zmy;q!^~ccSu!5;wYm~!GEBLAD%U*k$`6b1Hr9TB%2lSkU{0~5PZ&S@{QX9M<$jX<? z<$BBD)nIPLvoG^`iB*vVy6y~OH5%ve<yp?)xLPI>1{cQ2PwaR#z5k(0!SXU>eFLFM z%kxIFY%+ZlrT*?P{%+$KeRbW(E<ZG>%}M`UcB3k&cQ81guJeL-%jY12d0TNN!~@0# z1JCfjc_K!~XK<Xi)0=oO;&PPpOi;T3xh9k#8duj!_2xA!MAq(oli@lh2F2dfX3#&V z!xRHA8MZwNr^7c^1L5!$wu_aE1Fgf863EDc*J#SGpCgPPp(Y1j51Pt7E$XChsXya5 z8Cd^xbmSPX2cNsW21X4$5Ewlu*ecj3)^DPzk0wvt>vD#lXf;;!{q=p)=pUiaKib4i z4y3(qHsTd$XTiZYr~}8*i!iz46ITp;HAbVfLeKI`C`w*eElN*U43@y4XR`1>r`{rB zWh*`WHI%2@vg`z&Cp?L0;nt3B3;=o6t1cH2MKXAiTv`T2CU*(}sW#F^_si<WMTF6I zPIfMew5m(W!6h85q`QmP=wjW$V)bF;X6^f^&-yw)bEX9DpAMJ7aL4E6%klAzq9C8l zuSJ`cz{iD3I8y6+ogQoaE<yn6?bi1+ZWvw)%F$O#SK^tA45tU}wAPK8`CDD93`Ee2 z)Cai?NJCy6Q7e~ze$HRRZ+n$?gx-ftNFXKOzkE2V{<=hGHfF#nTkTF(N$V`>;=!L3 zy@o%(3CXjU`uo~|VFC_BQxnl`JWkU-9LAp2`;ARG1D{VFWFqY{2#~70OdE^m%b6`4 zLBX}W_PWXU2rYtW3OED0;Vd2=mV=Ksv#qe&_o5!~`RLBT5`H4J2IIAkW@Es+bq{&= zc7m(*t1I2|cPPpGY20UyF@#nMZzmlox7D$C>&<?3;a5T@degUBsO(5{dxv!r+;Bd5 zj7Ff4$Np$~;ml4G_!$3$fkDBG_ehNlPTJ=mBBC8{HYo9`9T-Wk%N#)PZ`;@NI4zH6 z>nxnxo*0UzcQt8a7US$NJ{&nw*Yia)z;N`XjI|eVTFaI%1(RLJf{lSHw)Lm@gvg`G zgva`{s^H8rU<^KboJ9sDlhh%IwqiJvI$aMusEs#3xZXSc+`ZY?yMBGonrC}Dek=nn zeLQp5%m~zhGi7tZjNgO&<kvJn8#&-En$4qYh4=NjY<ZpsYqy9NX@1KRL<1CVSl(sL z@RBXu7#k85ZxcGOQR|BpiX0j0!yO(BpUbtC=%b;21Fh!i>T_P=K-p}Kqwi>$R*;T2 z1UO1!bZC%nFO@%74IBIEn^X4RNt;Hz7cZmUd7z+tHU$-i@|XMm;84CuaLp_`IjZ=P zll2>vn@8c$ol$*f-XqQfQ1j*L^}}d(iUSYhr{|!n^Z0lmA*g4(4BjB&N&Gf3Y1w<M z@qM-%<+JRQsr1VYIt|QImUZLp-c-K0DgcX)XFH?Sihmnq<*;pDAwiMb+rl5{#>8rs zd{^dLy9*$o4B|itn$nvu5@Wu&Qc4tb(#e(HZ1U~LUDp9XU;n0nE&h5DxZh1)yS!c) zMu|slwZ1N!T#zGh1#LQVk6}nmybik4naPTVwtv)W{!p3K7gGsS(6e~LW&aeoNbfBe z|Lrq5%V)RU+gSfkK*Uf@b+abrPWscPf^lcbZG_C|vdAdrRFkX8_`DA#B|ZD1h}h~i z%cbJR__Qj2zW>|}qQx3?YD;e}&Te?tzMNI<8tchI?r@+(XeV%Tk_={84M#oZ&VR~m z_sbzE1|Kb(bC>J3%jOG}U*m6AN7`i|!a_;{io#cOMg3SpSaV}X!^xZdOgCqeiyzB{ zfR$@C99FWB&hT(q3`j$ctd1u2T8^=_r+6CZnDZG#1IDZL*^FgfWfyN0$%f~O;BV1F zrwAS?O`dIJhDOcSDc0ZpD(c;%NZ#wKR&JKN{5_#Epcn~tFw4I&JG?Qhz@eIH5!RRw z(EuF@7dyG&RZN~inCtFYk!ZB{F!$VDXyL=J)d(76mL93avi-%v+`$3HVD5M^qR?vO z^&O21(zmvBXf_o#f4x=`!{kjYyNJ6ba%NMk$97FYhX+Oin@;r%>#j*v){8Fv1=0vo z{lsdbcpY><{mr}$`b}>)AKz0z#o>~2r(^F%E$3{)+H727)-G)SggPaM>&*lCxn0Xd zM3zIT*2_K>C?yMR<U7w~F-v&`U81k$@r(NSzJ%xLC8UifE_`Nqx`BS073kedrE+-B zHUr4;$`+Jy3=`jIH4G2r=6_kt1sg+}wn8&6EL8U9V!5dz6&?O6q*_QnRZ!#GFc;=Y zqULM_lwFTz!s7)TpGgih%sM=VMIixm1AwRsMFPUAJ0X9-109Idg+j~^!Z@AN&8MLQ z0WO@4;KO@gB>CBMXG%R8kN5!=55%$~JdcpTg|YZ><n4FFKj~eNi*+*puS@aWjlgsF z`?w=@olE8*_-`*_Y0S+=3jwL*MGxV4EO7q!q^yM}vLnQVuLIZ7m#7{BU=I+$V``?m zKu1BuZ~rjd{UIF@L0aR^33Kakyo7#UN)1Vbw-e$IAc6<M?wC{gDe=Zpf1(0G^aGau z|4f=arBa*-R8o#=z9AZ1v!>@p=c!a8zP||mV?HFV#n0yez4h^F>gc-K?xYu|Lw2k} zohE<>>Q{RwF2DFRN~a&s0F(Iia${~hzgNJ1XpaspA&t-Nj(iZ2@D7JJo{{idr6l*? z(O!(3v%qIJ#8f}2k9N<O-Yep;CJ~fE3VcwaA`l0*(E&EK=)a&ZqFkb&X&-vC#FFp! zOZqu_`n?>mNW8IWqEsPp?@zbMObaL)J%0P1ug=1X@?vWVE3`9wWB16+M$+HJT*Q`R zJtH-2m4QM&TVtB_i#va=T+{}_9iSMGUqf_I;;)Qtko3P<8I_O*q^c})mmnLi=9|d? zYHS{2ad~M0G(-dM53#4Y40muFlWJv1FIz{Dq@FIeuOfcwWlN!e7?|#;RzT=^G)Yeq zhK}^+4a-9pDRGv1F7<lH{?taSm(c)Cz1d3s?W}TLo20s`a+<*C`oY9ui#H50>DT*C zM#8-3qM?gvE%#@!;@CCSMjqkG<z69%&v(^+<Dq97U_DL|7P{lx%KNoy-?z@o5G1%V zz@A|`qMN$M$uKfaGSFA$4o>6PvH*MX=Zo{(hsiij7}cC`X_51bG=%3M>B``!atDcx z*VNKQKKt|-DOOz6Tyk|%ZR0-eG}D2}*w{y#7yrWlNU#KruOWPa-ukP95T(gFTIWS> z?;qz}gRAv<bbW)4s^?<WH>t6XU|>>@$eT>Ql^a*j%|ZCl2jSx);uxnQ<mpN)`m8+* z*7~FxM`mp5@Aegcr@Qgz+uDapmn#TMDoCG<mT0XG{2ejqXOnmP`yKD>wU;rR0ZJP7 z*Q~)TG`Z7CcGei-hjW5HjtOT`&N%nE#kF;s^X;Q4O^Pj!<PuOen*BZRz|$c@!WrDx z&bMV#L838vlxDV%bH*N>veL7?CN5H4jvUM<Tj#o#km)JQ%x~kREnlw5NZSky=XsL9 zPZgp*D-TW%!=S9~4=4A_knTFHEm&6{>MjGpA;$}1@SHt)4|RV-G|NgV`m$z3zG%3L zW};uNpKa1}+PQn_%TE;<ai5}llPWWq@^!vh`@_z^hN@>Zy|MKCY`sSy7{pAd{0Ou^ z@kDvHa%QZj?;J$E@sc=DZinM9MFN^fzOO4}#KZ?ZrgeA&<<saD!RC{kD&RS7RC%|u z2ScWA-^15QD#4o=&atRoVp%NDD#Mm^8SNR4Zj8#a4oWC8!8j0F;#mf+;f45y)99K7 zdwEYZl;@#2vw6JK6*WO}s>EC9@Ukt~b8Y_hC{f(u|HZZjylw`(B3tdhK|E9AFLHTN zd%M4<Xc2x(X>(V#+hN}AG|2CN#PH4Z4@eFzym_h%4XxTw1o0lLEUpA(Lp<t-DX(Fq z(hHam`1k;WnqL3wiF=|=NGM$gN9O%r>8dGupK(d$u;Uq>$-R5rp)0%+%o_Ir^5XVI zpq@326x4m1c}@*}jGRi&u!+dg=54L5IqW}I%vJ*2+ryz{dA&V6l}*jg`NXJjdA$*u z6FZ70>7KcQbxVl``uY0A>UpFjfqAw9ygZoHgLHxIZX&ysG(BHi7DV<}rbF%>`zH^1 z=cC|$gT$`>f%n{b`@HvM-Oa<WjK%%NBR6P9M8^W0sp*Y1%PLb%b?X1?!I5?q>C)QX zRYSnz;J!FeI$HiyMR6#CM**Ih`E}YBLE(QPcF+U5A7c!dsbS{T>(al^;ai=+<I$15 zd4HlAeDpGDD}X7Ov7Ns&b#1J*etXHAP1$NZc_^mAr!ltf_ulw0;4p-y&fw9~HAmw` z%>+LY?gBMw7;U8KFVA#hKd6GcPMu)M4~M%g|3r+fVyS(3`S76LKr&=fjbO@`bu&I_ zuPxlU#(X+sT=l!|d0GJaOGKgNj33Jonz_l&&Ee&0`3v_sf^G<uAdw984jUDNSNGi7 z*knBLSB#Fjrt9VU;m*8sNNgrp4_|Q6y!jsJdd%+USWVg*Sm8(WGPBoEXN*kzp2P6` zh^f?kXbVO!%WqFJJ~pB+#+NBIW7GSmuYQZ6wiFqHux$AFdt!S-Yo;?~<%Xabi7#JJ zw7iTZWr~5l2TSyvo28YpS!WKerQQdB#Mom53;#%=g6}UE=BW8lKn>)8fqt^<bl3+E zHw7mnkEayr1IyM3KDjYymOh1w&>+F*=bpiQ)^<b9!SO-nj3j)v^mBUrLn_ZN)!U)w z#T}SY8kX;4U|3BQ+$7IC+sRd5urSF>G*<kWV=TMZ=-FBi7x%$^EV1%bI)S4#ep_9^ zvuhRmdKlREI)~fwEa;)3aI|JDIIfv7zb2y>jgKRfm=K*l0T_##Ylkb$bVrkDvEcX+ zn>(%ehvy3(X^t%Xwnbj-anK!asWp)KMEARChV@Je(43g+2vNf7oaob%Szv8PSP;IX zo$2hjD&D)SW?3@2<<t=FoL?q+5I&TdLB@gJ?-aPMrsdym$JE9V?C%I{_=%r)NLyC} zywBg>(i1%<Gl2aJ5A^;tL%yA`op}MlHM;qPrHfMThtl3~E#7X6&WF+km%V1*RR_og zxcA#*x*Zm1)#|seml&PD<}!(6P+|=}7k&a0zTGrrA++j~nQ^NL8eU&A@(O+HK|A04 zG0qop!SuoG$G<n9KC`0~p)y+m2n{Bw=Ma5#V`?X&l!{gDw8=J*U6dRWoW?Tzpa7ph z6eRL3VZJ+c_o?l&Kb|kK)(?77x-yVagNl=Wk@X!-`G`o+LBYR*r^xbf-~L*5KgTy? zF!bmcnC7bks9!IxO`LyGrv_*`aOPBLQWO22fpyNtm(g6P_A=UGaGWQ9scM2OrEgN& zfZvp}STA1*qfRcRPFY)GInZ0;ZeT=In88bPlVW(iO&G4upP}Al1t<1N_brG>nQ4MP z%^#rKJPH9<p-tib+f-ZElE;fXP<Z~s137wu&3$1+`U=Y&XxC1yaueqY{X`p0|IVUn z3I2enh=O1i3nUOxqCeQMOhOnxgwg4KY2K82AdtLh#4k{(IiVk1)y`&a+JOFptLF3j z_!JzGe()e|H1>Ms|IAYBL4c+v{Uy~<jq<{&M99$WC1EEQ)d0vwUp@h^DV;xx6$(W} zLDcpP$ny?p9Hym{qT*BCB(#!hm;X4AUCRJP4**X})99JTQDlH1_!z!|$}#g{Ix?^v ze|gGv=IjGQ8S=+fB_vndqr&{yA_2omPhn&Z?LiCdU{oO}9XNpr2oW`8^7jp%hBwJ+ z2Q5v{dzoMqA}cgBDlL;|TU9B8!%p;vp0p0TV?t@glte_R%p<|!)%ZURjl0UEp0Wf+ z%gBbNBmp{Ai|kOyGW0$Ok!?aU5KRaCop{#%anysmCt5fpI_Y#9ik)BgS>S%{<umiv zMJ(>`$D{l$bd?__27db0Up2qv3j*<%5QHq<dicFQ+<CB9MFJ36qqxmPi7Mz5s+=L= zjgr_~)oaJ6OA6z+v#XN_2j{ute@Vz?TXKV}j;j}EPXu!UvleMHA?3<%UHFr*Qmcd% zWkCbGw=lQXx*d&H$G)ELv;J^W#gt{EV-~U)OMfx{Do9!fiwT|vGTM7uv=p2bpDmfT z#*yH15>UiHUb-bi31<3GMzDPz6Cf4?J!#=9kU*cpSJB>YK*p3Y?&YZTUR`i5=J%ue ziI|obqLocaM&E+~=cHYSP}EP9<pcc-WaBkF%M<`dfDf8hOOyY@K@JoY0)0geHl_Jc z<;X}y3!lZo$dUn7Fv--9;3bkfF<Lf2q`mtY;=>Pl<`w~euq4Vg{eIQk)1626Rl;g~ zTkz7g)75DDEfKM=?2|wi7we_P`#PV0ZSxm6ubj?kQUeu2pAgsMp<MDH4NHnjFtj0s zqRk~Wt5IpK1Ul?Ak@X~;)Efkhsv0RST!G#{K;?sH4HXCL!k&)gAi;38Da?Fa)Xy?M z0%7R*J;Zvmvp>U-jBY)4S<<&jM-wop^q2{CAMlyUHfZLas{5}MxF*_lApby@8n=RQ z$v9`6xEMa*#26vLn?#p%<2B-&I;C}8RvH_7hRX?#^~~_SB^^s>i`J9v3}dBQ&!igc zE7#Vu93x;=XUVpMZtUdsFbCt;<AfIb*#YKN=Yaz!tzsEeK`;*ctTn>{fix-mo+Q@J z05Kz3!0L>HEHd>7*vIAi%2mMUJ8vvInakU4%qCkOa^h`1PubI2Y^KWja8d9CUp_{? zp+38-st!tZ>ha$|h@h_RhofN;W6hcj=E0YgM5e#JV(xzvnEN8LiF5_A8D<QZa1wgX z@JY#b+C^JEaoE&$<S&g^>L0IXMg0YWwOkRO-PQYsT_9MIg0r-@#b#$|91g)oH{h`| zEt1r1$hp;-;;fy^Ua18`hfom~0IL&W_b~^EF;q58a@Nz&*@uR>U3y6|Gm<fB{)wIA z<RH2HS7o4EuiT7%3`?n!c*pCpfdthF!5^ROhU1IHznH}JrDG6NQ#^;Rdy{-fciQ!q zC|oWLHQZ_Q%j}%=Yfeo-PuHtTF5-g|6!>A?OHzi$8EWy~XX2E(4KLNFO}nA}v>{k! z;*kkDp2eZ;V4GR3*HK3LDNGX(F8ahH7W9{eLWVhU3+<u~<<*9~^fyAN{+cdpN=qjP zF^BUECXhy-Gqs$+QABSzl!x929{mzMywR{hET#C>mSM6!ZtPKD0LOa8w5l!_oQT03 z%WoM3T;>Lb!}XMxB!>Y?Y`F=;%o2pV*?XA1rKHnyr}F&u`BpxZf2<Y+S@#hlV{?0J z(AGuX&Nxh?3XYJwN5PxMYSEFN{&?|=iB*rq44MInK*%^nIazZC7G8=h*_uVX*`F_c zn4BrYo9*1VDAcciaC<6Da~<1Z+r7L)sQ~rp0n5*w>>Ukswfs`_we3UOZF`Jhr<@cN z2e{Pj{vNh@^GY*dC3QuWgLxSR1h?TZz@L**pKqY5GlBQvgUe#%QRI%{&q0m_8u8%j zA@4L4C+(DDOTuHH8&;~mWb5FQ4loKWG#J4jqjFb*Kp9quatEm*MG$&;A44nA-Vnvy zmI-4&ioc;WXt5`L)U*)G`m_VQ8T8p*^7{bZw^<U$6yD#QzZ@CUlH@rLFN<+Bixoaj za3L|_b@sAtQr=324tb$UCWIN|$KK$ct@=M#bXtBZe?zg?Ac1zjKHH9B+QC9LMALil zg1-I0%X%|jX%1OKyF5rR<w)#iJ4>I@(u8&1@hnwX35XPdF1c*%!F-B8(3CdA$>9os zW5Jzg>F@_b%H?><<x_SLnXmkJdI2L|66AiHTU#akK*QErY&I=X3l@`^Hs~=9m8?D6 zZ2C4hHcd1Wt|f7^s8Yg9UVgjY88wh6cI}CM)rt*BR7D=#&Li27*PHPPx-gf{8flly z9_TWIAY+D@a7Nn1N8FQ>n{@RUVy1I24I+>~B)Jo)=4*_VLSP(Ve#@3`9D{@xU`Vrn z{_D`p+NM>@|8?kgBK`nJn4b=99fhG@;q+g#w-%ix-1?9F(fAj%Y>xNS?5l8)nM7Ou zBY&1%^P1E{ewzJxFc{qY{}QEUiJ_Y?DKuMTg)BIK`LTqqe0~I*OA^^_mo*BE%*&tj zEG8<nHBskF*OHfqUQ3A+o*D2l1P)oyOShKa?9N>Cn7dkoS?fr@?cF(8m<9Rc?C|4W zCm=@W!R<Tg@O~i+rW%3>HTQ!E$wlpy{h5vo<6Okdh!M1Lh~`4nt=hl5r{4ESNPjmv zz(<fg+mO$+Rod%A)9S0h@Z<Zu<o!+(|8jQ`yw4@MG=z`3g0vHbax?C{>g;+q2^G;y z<pm^>NoFSfdMhK<jVl+HnqV$QR3Z(N1&9hM=j*uMWg86X;dd5uk7}{TTu&*=ucxiH zpi*Qm<>j|&LZn{0g0amo2dSG}qkzz3ME;OQ*rT~p`%VhnRgC#%jvFsFS4?XqE_Zny zPKbw`M7m+7WALFq&{|8{GTBW57ZDGJ?M1g;0{do?``XgkdNf^!1@8Qrwl%q%qd$aG zbW)}i?CbBeF!YK0cT(N(0IUrzE1iS!#QWG5`^PBN{bHs}43aQ{A2M(+sW&M_$K@0| zPT46gHtLF`(|xm<(-uPV7JHtTW%tiQ3c+B_Yu;v^jGOwaNnk4iR;?E@>&5+tYV66{ zV}JM<i3cb>_FcSpt%L_3-pTqS08vA9d<H+~*~yKXh@|Qb%o#}x9q#9|b#t1-_{KJU zSsA*P*O4&+cssObYQD^{hGdXKb-$}XnYO*m=SS@Lu&r5$suZQ0i6B}$c7QskxPO!7 z)+rPD+LiBis74^|%;1aFWG6B4z|e=c)q7>MqQf&umbXz<7ft+55NyhqHv^YL-qE05 z!KBFC9MgPIyQ^L6tLtEW?NA=EUd^NkGbiWqUM6MoR9HoBiGN`0t7A~pm^6g|fny!a z$1?M=(h(o9sFCinbjf`?n3u!Y^dQyhd7LsjN`7f2`djt!XeQmZ@D_LO1OLY5^RO5v zbPwUWRdXxR4x*h%zzzGccEz55GJv$vvDn%DX3Jj5B}DP3o0dZ0?a-Tzy1&wW*h!4b zLap=a4Ti#(cY|9bxytcv_^hU-Ij4eLYtw|?i#Ifu|I;BQ+TDQ<;V`|Z7Y=tjW0P*? zw;ue%b4?{JkxldoO_k?x%TWd5N}y)<jl`3HLR~AzxvHi(_wC+&`cpBcx~P!nb#c^g zJ57i!8#=y3v&YMVP=+261(Sc+sTHHwa*G+%;S%Dds+h!Y_dLi8ZMHbxPKY|I8}XU_ znVgrS8*M|^o~^PFvJzu@EOzsQhdM-a>)A+xZW;on{_8*PvTHByo3Q3j0S^hxhO@h5 zHZg>Neb)EB`6-`b?dp9U>X4|3X99-3)(}`8;#J;L?}CI{Ys{{%s|7l2(@a--I|2p4 zaQw|uzen@ds<ojwea4Nw0X~OvhAGBE3E#P!?+C)foUHD*qsYBOlW^MjYVDeZzk6q- zQ&eCYqx}ILD_C}dkA*3Rw%@Wk2<>hGU77~*>Z8e}7&{#?4kwPsvzUFin5IT&^geap zf}F)&P$Kn)^R;gCe`lh+^sa(PdT#>T;v#SeZQdBT>-f(4^KK>ANV8HM3+55_AsV2F zS?fLEQt5?W>HdtTBubd;l~8bA<E*aRj$sA4%iGi1-Y?Cl!Zixpn=2#Am$%8~?>Mqr z>#BQyM)IGU`LxAgQ#Ccn0hFX2pu?`1d8A`k;9+Fs%$W7|ZB#ZX$%VPhs?u+}*rm0B zsk955+kd~GX!A$v&W?V5$CmfnWC8f@0^mdEORfF?O@<=LV2B|QF(Zhs;Jc-u_S@~e zD7d4?Q-7N6EX}~aCqikyIbF*8oQ}}yY$q(oM#PY@AR48gpB~A@zqP^Rr#X|I8X5<t z3U*2f)R(7aU;i#FM1gN%9g`NHqOA&M+eV^q8!WV=lBei)xqNi}!R!Vz$w(***+&m? zz&NF<Vk)YIN{~#nAb--pJs6hCMn>s1?O*LbPAit4t&O~&w<Kr~Otwitv)&PJvx+<z ziEy8X3a@wkefy&v!mDkB5w(>lE33<@X~)-J$hdlAX{~1$4nHD<T1HV0)6ssPvNPx- zrrF&r4`qM8cpWlPLQ=KOdZPI+31gY!#*lSm_1O^tV=?7En`#r`ewl*UDcyFNHT{-u z9g7_!Xg#w2v_!*EUN9IF#!w(`hndn#p%l|fJ0_o}`S6~DtVKx|AX#zo`-3ILM3*uC zcVI0FcW@b>5=k}}ILRpfvS&+%N7U=Qm;K59nTe-|RWKkY_S}1wmYMfSljn5Zr~plE zipwKm=p3Kjsdnecv%_g5FNuKb_>Z1Og5Ss2)$y07*ZtTf%#K#RBBNHdp(Xup&r%5L zR+#HXwbFe>7l=^!p#Gs;_fq6_RdFg9&Br_)n5plTab1T7<cU-fPN=M4HdduZnelpN z@f|^TizwQmH~o~d^PN78`p2Rtnz`H`xoHB^0%K1b<{@<!5Q_~gze_B)31n}JV`&b+ z=K`H@m9{r6EIPdk#qP|{ypy|9BG-NPu%%rQ%i&c3OPu=$l8dS*)%#z~wz20(Ulns% zmvY|ny5C)BZ-n6Pp*P8n{5N~Ef05Ofpfo?HHX(?6%nv%a4#C8;)cFA#ml1U4D;_@! zcizDh-FM<2XN!)fT*vm0LuLM*NEl)R<Hx=7z8|svi2s8@wh`z#R=fYRqhRPPRzLqN z9=t>6`ksE|o5k*zeCIBaA5`)z5DDK*_oL%H4Pi8$bN|4TE&_eWTCX22qKU4%RO|Xb zQsNV_AmB3M$Km32D|Gq){gmK#NL-FqDhCf1MVRYvG^qW~mX`)M7Q7~RvJ}O2!HIJx z{*%_q#t8U+nf~f;hecYChHMP<4X!}!__|noGLvXeYj2z2Q8uY_FUEKCTUW0O)0{+m zrJf`K!He__Mm1-p845C4K$g}juR2aga7e$aj!<gI9SowX0DVIDL2UE!FOsJ@g9rb^ z_BFgtS)<&8t5(J_1h5l7$s{3Buj$NV_&0LTjLQ-ZDVT)ha>(AXnn6WvlTlHgIjJzz zG+!-=`{G$)ZB>BPL@C7AL7Div2GXZSU)T8Wxq`>r_W3p-E#sfRl@y`9Yv+u6e60@$ zVmV!mjn!DgRly>jx~fBtB9|iio9KML^-e!{(}eRYquq<5d7M(H!%j}KgV3fkgt+nT zr@efi1%F#*ntpM&{miy2xo%D2=6Jw!ChGV`2a{kYn;8#|P(xiueC5k3Dx@Hd6g~Zy zhO=8Ixlv6cX3qvRuvLC68}DA_>Nv??^JbD(6^+<46x5z?Nwrl$7>=)b(=76*tGKus z+nyoON?ew=TSmpv^0fuxK*)sRYDrVdP?H7BO)>SNy9^dqxP%+oB1H`)TZ&%|i^yAT zjn;O857UuQd)D^&jY_<wA;h^-sK{YNv5Y<kh|Y$RmYe;_c7w^qK~(;}U2U=_jLb`V zPs2?E_Few=BnV*YpmpUAE!A6|or$zC@c0n{CiiGhUQI(9yy{u>v(FoZr(np(A(9#? zaPxUE@}XNtpnc7BDkXw2`3n=dkucoBFYDu09)jRy+Wfb&Xbe{5bvfCnXCSZT6(!BT zsyMZQ)L?x0DNzd36b}o70*C4MmVSTmsId?6M@eC0GE!KluIvijnh}$@s2T)Ci4&^s zs{5$mKggw$uDZ|6ooed?&|AlDp`bGk4V@_+`7Pl!p}C@wdTz87EU^WE04QqvPj;E? zOH7<a$bvNW@!)%5kwLj!t2<7A(NIz@VNgg9ddr7yQ?Ig99LLEVQI>@L&~WLBh(Ff) z`tYSzIMn^Z`1uAX>@WSZ!?3tsrPpc+9c|Q~9_!WVNAxl?5~AvsQrGevwd`ThDjx|9 zsJuaB<|8Z!^&JJ&PPqDRJ)-;krSvAa8Wf!0_)Jw;u&@$>_YrrNM#3}>#HoC8!w2pW z5CXL`jUlYzbF8M22zF|jIJ&HrXV)qF_K{lVm8aLVh>mewfP5TcSyOC{!NEnQ4+o+$ zhvTQmO6}kxp%Jq670~fI8^Dqa<{O^{{<Wfbv4+%q>Jqa$4}|=#IAr2S$M+B2Lj}A2 zCLB==t2m25*{^x|4Fjw3Z@5h?X)*d~yVFSFW^s>-QyFGj**{fOQN{H)PeqH(L2^?= zrYPWLnv33~|3jCT0^TkipL~_gnvd~IR0h!aN^%5&(9{OJ7iPHMSvo4!x4{3}F4?|P z!TB&&9*AqulN%+M*X?#Y7WBBJRJ-2L>ud)DvxC_@_{X7!FtKguFUvm>1+YdlG-6`{ z&!)ML^OwNH)=E_K(xwWV+MbNIvY}8SbX#`)QQg$RCKpnkMRa3BBxzGcUzAE5fzF<* zk&cfPswzferIdI9`Dv344<Ed<H3{NrZ@Ws%U=5r&^=(9r)|KU>s~IBv9Lqa9Uo|#1 zN0?jHPDV15_MD5<LHZ^<vz4SmIEUJT2>ddXC6sCl-5bGNrrV5TH_8*#$+q;I1SmWB z%u8eG!RCfzBYcf6-akJ5mO-2@GQ{YU86*uuKNI%1totDn_St}AN*6|At5DTVA<U_T zUtbX;xCf)JT<sz<iA$d+24#yjdh9Na-Rlg8Mn>PjTe|HKR|pYTxe%>gSl!84QRRht zuEp3;<EnvvMcFhtc|Zd4Q$XTLSnEpaZPz}XR=3i~F)9Hy(6c~^?6X^3oPG=t_EuH% zP0DWokLuCoY>0qmCofw{GxJl5x{6H@ns)VwboLT8G}3(3aRce92~=4XEvF9?&%Umv zDK9>kM#5%eTJDHF=&ywJNkMG&<`qN}40RO<CKB*ty_O@Ub+tI&M(iirT3<0_G%|8T zsT3*hsmj8&;cE)X<Jn;O(Bf)xNyb!jnN^S%u(NC(uncZ9^Xg-sJ<r<1dml#%z_5RI zSJW7aJ7t2o(K4&V5qd8rJ%K4dt@zSdVXLNtt9Cyw3{F?9SRED#EtM#`<x#9V{izkb zk@rnySim8RC9>JN4a)QT-NDp0cCBuOWJA&KkhEe*Q=}k4Rz?zJ36nq1!KqW^#ptx( zP?e%NX|^#%2&;c4M7<FC#fn5Z+Js!fVF=RK*KN~+^l#UJ(}?IR^xv(Y9ZWM*EG1Ie zn`+<v2PFs8*9<Fbl6Qvc`X@^a|B<QW$D%E%41mPj&Z^kw1aGZRFqn^$N=?3}^~*G1 zUmr%n7Bc9vw2fG{r=mYk&?L3OCQCf}VJtoyLq*1MOUEiDXNHBqY1kf+cav#(>^!5* zQWw8|hTMQxAQG|O?G+08L<E9NKz*Lkw08=aBd+l5F{Mu>?uEbt|KWhK>u_M0A&0OO zousX*EU%vAL2tRn$I=@>^o4g;7e~j9HB3fH$eV^2#6`wUaVcp(a2M{~U!p-cYFN^e zn#AP-;l-R{FZ@siL=%Y9lvR_EcT%yCk}1a8byb!#vAR1IhO$Z58XYB660A49%+^UL z#T>jPFEL=@bmOE_ygYddIXUgE3C5T?<okfiGfPY+TK!h)Y{dE}e5MfaY@Dp9Vp1{^ zR2+f#adH$TCf;6LwSZu<Rf$^H0vc9$M@L)asHJ4(beXjuXq7=0vm*6t#*uhrAVrvf zGbbtgh39@3Y#p?ASrGS4&0fz*OQ`QvHiw<3gO+L~qG<h5aC#9-8iXi^ECj5#%$MAC zw0V9Bgn=A1>$OYaIjDw%f!?)+X45C?lsu5p)ylB9J%&VuRvHwmy31q{+N3=$pehR~ z%K=5%k{as8)`G9l3P4zCId3?bbC`*k&}yoU&1IBbucxp?L4?#-VOkYua54?VE>;`_ zb-hy{Rq&5wTgE@cIH5!S5LqfS1xup0lPFtmq8!62;(=4N;^(dX5Mi85fh>R7w|Dnn z%MB1CXUQ#O(cPF9nq;5VGv8GocA;HKOcgBy^AgKgjl8``LdJNT3wcl_<Lnf3Hj(mR zI`)uxr6=WZRbnDie&TY)A9$EZLu{-#-_Am!kE0TH{-W`W@f4l`09EQv?C-`n|C*Et zq^a)mSOgJKj~2rM!501q*4Y5IGXL_ANsni^H?N?v5X`LRK5Z7s0-I^oU2QvqqEUjx z-VJ={>|t6YMDN?aFoJK1s}}dLs6$Nb#R|6g;JPY3%f*u2pDcU{U)d~Xn|nuXOFO%j z>)c7MDHipC-;mT!+LzX<-cy70{>Jvck0P6AyanAe86Sm7O-*kq2UfYls_w*pywA;p zE3WLjj|*YONGx?bmfhhlGi1zVBS)AS2`;&8XvCRo;GijqRCSk*y@SAJPDr8dw_yik z(hRzZ!8`c7cxfpnZ7i%zpIJE>!Xbkx<zwkvrUYp!ny38e*B|POPm(JnbMSy^<R}&M zAB?@-&9}hw`+>0}vco(NnEx585NO+%k$#Xd2Z+&y!2jY&zQHy6gU_Q*gS)x}?Y=*- zSXSS@Jl(F7A2!el7`hP&X%-9^PK79$*SuRnL#p0P-`irH&$8{<%fi{BU4fRS8pSU1 zHY#ttZ*EE+j%tx-I{aSRNVCf1pIlfN(u`AN*4TaLe%3E)Ej-)s9K=&^P9vw@?R^B| zXPMf?Md#Pv=bQbPZZ3=x<!&AoP29g$PI(LaiG<z7!n&MQ0{_8bEEJbesJ-ER)UGM^ zJ3b(kJUM|~!F+T3nBce5iT_=Ddp9i&VX;7e@vbO3UWwZt2RSV}30fE#3NzsjhbqVg zfJHdL16SoE3dx}VDIunrqvLX$soVdHkWF3-*rf&y1%TZ)n=YcFcs!4aN-Z)q=WWys z_ddH(#nC*I6O5-VVx~69DJo67iq8rwIjL3`K$^a=<$-oGyQE(%WE^Z&6?pN(KKrKO zE1dH3i_<Q6XaQ#DtEkHI7}M_3{3qqHlVRk=l=t5WPZ<|zn0Oi*<D(`isYM@CvE?9% z5zD=g&ICq~yD+V^^TL=6S*7+TZNM!th&&Ps0*(R;Q5g@%c`bKt8{?Ol7;B!~>qi!s z5E)XXf~Y`Zd<7v1Gm)v4He|Ev4bV2W@@M2}O5vQKfEkPGnkMowrW3Jc;a*REw<qh* z1Hc+%08xJ4gsofwso|Jc`90+^*HPE!7`SARy9t_o#fqUfL_d=dC082X$?HwjS8^Ma z$)2Q8QfQ8E^*(kiA(Nt)rDU_u$?z0Sr<im-VH>76jKVS${L!5Wgs>G9z9Tbz5ufHz zJ;vEV0@GktX(J|ag8VWy<BVZ$78(^#qS7!^6;C~D%gX2;xu9o{=Hw>o;0P;Sl=ShN zPrdU!MfYaIt#<|^p<~V?qV#f|MB~_$1l6<X6T&eSkc&OhPEQWipOHc@NYfSJXbS+X z&I{RlUYV+9meX(AqU@I&x27PG#39=pK|=2?k=mXLyj<+s`9BT=uYkIelM+R>6E}^F zsQme~xkGW;9As=Rzh5c`RavfV2a46B6*5OHLx_j0s{!WsFguD2PD*kO&g6w}4Kd2k z7l3wTIcI*X(@U1IWz`_JZSU&L*_Zo#yX?)3v=O3TU7V)HH0CNVIvG8@5o~|dF7#~3 zi3327P3XeeQ?<_ZIj5rnjgt{w<dn(j8OWZc55u}3(2SAy`&VA5L>cL7bFGkOX`%bV z<y>u_IY{6Gd#;SwH+dIV3_|TLozCj#F|Qms>dRnm^00|@7x*gd^Ny`VRAWKPA|Eu` zyYg2yt&MLw{b<R_2RmDSFBhg++1RU5(|unuS#SPD!PY@K!Dw5+0BNXSPtRc8*RqMv zc5Pc;08`!Ccqi~WShR%_RGadT+Jslj13w^?u0<1@>Q+<EP`p5D9OE)pGIquq>jc}h z9uj~_6Q6X11X&j%n(G=Kfr`4KiP@9BKABQTn7t2LnOJR*br3UyI*I#RQm83j9Xvnr zoO%eL9`s&8Ua-ryhW};g|HrSk@Lf732q36;c!YG99sv=?>-c+sv?#sDzXtJ5UhqnQ z=}^YmYZqv;L`;z;7!A{ueJt1$gFr{Y2FciBZYc_*gRZ+aQ4;B<6p_42knepOFr{lw z6zn0zsE6~#W_I#Aevvp@RSTY&txa10z_v)*M{Ia;M<5c9kA$3%oRma#qm?x^xNJ=< zLbqmeRnrC$ePnp~0)mfO$};0Rk34emvXwSCeGJD4u0-A`mVEFMoRtP{q=Y!c<5-+x zgNrhn{qG~%L1~#RE-Io4xX#^xi>gae^x|b1xtNNw59X4OuS1#0)mrjdQ7Zlp^U4Xa zXM0n$`O%+c7!h*YF!cH86+wCUZ{D%T#_c70+A}{*99XMK{axN!9vV*k_Biw3aZ0%x zHO&)xg_zd}?s#Wo7J7l(d85AZzy|*l0{en5gA(e&&Ly0yWlq|y95$?%Uh>*B*^DSJ zFq~@})jyE>`1qJ>;5KQ~)=QIkFI}Urde?0767Ov8m~p5c;uMnV5x&87%4@1SKRp_O zttR?_k=|Sg)`i^Ls{%whB~yw=o=3MDgV6;iT)<O#WpW$!ls|^jyjVGvVri7AuXkOZ z&GBAiK&ezi1?6RmHCc4V5jQZ0svw$=hH~!h<*_T$LSdgoKK^&+g;Ybm{hliFuj9<+ z!k>pLRq&&jDSRxW|JPz4?qcxFB^+YDJ<Fi8lb&(7_M}W4UZwJzBQe(VoPN42QPn*t z8~{uk5rpGL@CuwLV2c>U{6__ohapD=fGPgB3W7~0`JWie-P5TmFTx3O1oO-6{Mc!z zNle}`GmY(BQxbu-F<^v)h5h~h_?j_b>#@;yRv`>s7L(*Ho;^DHo0jVR_@qbezEaz* zNMR;qB!&P3J>|pm>3IV!>7Uk%c{Ho_!6KMA?~v{%@YiHEi(Bbbuk<tlrY;|lEf5nB z3ZL*4kqC4dRHAoy-xu660cp&~2?(|?6d{SuDgd8q5rpkc2mF{|wdb@ar|JRmFR<aF zWgx0N3gstiz%fKC7&C!-uiz&IjcDExkUkXgEc6fOR|NzkgyeVh16I-v{K+l@fCM>h z2!>_~9_^*C(i)DlMF#HWMI9-gW#xs#;#0<243);{Av05d!wJpDlP;Svg5-l>BQVNj z&6UheW4H#jd{lp9>{@KHLfP$jmVAwsw1v$`<W+iQ82ob?F;-?#*(xNpxt6cNbX8%N zVt$?&<u*iw35NFx&*^OkkQ8up($7s(n5s422ZZ>vChMqrcYvrqSYjxOauEa#zA~j! zfmuq7TXGYzJ!CCqy<vzeMuthFGJ=}Sho1@*WclolFW?`GPgqZ+COq_zxUDRHs+pr; zWS?Vjl-En6A;a{%4j339&+Obfo`Q}OSw14q#p5*u&z8y~zPK;L*VD~^x}Ny2;u2=` z{=*bx*kXpwe_K4hUaR9E>%tr)pVWEp6Fh2(CxmxyB;@FVg>GZ9)+6d}-YSh3;-scn z+gWq@{c}MS`BY1Qx+I=2pO|tzAs13UgMcS_3>o5ba&%njL%gn+;4(L4;sM(PDD(;6 z0^6CxzVL^MZT~itYQoEXK#5~d<6VPdX&p8<w_{sJ8?Yd!j_tCURjNo%V6GCH!DdBw z)QrEY38Kd7LXxM1JLlytm<HRn?{H37pV7<P687p17b<d&_%tC)w*onb@kk+A=9tTE zKH|zIN`w(jN2#(Rw9$Xol`$Uv-?#Ah^$pm%nsMMrrS^>HB_h^oDz28#%c)KQmOXc_ za3DQ@qNeGy&>E9>x)_{4r;{88>mZK#EqBZ3BfSR6{+iY=S#ZH+4w9!6%4XmoW}EWZ zX(ztN#yi~l<DCmfZq8gQchV9PceFUv_AV4YD2_%m0e)j1GDW|2cMbRXN0A=q5(>wP zx%&A>aoc1<hK2Dm%?<5jixy;|uqblcGK|+d@4SgZE@h*hiV4I!Swd@L&649a>yufp z>pT{Dfe#w2^Fq>R?CYr``;_OGf&C_nCq|k0-`NX~d)(j~_c>1Z*vUWUK^>14olp*5 zd&;)wd8xyF)Bl5T!M0=$BxpCHC&Hp)cZ!5ws10?am3ChCO7+mbkFG78Li$@LA-+n$ zyz=84O&dtPMGU;Yt7>2GoV4tRih|rl*kf;f6P{9DMUsTlf<N?>k-?mW$^q-}og6$> zIDkzEOi2zlZ^7P&&080v3N%kanQy8*s*v5oYBwyggr7Q}>C%yfTi`C}T^SJu26{bI zYi6KFb{Z5F52ny(6Oj`OwjtLl#CA_-uCAB;b$PfXR&>+Rg^2inaDt=a$c(}_dS=9M zIu<;ZY(%w)bBnH+#=jN?u{V+RYgs8m10`l9vCF&uL8#QS*LUXMHrwc1Zua4{oi*Ax zgmroB3TBsT4|<7YHvggBTSX(tAmR(mpEuW+!IHNtNX5J*1ce&UxjhLcQmcwIe_V?C zs-wPX07ar?N6Cpbz{PC~voVu2cleJ)m^O2DI6}g8;Ox+er(=gHW07RZE*H|4jJk#; z`1Vbb$>A~T*uia9X5?hr91XKJE?!JfW9-rrzNctyZ;f=w;LM^B=T)UVvh_%~C|tal zKZ`$4L%KoT5Uh8WrTiaLJTqlF^}uQ%>rilEAT&dDic->%a7{ES(g;-Z{E&DZl6Dg- z$YzS(?pe8cg#{7$Yx9&OpS?cA>|D5Fh-K(Zhk{moR=Yg}iZzyG99+J;F*CwawRT5y zVtwPuIp@EfO5vJk@-=w8$~0#<D#g?^{?Less6hPLmd6ZR;@{J)<WH*m7EW{|q`Ig1 zyO{TLT&4O6Q<yW_q^u7G2+s#gc5TIi=srmkeu$fr3_p|n0&2#5=&sT=1f={Gd+-%( zEki8Wd9dkrDhD#KL-#Xk#`?*w5>JGR$)W~e#Sv2Sf*7n@8N7@JgHEfF$SIXZrvBUb zmmG(1J#f*}_^=fAWhN!ulC%&fmsHlr)okDL=Ue(jYhf5vZ7dpEO0UTVd7fxt9l%sW zp}@@{>isehZpCJMt$yV%I>1Qamh0b!eGHyv#^*><4x^9N_9<gr9P`|V9bm%KD_eDW zHhH79vuP7y`A4uRAC^;cdskn8Ul&>3;4V>yW3``IqhS#Nd-(OA^aD%Aai{;^1w)vd zO@%71mnfah!V-~l_>BMcCMl0tfH_D}j{eO$lj~)I_uCV$HZZwwWIFW!8RvJk!lo6x zD}XTt`q}W*gK;aAh1osvLF;TzFfpoo?%lHhXN?v01=<Dr$vEqoeIRq~^!hiW0SfW3 z^8M<MU(N@l133v&um<B72cRxSLni?_Bl5*zf3yWq3G#ve=uBw6y7a`k_&=-vr`Xw7 z@l%v}yjKR7TpoA?5^xuPZ#@o{&^MR(Rv~(jI6>U)yQo(}4~tR@OrijoBdlK)%2<!( zQj=O$)aIkdD1j5O5IZ;DuDr$edfqZMsh0q#pNi})ns!A2gl!&7s4tjb|NB7<=Z_qD zbH-PI9%LAPF*zFTVyKeJ5B31<w;F*A-84?q#A07HAKD+vSMuZJSu6|2$+G%ShO>&H zGQUv#k3LwkEoiX%(dtSs>4l<x44j3sCD_)E&6oelXxzo6bqfEvgC6(LSHhjPz_*%v zoOsof?v*lKsQ;OB<Ddz{BFSowtd0T3N5jn0t7v%~Uj&tJ@uXQLzFb?N;L+2m=X0~z zIJu)>T^vpd?y3gS*2ZX9NtKZnYFjVi>z~hCW|J6P2rpjq7x$pzvd<`eWh3o<1!R0* zccK2diufN5qO*zM!4k?_W%?V|<lbi>ST-t|dBX(jD^(!PW&+aa?Lr8Zi<yM}hYe>d zU9X}G|GFWY^de{55gu;I*9iy+iRi!o0@xaRQ13+Pw_=NQ?)IU#I$vufSy&OCr%ZHu zN2e{-YM#h}(k8uByVb~i|7y&lS<GAumnu@(Z{XY)%4sbvmfgC|3R6nH(xch5%t)tQ zBKofC$0NUBm(i{@)9%BtQ})yZ5oAj$`ti#C`-On>&(i7;i0H(6p74yT)H1SvE4?n( zY)H}qX*LzmND?DEW?4Nd{s1Gh?NG@!8Bo#<?ihL~N@=0$O0FdM8{lGN&k$7KE`WH( z##1-QxH|4jf*~PAUY)AvQd}R38=hpYZATccA#q(_Xv`+%;^G>${ybSYj#sP~v7kAJ zA!kLhTQ6<J4MJ>$fk!}9U3Wg6;Xw;Mrpx#ro51-xt3^(0?sT=><o~azao&BO>xY|n zF9|LU$bmwxH(lhnSUK(cx?<*q26;N_FPd3Y3|YfDY*V?fO~<^oBD-I39pBfT7Lu!> zcqQUFug~K@@kiBfdF88p%#uLLAZAF5#wDNDpkFCEk5a73bwz|qV@X(*k%7kQv{&EU zY>m3mrlB)?RF1P*^!dfgI;_dAca}I<e5HWP5JhjWAs=J42)mJzBuE8;V<^_f`Ad*T zQb|c#UDOo!UR^G<s%v>CiGo`8foxHzSe8d2l^Al=UJ}wNg>KMOg!slY1_j$7HPm8F zH3QBl?;dRAoQU|Lc#4KgBKq>_^@8gqq0LDNFuhy*Cr2dcZCfTrCoR*4+1UBSQUri2 z0B-R)LirJ<u#*>E$g@O2$Z%+BJ(k5WKQe8QUjP+yOn`qP7`kxLJGIwP#qQ(*Ld+26 zv5LMfw_Ue2*Q4YBal;DX<^W})ENgq9$&(U#jUukj5{9p=!j&&_eKag8l<wV7SN8q} zE&>JeLW-fU5YY?EPng3j@b{fLI#rlTrY3;$P|kmd#JZ>|v!P?mW}L=Fq?%@K>OW1( zT*Sj>yPnGR>?RSf0^Ecq<F#LKy|RW3PCpKFHknHRaVZfA`viRfMu6{LDfIu!Ucm1Q zzx}WmZXpe+p|jFky=2#=Mn>Kn`}G!CW}(ud7AC!Q^)Bykxj243+l+)H!LuP?B*C!O z^_L6;+;rwaY7Z?T1z2x=4bVNgzEn>?5PP61O7xFSXjK$F(}T<^FNS|9H&Vn~o8(!R zCuvD!WOKv+DJqpP*kOe-%ZNvs(GDhy8Ym2)4Hi#t0@amqTZO>3=ucq+0%FDU6FGgw z0t8wRV}>{t?2F&WvX&LwNX{9rOAN`s*zYy>ntFHV)BL-zdMUwmMDhN&Zq6$l&Tb39 zdP(prA$l1^iC#iXw9&g@7!pxKjFw>3AfrZ<8AG&05{&*6!XPt*=q1_^E$XjCKYB0c z%k8;1cjxAOm;3py_I~!W-o4hlmiGJu-m8Td9`coX&oM&tfr$jo(gzXsEMz&Buh~=o z!{|W?bmGRHl(_^U$F&rgrRcQ_TjYfa6QF{ie@``3(C2JB_B#1ucP3*X{4R=9@?Nzf zOyh4K6K)Z_-MyYI$(+45kcd(-DC?U(j^&T_p`3K>FvW@_S)<eX@0JFx)%&?%;W-g^ z6HO%DsS2!~ru&u36>jU_rEPD&lTq}=XdlV81oAIjO<d(&kp9KKgD<T@wiu4$inITw zbptes97`Hqt0U#SVbYG*L;tK(d$EoaWtIKZ3)?^1I7frD@~Mj{cC*DZ^pbBlZgo!+ z<b6NY&ne$*m96{oJHArlkVZwPAl6@W5<c0i-n`GX)8*1}=X#iJr&}O!CCV~c1xw(z zv#|GzSB4QE_xyy*CAnE~X?Z@%Y!R19IK|!<e%K4e3l=C$M|U@MX6*w79a4(2<RV>% zPiaq@&a(pKt$&O0|6*4MlOTVCZz{?Uxt7%6BtQYCy3d2Dsz0tfTGacl7^6P5Gs8YV zQQV?bvS+B#9m>wkz(B{8O-djP^+y+Z-U8>Q%hpylHq$?nXQ|G)dA&QZi}6ImvW2gd zxkxBb8AXZLU;<kg5k2ksDjo4D_GS59yqrqMgf@PM+#vA?Pg2=pPAhh;@^qn7t!B)+ zyPjBN9Qf0STN2m%%cx_wsNBi57kO*W)aol7Nm0^<DqSFN4d5Ek6jg0nPp;CsS2^}M zf7b&8^^#afWmC|oh!lF)Zt)!}cl|R4G-*-NuNBIH9dEgz>^U&P;iOEECA#^8uU>i9 zQmtd!V1YDq9WC>Ory!w4GEv|_Nucw>c7RqkA#tTJwj-Q((@A7PpgYFd2z)las!Jzn z5`4|;&B?>J8-W2|wD&}Q>+8*G&{=!Y-ksvBRcR61X_%a+NEGrnfqKESNuB{$9{Mpx zBi(OmN}sv#aalq{xnHq4=h1X~r##%&yQMBS(W0cWZL|ef$)->|-{<<()I<1P*2J1Q z`9g!R=IDLtHeuDf&g<+HqoI<QW=h=>ZhHhe{yF1^eKM&Hrp8GouGGv$Ob-n66O)|t z3=ud4D-Oq9X&`O+lLcao5+0<<y@{&&Ae!otD=S`+ZuRP(Ob0fhZLi|P8pDLG^qt2p zh}~41l+!v5lB=CCdVk)mMU8v-vhs)f(2`}|25y&7Un}Fmtc^FV?d=oO+KM4Cr`Pne zSm?eHIfKn4D%{T9*dO;s_|q1L<#qP<t6WJL^J;J-k(XlXL;|atk|+FHqYD^xv(Em$ z`gYTw{_J|K#1sS_;Ps?@=Lj2q^v4+j*RI0;`%iYq1%O(10Ii#KMy4SAC<flb$uu@Z z(;alIi#>_-bpR5vk^(GrxjHxK1ffv5Nyb)@y#ud+)P2T?29fnWE{EJkur&^wT|!8l zD+Xefm`id;&rG~=xIBK)&>Ew;r{MUSxn?{QDGmUmQhL@KAhKS7QpL0=`xyU^*337c zNrpI9R#vFB^*sdz(TWa;wuT;)4DiXzb16JS=Nx(J3(TYRg}&6(ge|kuE0%chy%7K` zrk2Gnl#!8<Q(Sou2tfL(E0~=fC{iUjpqjv-LE@(P!#isMegyPZIbO$IJY;dcHFBTA z(Wg8TGK_|(P~YH8qN!y84n2^J?PnkxaOk;DvBQIbf1uU0C4Xul3Lw;)B!#m{Qvh10 zXkk{rY=P~^!Ld3@k!QP8r$xxTj-by^jLxdDcxYLzB`jd;I=K-Ypao7IF)asjP+XZg z_TNz}@tHKNc5mCI!fMq{zW3|hC>#vm6^(QlYY$()E1B>v1Glm6ANcEe^n(!W>a0I} z^ms)-Z!CGIuaiHr=xm&j+`nFF&F3gZ&)GL6)I}=E>YZPTFnqGY2_cug+A9dH5{Mmb zGk9poez-{PVbJlJ)-0=$qBl-&ysm%1kK5pya#ISaqaD1Mzt}x}7qg7L>O|S5^u5`j zNlk0qSwjV}7JGRZK2JGjgVa6t=MIsL-3H3w7r{vA#%jjCWm*%PSIe}xTC-uGX>`Qr z8AjD!^_NcDDdmKyQVznGgsOw};$-WY^v(M^I@DtZ#<_$7Sm>i*zx#)%SE-Yc<hR`% z`fMUu(?P_ZZ>hkR?1&&@YJz#c;+F#xUVEID?2WX!PP?|G3SdMd4=41hTPtM^tP_4I z+XPu%_;=ZB^+|nB!~d&h@<Xph9pk=IC}-kco5q(%^`FyzG_y4Z@HZ%LVqpUrvo0A2 zQ_9)so+joriB8y9<^@baFw6Ln=arV>XH?f{<kDgMg`s{!c;?pn%Axo!^MQe}T+KY$ zi;s^d?Dxx`4rVo7HhLv4x{Y(MjxBOkts4!L@&tXmpvU9G;6^`--SOeMtwySjF#P#8 zvF$Ks=;=|{%+i*01;dsiXXa&hm{BaKe%Y&C;*T$g57}8#TWB!dK2r&~6-jG1_Pcn@ z2F+_U7m@(Av^iMSb3Qn%bQ&P<r$)UvapMvo_&NxLv-Jxa5Y}kR@(wgN?M-*yscOh2 zJg=jm=kf8lcfpf*ZAyi-faPF&x<`r5tNaiuTRPrnKD|x*BjYg=)mOn^-o9P_<lz}+ zBG@Wz+AGbdDnZ5hSMb{do|CD0WD^i1h*dTP889#y_N{kWQorUpoXa=sDM~YCMvYrH z0)NL|$AR*`*1pqq&}M#{Qd;u7F3G)<@YBeDH9DycIj!#TLn^OgMFgo#pnNCAH_Fft zZLeaoY$bDoysri@ElTawtvwrL<(h6^|5o<r4*i<wdLzvz<A<c-#0KJ6y=Qyf6I3Im zuE-c~bL~TG$pRC(>J}`o=8L~`EFbtV$}DSA`a6E_tXz(??K{I#ac73tw#NwB-j@K< z(i4P3g1Xnu!Zifm>)Y9}ZBSFCTIk#&=dE|mBi?;_TJb^gn|(8DBG|L81Isb7rj}7U z;6W*`|G}0YH1l4>zk8bi4tih$(w%F3s*Oc`%@#Y4nfV9F`%pN{7(_)LCKYkGjT}`s zX}&Yk`u8hm1ch7U!g%quueMTM+nGu#Q@Hi85Unk}nWnqPVws>sEUympGLbDn_?Ec6 zoT2ZzZ%D{)X7uIks+)#SrwM^&uvWH~yUtD8R<KO`Zc|PF#&FU$Yo;1Tyv-NE)bc0^ zuiw-2T><wJ(VrDJF=O^46GrzN(?v}1e9-O6UHY*)U#7XXY!hzU0#W{vMPDFPeLw?J zuM7*A_o<HrTR=OblN>}^kLz#Eh*sdk7T2%2vfEIln2l&RG1j2;kJ91%LaJS7=r@v- zFWkk;_+~CVf}orB(Wtr2JW(^7yos264B?R2q1P*`#l9la8rF)}EByV}9I}`p<6udb zFA6MmSDcCS-8Z=74Y11m?ILTymWA`U$~8*NeV}F;kU7bHVucj=o8(80<Mxn=nC@cb z?D3JP-$!c)m9OIo*X{$x#=w}A77n>h!5U<hNOU_-oodgs>4h>eUY*4zxK!iFM*L}# zcoF7w_1^QO1sMzZxdNGOcs(+$0;CQ<d1;%_Hv#6`k~ujs`laNgRDupo3BP{2=kk-_ zLuteJ;tBHYz>r33g3Y;hgtl{ZBH;hNymBI!i8i;e4WXrTXtq1tp5VTkpr(#HR6bA} z>L#&<jA=k%!Z*U_b>u0Ygq|I&C9C_=a}PD!LX9Z1v$F;DQoIcE{l|aa)Y<WW>(fc> zDdD6?#~2E>xf@yV068_-4qbyTaU=uY(GUN$<>KtvU8wGZF>=nMVUtAAqdiOJCKGQ< z@14Fxylk&QtBB(4sN94ekeiF(e74C#(%)x}uEh&{_>#<7%uzaO8E?T%;mXCLO#zJQ zQ_uv^gBCtP#e@bD9vDp|hqeX_5QcI-#d$N5a{S@+S(wO36tMh}@!8MmF*Ia{XcfHZ zQXWbac|CkvRrgi+JFy?n6~jk&^6oVTG!zxxQBtzGR2nPwU<3lBG#e?uS*Db|V_dmM z9N+~v`ZRcsSHh2w&aYqj2$1+c>c0(-cHq1UjPenP8UvAS#sASH&N*Uom`MNEO;{(_ Y@t*Is)<M=NV45O{fsUzmCB!l6--%Mq#{d8T literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-pre-publish-panel.png b/docs/designers-developers/assets/plugin-pre-publish-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..ea765f4f54319b41f7d71149a86c478132b8943e GIT binary patch literal 21254 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-sidebar-closed-state.png b/docs/designers-developers/assets/plugin-sidebar-closed-state.png new file mode 100644 index 0000000000000000000000000000000000000000..025da900ffcdd594c27a097e0b6ca23928377ec1 GIT binary patch literal 6773 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif b/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif new file mode 100644 index 0000000000000000000000000000000000000000..851898484bc3cfc978835458d715176206740e88 GIT binary patch literal 52542 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 literal 0 HcmV?d00001 diff --git a/docs/designers-developers/assets/plugin-sidebar-open-state.png b/docs/designers-developers/assets/plugin-sidebar-open-state.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c3781a500f0020cf6db25d807eef939f612ba7 GIT binary patch literal 10854 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 D)n!^F literal 0 HcmV?d00001 diff --git a/docs/designers-developers/developers/slotfills/README.md b/docs/designers-developers/developers/slotfills/README.md new file mode 100644 index 00000000000000..50c23ac616c1c8 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/README.md @@ -0,0 +1,112 @@ +# SlotFills Reference + +Slot and Fill are components that have been exposed to allow developers to inject items into some predefined places in the Gutenberg admin experience. +Please see the [SlotFill component docs](https://wordpress.org/gutenberg/handbook/designers-developers/developers/components/slot-fill/) for more details. + +In order to use them, we must leverage the [@wordpress/plugins](https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-plugins/) api to register a plugin that will inject our items. + +## Usage overview + +In order to access the SlotFills, we need to do four things: + +1. Import the `registerPlugin` method from `wp.plugins`. +2. Import the SlotFill we want from `wp.editPost`. +3. Define a method to render our changes. Our changes/additions will be wrapped in the SlotFill component we imported. +4. Register the plugin. + + + +Here is an example using the `PluginPostStatusInfo` slotFill: +```js +const { registerPlugin } = wp.plugins; +const { PluginPostStatusInfo } = wp.editPost; + + +const PluginPostStatusInfoTest = () => { + return( + <PluginPostStatusInfo> + <p>Post Status Info SlotFill</p> + </PluginPostStatusInfo> + ) +} + +registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); +``` + +## How do they work? + +SlotFills are created using `createSlotFill`. This creates two components, `Slot` and `Fill` which are then used to create a new component that is exported on the `wp.plugins` global. + +**Definition of the `PluginPostStatusInfo` SlotFill** ([see core code](https://github.com/WordPress/gutenberg/blob/master/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js#L54)) + +```js +/** + * Defines as extensibility slot for the Status & Visibility panel. + */ + +/** + * WordPress dependencies + */ +import { createSlotFill, PanelRow } from '@wordpress/components'; + +export const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); + +const PluginPostStatusInfo = ( { children, className } ) => ( + <Fill> + <PanelRow className={ className }> + { children } + </PanelRow> + </Fill> +); + +PluginPostStatusInfo.Slot = Slot; + +export default PluginPostStatusInfo; + +``` + +This new Slot is then exposed in the editor. The example below is from core and represents the Status & Visibility panel. + +As we can see, the `<PluginPostStatusInfo.Slot>` is wrapping all of the items that will appear in the panel. +Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed betwee the `<PostAuthor/>` and `<PostTrash/>` components. + +See [core code](https://github.com/WordPress/gutenberg/tree/master/packages/edit-post/src/components/sidebar/post-status/index.js#L26). + +```js +function PostStatus( { isOpened, onTogglePanel } ) { + return ( + <PanelBody className="edit-post-post-status" title={ __( 'Status & Visibility' ) } opened={ isOpened } onToggle={ onTogglePanel }> + <PluginPostStatusInfo.Slot> + { ( fills ) => ( + <Fragment> + <PostVisibility /> + <PostSchedule /> + <PostFormat /> + <PostSticky /> + <PostPendingStatus /> + <PostAuthor /> + { fills } + <PostTrash /> + </Fragment> + ) } + </PluginPostStatusInfo.Slot> + </PanelBody> + ); +} +``` + +## Currently available SlotFills and examples + +There are currently seven available SlotFills in the `edit-post` package. Please refer to the individual items below for usage and example details: + +* [PluginBlockSettingsMenuItem](./plugin-block-settings-menu-item.md) +* [PluginMoreMenuItem](./plugin-more-menu-item.md) +* [PluginPostPublishPanel](./plugin-post-publish-panel.md) +* [PluginPostStatusInfo](./plugin-post-status-info.md) +* [PluginPrePublishPanel](./plugin-pre-publish-panel.md) +* [PluginSidebar](./plugin-sidebar.md) +* [PluginSidebarMoreMenuItem](./plugin-sidebar-more-menu-item.md) + + + + diff --git a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md new file mode 100644 index 00000000000000..cc9e15fb9ffde8 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md @@ -0,0 +1,27 @@ +# PluginBlockSettingsMenuItem + +This slot allows for adding a new item into the More Options area. +This will either appear in the controls for each block or at the Top Toolbar depending on the users setting. + + +## Example + +```js +const { registerPlugin } = wp.plugins; +const { PluginBlockSettingsMenuItem } = wp.editPost; + +const PluginBlockSettingsMenuGroupTest = () => ( + <PluginBlockSettingsMenuItem + allowedBlocks=['core/paragraph'] + icon='smiley' + label='Menu item text' + onClick={ () => { alert( 'clicked' )} } /> +) + +registerPlugin( 'block-settings-menu-group-test', { render: PluginBlockSettingsMenuGroupTest } ); +``` + +## Location + +![Alt text](/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png?raw=true "PluginBlockSettingsMenuItem Location") + diff --git a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md new file mode 100644 index 00000000000000..7db8dac1c76d50 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md @@ -0,0 +1,25 @@ +# PluginMoreMenuItem + +This slot will add a new item to the More Tools & Options section. + +## Example + +```js +const { registerPlugin } = wp.plugins; +const { PluginMoreMenuItem,} = wp.editPost; + +const MyButtonMoreMenuItemTest = () => ( + <PluginMoreMenuItem + icon="smiley" + onClick={ () => { alert( 'Button Clicked' ) } } + > + More Menu Item + </PluginMoreMenuItem> +); + +registerPlugin( 'more-menu-item-test', { render: MyButtonMoreMenuItemTest } ); +``` + +## Location +![Location](/docs/designers-developers/assets/plugin-more-menu-item.png?raw=true) + diff --git a/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md b/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md new file mode 100644 index 00000000000000..07498eb1008da5 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md @@ -0,0 +1,26 @@ +# PluginPostPublishPanel + +This slot allows for injecting items into the bottom of the post-publish panel that appears after a post is published. + +## Example + +```js +const { registerPlugin } = wp.plugins; +const { PluginPostPublishPanel } = wp.editPost; + +const PluginPostPublishPanelTest = () => { + return ( + <PluginPostPublishPanel> + <p> Post Publish Panel </p> + </PluginPostPublishPanel> + ) +} + +registerPlugin( 'post-publish-panel-test', { render: PluginPostPublishPanelTest } ); + +``` + +## Location + +![post publish panel](/docs/designers-developers/assets/plugin-post-publish-panel.png?raw=true) + diff --git a/docs/designers-developers/developers/slotfills/plugin-post-status-info.md b/docs/designers-developers/developers/slotfills/plugin-post-status-info.md new file mode 100644 index 00000000000000..61d0c0435e55b5 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-post-status-info.md @@ -0,0 +1,26 @@ +# PluginPostStatusInfo + +This slots allows for the insertion of items in the Status & Visibility panel of the document sidebar. + +## Example + +```js +const { registerPlugin } = wp.plugins; +const { PluginPostStatusInfo } = wp.editPost; + + +const PluginPostStatusInfoTest = () => { + return( + <PluginPostStatusInfo> + <p>Post Status Info SlotFill</p> + </PluginPostStatusInfo> + ) +} + +registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); +``` + +## Location + +![Location in the Status & Visibility panel](/docs/designers-developers/assets/plugin-post-status-info-location.png?raw=true) + diff --git a/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md b/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md new file mode 100644 index 00000000000000..de4a2011d0504c --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md @@ -0,0 +1,26 @@ +# PluginPrePublishPanel + +This slot allows for injecting items into the bottom of the pre-publish panel that appears to confirm publishing after the user clicks "Publish'. + +## Example + +```js +const { registerPlugin } = wp.plugins; +const { PluginPrePublishPanel }= wp.editPost; + +const PluginPrePublishPanelTest = () => { + return ( + <PluginPrePublishPanel> + <p> Pre Publish Panel </p> + </PluginPrePublishPanel> + ) +} + +registerPlugin( 'pre-publish-panel-test', { render: PluginPrePublishPanelTest } ); + +``` + +## Location + +![Prepublish panel](/docs/designers-developers/assets/plugin-pre-publish-panel.png?raw=true) + diff --git a/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md new file mode 100644 index 00000000000000..238d4297494ee8 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md @@ -0,0 +1,41 @@ +# PluginSidebarMoreMenuItem + +This slot allows the creation of a `<PluginSidebar>` with a menu item that when clicked will expand the sidebar to the appropriate Plugin section. +This is done by setting the `target` on `<PluginSidebarMoreMenuItem>` to match the `name` on the `<PluginSidebar>` + + +## Example + +```js +const { registerPlugin } = wp.plugins; + +const { + PluginSidebar, + PluginSidebarMoreMenuItem +} = wp.editPost; + +const { Fragment } = wp.element; + +const PluginSidebarMoreMenuItemTest = () => ( + <Fragment> + <PluginSidebarMoreMenuItem + target="sidebar-name" + icon="smiley" + > + Expanded Sidebar - More item + </PluginSidebarMoreMenuItem> + <PluginSidebar + name="sidebar-name" + icon="smiley" + title="My Sidebar" > + Content of the sidebar + </PluginSidebar> + </Fragment> +) + +registerPlugin( 'plugin-sidebar-expanded-test', { render: PluginSidebarMoreMenuItemTest } ); +``` + +## Location + +![Interaction](/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-sidebar.md b/docs/designers-developers/developers/slotfills/plugin-sidebar.md new file mode 100644 index 00000000000000..c929eef473b6e3 --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-sidebar.md @@ -0,0 +1,35 @@ +# PluginSidebar + +This slot allows for adding items into the Gutenberg Toolbar. +Using this slot will add an icon to the bar that, when clicked, will open a sidebar with the content of the items wrapped in the `<PluginSidebar />` component. + +## Example + +```js +const { registerPlugin } = wp.plugins; +const { PluginSidebar } = wp.editPost; + +const PluginSidebarTest = () => { + return( + <PluginSidebar + name='plugin-sidebar-test' + title='My Plugin' + icon="smiley" + > + <p>Plugin Sidebar</p> + </PluginSidebar> + ) +} +registerPlugin( 'plugin-sidebar-test', { render: PluginSidebarTest } ); + +``` + +## Location + +### Closed State + +![Closed State](/docs/designers-developers/assets/plugin-sidebar-closed-state.png?raw=true) + +### Open State + +![Open State](/docs/designers-developers//assets/plugin-sidebar-open-state.png?raw=true) diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 2095e773664bb8..dfee8a25831c60 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -89,6 +89,54 @@ "markdown_source": "../docs/designers-developers/developers/filters/autocomplete-filters.md", "parent": "filters" }, + { + "title": "SlotFills Reference", + "slug": "slotfills", + "markdown_source": "../docs/designers-developers/developers/slotfills/README.md", + "parent": "developers" + }, + { + "title": "PluginBlockSettingsMenuItem", + "slug": "plugin-block-settings-menu-item", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md", + "parent": "slotfills" + }, + { + "title": "PluginMoreMenuItem", + "slug": "plugin-more-menu-item", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-more-menu-item.md", + "parent": "slotfills" + }, + { + "title": "PluginPostPublishPanel", + "slug": "plugin-post-publish-panel", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md", + "parent": "slotfills" + }, + { + "title": "PluginPostStatusInfo", + "slug": "plugin-post-status-info", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-post-status-info.md", + "parent": "slotfills" + }, + { + "title": "PluginPrePublishPanel", + "slug": "plugin-pre-publish-panel", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md", + "parent": "slotfills" + }, + { + "title": "PluginSidebar", + "slug": "plugin-sidebar", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-sidebar.md", + "parent": "slotfills" + }, + { + "title": "PluginSidebarMoreMenuItem", + "slug": "plugin-sidebar-more-menu-item", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md", + "parent": "slotfills" + }, { "title": "Internationalization", "slug": "internationalization", diff --git a/docs/toc.json b/docs/toc.json index 5672ab70545ad5..4d0c1b38905e93 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -16,6 +16,15 @@ { "docs/designers-developers/developers/filters/parser-filters.md": [] }, { "docs/designers-developers/developers/filters/autocomplete-filters.md": [] } ] }, + {"docs/designers-developers/developers/slotfills/README.md": [ + { "docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-more-menu-item.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-post-status-info.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-sidebar.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md": [] } + ]}, { "docs/designers-developers/developers/internationalization.md": [] }, { "docs/designers-developers/developers/accessibility.md": [] }, { "docs/designers-developers/developers/feature-flags.md": [] }, From e91dc282dbf5f111225592c31976ef1ce80a4645 Mon Sep 17 00:00:00 2001 From: Ryan Welcher <me@ryanwelcher.com> Date: Thu, 23 May 2019 16:51:59 -0400 Subject: [PATCH 175/664] Update/slot fill docs (#15798) --- .../developers/slotfills/plugin-block-settings-menu-item.md | 2 +- .../developers/slotfills/plugin-more-menu-item.md | 3 ++- .../developers/slotfills/plugin-post-publish-panel.md | 2 +- .../developers/slotfills/plugin-post-status-info.md | 2 +- .../developers/slotfills/plugin-pre-publish-panel.md | 2 +- .../developers/slotfills/plugin-sidebar-more-menu-item.md | 2 +- .../developers/slotfills/plugin-sidebar.md | 4 ++-- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md index cc9e15fb9ffde8..ce81b96f3e1824 100644 --- a/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md +++ b/docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md @@ -23,5 +23,5 @@ registerPlugin( 'block-settings-menu-group-test', { render: PluginBlockSettingsM ## Location -![Alt text](/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png?raw=true "PluginBlockSettingsMenuItem Location") +![Location](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-block-settings-menu-item-screenshot.png?raw=true "PluginBlockSettingsMenuItem Location") diff --git a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md index 7db8dac1c76d50..627d6fab767a05 100644 --- a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md +++ b/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md @@ -21,5 +21,6 @@ registerPlugin( 'more-menu-item-test', { render: MyButtonMoreMenuItemTest } ); ``` ## Location -![Location](/docs/designers-developers/assets/plugin-more-menu-item.png?raw=true) + +![Location](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-more-menu-item.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md b/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md index 07498eb1008da5..50c38098c19496 100644 --- a/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md +++ b/docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md @@ -22,5 +22,5 @@ registerPlugin( 'post-publish-panel-test', { render: PluginPostPublishPanelTest ## Location -![post publish panel](/docs/designers-developers/assets/plugin-post-publish-panel.png?raw=true) +![post publish panel](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-post-publish-panel.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-post-status-info.md b/docs/designers-developers/developers/slotfills/plugin-post-status-info.md index 61d0c0435e55b5..4e0ad584872b03 100644 --- a/docs/designers-developers/developers/slotfills/plugin-post-status-info.md +++ b/docs/designers-developers/developers/slotfills/plugin-post-status-info.md @@ -22,5 +22,5 @@ registerPlugin( 'post-status-info-test', { render: PluginPostStatusInfoTest } ); ## Location -![Location in the Status & Visibility panel](/docs/designers-developers/assets/plugin-post-status-info-location.png?raw=true) +![Location in the Status & Visibility panel](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-post-status-info-location.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md b/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md index de4a2011d0504c..622a945dd08d87 100644 --- a/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md +++ b/docs/designers-developers/developers/slotfills/plugin-pre-publish-panel.md @@ -22,5 +22,5 @@ registerPlugin( 'pre-publish-panel-test', { render: PluginPrePublishPanelTest } ## Location -![Prepublish panel](/docs/designers-developers/assets/plugin-pre-publish-panel.png?raw=true) +![Prepublish panel](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-pre-publish-panel.png?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md index 238d4297494ee8..5bd43819501e74 100644 --- a/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md +++ b/docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md @@ -38,4 +38,4 @@ registerPlugin( 'plugin-sidebar-expanded-test', { render: PluginSidebarMoreMenuI ## Location -![Interaction](/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif?raw=true) +![Interaction](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-sidebar-more-menu-item.gif?raw=true) diff --git a/docs/designers-developers/developers/slotfills/plugin-sidebar.md b/docs/designers-developers/developers/slotfills/plugin-sidebar.md index c929eef473b6e3..66121d8c8941cb 100644 --- a/docs/designers-developers/developers/slotfills/plugin-sidebar.md +++ b/docs/designers-developers/developers/slotfills/plugin-sidebar.md @@ -28,8 +28,8 @@ registerPlugin( 'plugin-sidebar-test', { render: PluginSidebarTest } ); ### Closed State -![Closed State](/docs/designers-developers/assets/plugin-sidebar-closed-state.png?raw=true) +![Closed State](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/plugin-sidebar-closed-state.png?raw=true) ### Open State -![Open State](/docs/designers-developers//assets/plugin-sidebar-open-state.png?raw=true) +![Open State](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers//assets/plugin-sidebar-open-state.png?raw=true) From 8593fd62ddc88e3e7eb46e15ff462eca4d315f96 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Fri, 24 May 2019 06:31:56 +0200 Subject: [PATCH 176/664] Revert change to local Docker services introduced in 884b3f9e588246f40e13d01084655aed5564bd78 --- docker-compose-localdev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-localdev.yml b/docker-compose-localdev.yml index 502132973afbcd..4d58031eaa74be 100644 --- a/docker-compose-localdev.yml +++ b/docker-compose-localdev.yml @@ -2,7 +2,7 @@ version: '3.1' services: - wordpress_dev: + wordpress: volumes: - ./wordpress:/var/www/html From 77dd41be5a630f8a5a41c10f631c71c5bd1d0f8b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 24 May 2019 08:16:23 +0100 Subject: [PATCH 177/664] Add widget scripts to the block editor for widgets page. (#15787) This commit just loads the scripts/styles loaded in the widgets screen in the widget block editor page. These artifacts were already being loaded in the post editor and they are required to make sure legacy widgets work properly. --- lib/widgets.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/widgets.php b/lib/widgets.php index e35ccf5b0ef385..e32a78847f0872 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -5,12 +5,22 @@ * @package gutenberg */ +/** + * Checks if a screen containing the block editor is being loaded. + * + * @return boolean True if a screen containing the block editor is being loaded. + */ +function gutenberg_is_block_editor() { + $screen = get_current_screen(); + return $screen->is_block_editor() || 'gutenberg_page_gutenberg-widgets' === $screen->id; +} + /** * Emulates the Widgets screen `admin_print_styles` when at the block editor * screen. */ function gutenberg_block_editor_admin_print_styles() { - if ( get_current_screen()->is_block_editor() ) { + if ( gutenberg_is_block_editor() ) { /** This action is documented in wp-admin/admin-footer.php */ // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores do_action( 'admin_print_styles-widgets.php' ); @@ -23,7 +33,7 @@ function gutenberg_block_editor_admin_print_styles() { * screen. */ function gutenberg_block_editor_admin_print_scripts() { - if ( get_current_screen()->is_block_editor() ) { + if ( gutenberg_is_block_editor() ) { // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores do_action( 'admin_print_scripts-widgets.php' ); } @@ -35,7 +45,7 @@ function gutenberg_block_editor_admin_print_scripts() { * editor screen. */ function gutenberg_block_editor_admin_print_footer_scripts() { - if ( get_current_screen()->is_block_editor() ) { + if ( gutenberg_is_block_editor() ) { /** This action is documented in wp-admin/admin-footer.php */ // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores do_action( 'admin_print_footer_scripts-widgets.php' ); @@ -47,7 +57,7 @@ function gutenberg_block_editor_admin_print_footer_scripts() { * Emulates the Widgets screen `admin_footer` when at the block editor screen. */ function gutenberg_block_editor_admin_footer() { - if ( get_current_screen()->is_block_editor() ) { + if ( gutenberg_is_block_editor() ) { /** This action is documented in wp-admin/admin-footer.php */ // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores do_action( 'admin_footer-widgets.php' ); From 1d3b6321e5d56b0ff24bebd51ca18e537e0cbf0a Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 24 May 2019 08:43:55 +0100 Subject: [PATCH 178/664] Add blockEditor settings on the widget screen (#15788) The block editor settings are required to ensure legacy widgets work as expected on the widgets block editor screen. This commit is related to #15521 given that in both changes we add block editor settings support to the widget screen. --- lib/widgets-page.php | 31 ++++++++++++++++++- lib/widgets.php | 21 ++++++++++--- .../edit-widgets-initializer/index.js | 8 +++-- .../src/components/layout/index.js | 6 ++-- .../src/components/widget-area/index.js | 2 ++ .../src/components/widget-areas/index.js | 3 +- packages/edit-widgets/src/index.js | 9 ++++-- 7 files changed, 66 insertions(+), 14 deletions(-) diff --git a/lib/widgets-page.php b/lib/widgets-page.php index cc63e767cea90c..858866c02f1755 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -29,9 +29,38 @@ function gutenberg_widgets_init( $hook ) { return; } + // Media settings. + $max_upload_size = wp_max_upload_size(); + if ( ! $max_upload_size ) { + $max_upload_size = 0; + } + + $settings = array_merge( + array( + 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), + 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'maxUploadFileSize' => $max_upload_size, + ), + gutenberg_get_legacy_widget_settings() + ); + + list( $color_palette, ) = (array) get_theme_support( 'editor-color-palette' ); + list( $font_sizes, ) = (array) get_theme_support( 'editor-font-sizes' ); + + if ( false !== $color_palette ) { + $settings['colors'] = $color_palette; + } + + if ( false !== $font_sizes ) { + $settings['fontSizes'] = $font_sizes; + } + wp_add_inline_script( 'wp-edit-widgets', - 'wp.editWidgets.initialize( "widgets-editor" );' + sprintf( + 'wp.editWidgets.initialize( "widgets-editor", %s );', + wp_json_encode( $settings ) + ) ); // Preload server-registered block schemas. wp_add_inline_script( diff --git a/lib/widgets.php b/lib/widgets.php index e32a78847f0872..1bce902fe56245 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -65,14 +65,14 @@ function gutenberg_block_editor_admin_footer() { } add_action( 'admin_footer', 'gutenberg_block_editor_admin_footer' ); + /** - * Extends default editor settings with values supporting legacy widgets. - * - * @param array $settings Default editor settings. + * Returns the settings required by legacy widgets blocks. * - * @return array Filtered editor settings. + * @return array Legacy widget settings. */ -function gutenberg_legacy_widget_settings( $settings ) { +function gutenberg_get_legacy_widget_settings() { + $settings = array(); /** * TODO: The hardcoded array should be replaced with a mechanism to allow * core and third party blocks to specify they already have equivalent @@ -135,6 +135,17 @@ function gutenberg_legacy_widget_settings( $settings ) { return $settings; } + +/** + * Extends default editor settings with values supporting legacy widgets. + * + * @param array $settings Default editor settings. + * + * @return array Filtered editor settings. + */ +function gutenberg_legacy_widget_settings( $settings ) { + return array_merge( $settings, gutenberg_get_legacy_widget_settings() ); +} add_filter( 'block_editor_settings', 'gutenberg_legacy_widget_settings' ); /** diff --git a/packages/edit-widgets/src/components/edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/edit-widgets-initializer/index.js index 025488a0502991..41e15dc5b41102 100644 --- a/packages/edit-widgets/src/components/edit-widgets-initializer/index.js +++ b/packages/edit-widgets/src/components/edit-widgets-initializer/index.js @@ -10,11 +10,15 @@ import { withDispatch } from '@wordpress/data'; */ import Layout from '../layout'; -function EditWidgetsInitializer( { setupWidgetAreas } ) { +function EditWidgetsInitializer( { setupWidgetAreas, settings } ) { useEffect( () => { setupWidgetAreas(); }, [] ); - return <Layout />; + return ( + <Layout + blockEditorSettings={ settings } + /> + ); } export default compose( [ diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 06ed92455fc6e9..4a9a985e89e477 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -11,7 +11,7 @@ import Header from '../header'; import Sidebar from '../sidebar'; import WidgetAreas from '../widget-areas'; -function Layout() { +function Layout( { blockEditorSettings } ) { return ( <> <Header /> @@ -22,7 +22,9 @@ function Layout() { aria-label={ __( 'Widgets screen content' ) } tabIndex="-1" > - <WidgetAreas /> + <WidgetAreas + blockEditorSettings={ blockEditorSettings } + /> </div> </> ); diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 7914a4b6ea13ab..4825a45cefc6e4 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -10,6 +10,7 @@ import { import { withDispatch, withSelect } from '@wordpress/data'; function WidgetArea( { + blockEditorSettings, blocks, initialOpen, updateBlocks, @@ -25,6 +26,7 @@ function WidgetArea( { value={ blocks } onInput={ updateBlocks } onChange={ updateBlocks } + settings={ blockEditorSettings } > <BlockList /> </BlockEditorProvider> diff --git a/packages/edit-widgets/src/components/widget-areas/index.js b/packages/edit-widgets/src/components/widget-areas/index.js index 99a3f1e8689fa6..53eea964ec65ac 100644 --- a/packages/edit-widgets/src/components/widget-areas/index.js +++ b/packages/edit-widgets/src/components/widget-areas/index.js @@ -9,9 +9,10 @@ import { withSelect } from '@wordpress/data'; */ import WidgetArea from '../widget-area'; -function WidgetAreas( { areas } ) { +function WidgetAreas( { areas, blockEditorSettings } ) { return areas.map( ( { id }, index ) => ( <WidgetArea + blockEditorSettings={ blockEditorSettings } key={ id } id={ id } initialOpen={ index === 0 } diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index ede34168d9cb0c..c61d4c2f9bb781 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -13,12 +13,15 @@ import EditWidgetsInitializer from './components/edit-widgets-initializer'; /** * Initilizes the widgets screen * - * @param {string} id Id of the root element to render the screen. + * @param {string} id Id of the root element to render the screen. + * @param {Object} settings Id of the root element to render the screen. */ -export function initialize( id ) { +export function initialize( id, settings ) { registerCoreBlocks(); render( - <EditWidgetsInitializer />, + <EditWidgetsInitializer + settings={ settings } + />, document.getElementById( id ) ); } From 9b4f31aa92af80b234643166f395fd5a1f5cd998 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 24 May 2019 10:05:48 +0100 Subject: [PATCH 179/664] Render widget areas referencing blocks in a wp_area post on the website frontend (#15651) Here we implement a simple filter that makes a sidebar referencing a post_id reference a simple widget that renders the content of the post. --- ...-experimental-wp-widget-blocks-manager.php | 120 +++++++++++++++++- lib/class-wp-rest-widget-areas-controller.php | 2 +- lib/widgets.php | 2 + 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/lib/class-experimental-wp-widget-blocks-manager.php b/lib/class-experimental-wp-widget-blocks-manager.php index bb1ac9fa089f76..f2f26e5ef43728 100644 --- a/lib/class-experimental-wp-widget-blocks-manager.php +++ b/lib/class-experimental-wp-widget-blocks-manager.php @@ -13,6 +13,14 @@ * @since 5.7.0 */ class Experimental_WP_Widget_Blocks_Manager { + + /** + * Array of sidebar_widgets as it was before the filter swap_out_sidebars_blocks_for_block_widgets was ever executed. + * + * @var array + */ + private static $unfiltered_sidebar_widgets = null; + /** * Returns the $wp_registered_widgets global. * @@ -42,7 +50,7 @@ private static function get_wp_registered_sidebars() { * * @since 5.7.0 * - * @param string $sidebar_id Indentifier of the sidebar. + * @param string $sidebar_id Identifier of the sidebar. * @return array Sidebar structure. */ public static function get_wp_registered_sidebars_sidebar( $sidebar_id ) { @@ -50,6 +58,15 @@ public static function get_wp_registered_sidebars_sidebar( $sidebar_id ) { return $wp_registered_sidebars[ $sidebar_id ]; } + /** + * Returns the result of wp_get_sidebars_widgets without swap_out_sidebars_blocks_for_block_widgets filter being applied. + * + * @since 5.7.0 + */ + private static function get_raw_sidebar_widgets() { + return self::$unfiltered_sidebar_widgets; + } + /** * Returns a post id being referenced in a sidebar area. * @@ -59,7 +76,7 @@ public static function get_wp_registered_sidebars_sidebar( $sidebar_id ) { * @return integer Post id. */ public static function get_post_id_referenced_in_sidebar( $sidebar_id ) { - $sidebars = wp_get_sidebars_widgets(); + $sidebars = self::get_raw_sidebar_widgets(); $sidebar = $sidebars[ $sidebar_id ]; return is_numeric( $sidebar ) ? $sidebar : 0; } @@ -73,7 +90,7 @@ public static function get_post_id_referenced_in_sidebar( $sidebar_id ) { * @param integer $post_id Post id. */ public static function reference_post_id_in_sidebar( $sidebar_id, $post_id ) { - $sidebars = wp_get_sidebars_widgets(); + $sidebars = self::get_raw_sidebar_widgets(); $sidebar = $sidebars[ $sidebar_id ]; wp_set_sidebars_widgets( array_merge( @@ -100,7 +117,8 @@ public static function reference_post_id_in_sidebar( $sidebar_id, $post_id ) { public static function get_sidebar_as_blocks( $sidebar_id ) { $blocks = array(); - $sidebars_items = wp_get_sidebars_widgets(); + $sidebars_items = self::get_raw_sidebar_widgets(); + $wp_registered_sidebars = self::get_wp_registered_sidebars(); foreach ( $sidebars_items[ $sidebar_id ] as $item ) { $widget_class = self::get_widget_class( $item ); @@ -109,7 +127,7 @@ public static function get_sidebar_as_blocks( $sidebar_id ) { 'attrs' => array( 'class' => $widget_class, 'identifier' => $item, - 'instance' => self::get_sidebar_widget_instance( $sidebar, $item ), + 'instance' => self::get_sidebar_widget_instance( $wp_registered_sidebars[ $sidebar_id ], $item ), ), 'innerHTML' => '', ); @@ -245,6 +263,9 @@ public static function serialize_blocks( $blocks ) { * @return string String representing the block. */ public static function serialize_block( $block ) { + if ( ! isset( $block['blockName'] ) ) { + return false; + } $name = $block['blockName']; if ( 0 === strpos( $name, 'core/' ) ) { $name = substr( $name, strlen( 'core/' ) ); @@ -271,4 +292,93 @@ public static function serialize_block( $block ) { ); } } + + /** + * Outputs a block widget on the website frontend. + * + * @param array $options Widget options. + * @param array $arguments Arguments array. + */ + public static function output_blocks_widget( $options, $arguments ) { + echo $options['before_widget']; + foreach ( $arguments['blocks'] as $block ) { + echo render_block( $block ); + } + echo $options['after_widget']; + } + + /** + * Registers of a widget that should represent a set of blocks and returns its id. + * + * @param array $blocks Array of blocks. + */ + public static function convert_blocks_to_widget( $blocks ) { + $widget_id = 'blocks-widget-' . md5( self::serialize_blocks( $blocks ) ); + global $wp_registered_widgets; + if ( isset( $wp_registered_widgets[ $widget_id ] ) ) { + return $widget_id; + } + wp_register_sidebar_widget( + $widget_id, + __( 'Blocks Area ', 'gutenberg' ), + 'Experimental_WP_Widget_Blocks_Manager::output_blocks_widget', + array( + 'classname' => 'widget-area', + 'description' => __( 'Displays a set of blocks', 'gutenberg' ), + ), + array( + 'blocks' => $blocks, + ) + ); + return $widget_id; + } + + /** + * Filters the $sidebars_widgets to exchange wp_area post id with a widget that renders that block area. + * + * @param array $sidebars_widgets_input An associative array of sidebars and their widgets. + */ + public static function swap_out_sidebars_blocks_for_block_widgets( $sidebars_widgets_input ) { + global $sidebars_widgets; + if ( null === self::$unfiltered_sidebar_widgets ) { + self::$unfiltered_sidebar_widgets = $sidebars_widgets; + } + $filtered_sidebar_widgets = array(); + foreach ( $sidebars_widgets_input as $sidebar_id => $item ) { + if ( ! is_numeric( $item ) ) { + $filtered_sidebar_widgets[ $sidebar_id ] = $item; + continue; + } + + $filtered_widgets = array(); + $last_set_of_blocks = array(); + $post = get_post( $item ); + $blocks = parse_blocks( $post->post_content ); + + foreach ( $blocks as $block ) { + if ( ! isset( $block['blockName'] ) ) { + continue; + } + if ( + 'core/legacy-widget' === $block['blockName'] && + isset( $block['attrs']['identifier'] ) + ) { + if ( ! empty( $last_set_of_blocks ) ) { + $filtered_widgets[] = self::convert_blocks_to_widget( $last_set_of_blocks ); + $last_set_of_blocks = array(); + } + $filtered_widgets[] = $block['attrs']['identifier']; + } else { + $last_set_of_blocks[] = $block; + } + } + if ( ! empty( $last_set_of_blocks ) ) { + $filtered_widgets[] = self::convert_blocks_to_widget( $last_set_of_blocks ); + } + + $filtered_sidebar_widgets[ $sidebar_id ] = $filtered_widgets; + } + $sidebars_widgets = $filtered_sidebar_widgets; + return $filtered_sidebar_widgets; + } } diff --git a/lib/class-wp-rest-widget-areas-controller.php b/lib/class-wp-rest-widget-areas-controller.php index 6d333efa96c145..e191a50305c13d 100644 --- a/lib/class-wp-rest-widget-areas-controller.php +++ b/lib/class-wp-rest-widget-areas-controller.php @@ -49,7 +49,7 @@ public function register_routes() { 'description' => __( 'The sidebar’s ID.', 'gutenberg' ), 'type' => 'string', 'required' => true, - 'validate_callback' => array( $this, 'is_valid_sidabar_id' ), + 'validate_callback' => 'Experimental_WP_Widget_Blocks_Manager::is_valid_sidabar_id', ); $content_argument = array( diff --git a/lib/widgets.php b/lib/widgets.php index 1bce902fe56245..28ebc003674759 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -202,3 +202,5 @@ function gutenberg_create_wp_area_post_type() { ); } add_action( 'init', 'gutenberg_create_wp_area_post_type' ); + +add_filter( 'sidebars_widgets', 'Experimental_WP_Widget_Blocks_Manager::swap_out_sidebars_blocks_for_block_widgets' ); From 95f51b4bc28bfa26f0929886ea54cd0f65e613c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Fri, 24 May 2019 12:08:35 +0100 Subject: [PATCH 180/664] Port blockquote block to RN mobile (#15482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enable quote block in editor. * Add mobile version of quote edit. * Stop reading focus from context. * Make default tag to be div like on the web. * Implement block quote component in web and RN. * Simplify native block quote component. * Implement empty Alignment toolbar. * Fix style import. * Remove import * Rename blockquote to BlockQuotation. * Move BlockQuotation to be a primate component. * Add documentation reference for BlockQuotation. * Add BlockQuotation info to the changeling. * Update packages/components/CHANGELOG.md Co-Authored-By: Grzegorz (Greg) Ziółkowski <grzegorz@gziolo.pl> * Restore style file. --- docs/manifest-devhub.json | 6 ++++++ .../alignment-toolbar/index.native.js | 5 +++++ .../src/components/index.native.js | 1 + .../src/components/rich-text/index.native.js | 6 ++++++ packages/block-library/src/index.native.js | 1 + packages/block-library/src/quote/edit.js | 5 +++-- packages/components/CHANGELOG.md | 6 ++++++ .../src/primitives/block-quotation/README.md | 15 +++++++++++++++ .../src/primitives/block-quotation/index.js | 1 + .../block-quotation/index.native.js | 16 ++++++++++++++++ .../block-quotation/style.native.scss | 7 +++++++ .../src/primitives/block-quotation/style.scss | 19 +++++++++++++++++++ packages/components/src/primitives/index.js | 1 + packages/viewport/src/index.native.js | 1 + 14 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/block-editor/src/components/alignment-toolbar/index.native.js create mode 100644 packages/components/src/primitives/block-quotation/README.md create mode 100644 packages/components/src/primitives/block-quotation/index.js create mode 100644 packages/components/src/primitives/block-quotation/index.native.js create mode 100644 packages/components/src/primitives/block-quotation/style.native.scss create mode 100644 packages/components/src/primitives/block-quotation/style.scss diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index dfee8a25831c60..bfdf2e267fef62 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -833,6 +833,12 @@ "markdown_source": "../packages/components/src/popover/README.md", "parent": "components" }, + { + "title": "BlockQuotation", + "slug": "block-quotation", + "markdown_source": "../packages/components/src/primitives/block-quotation/README.md", + "parent": "components" + }, { "title": "HorizontalRule", "slug": "horizontal-rule", diff --git a/packages/block-editor/src/components/alignment-toolbar/index.native.js b/packages/block-editor/src/components/alignment-toolbar/index.native.js new file mode 100644 index 00000000000000..fdd842d0375141 --- /dev/null +++ b/packages/block-editor/src/components/alignment-toolbar/index.native.js @@ -0,0 +1,5 @@ +const AlignmentToolbar = () => { + return null; +}; + +export default AlignmentToolbar; diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index ae73aa0e4fb316..18e029c6a47229 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -4,6 +4,7 @@ export { default as BlockEdit } from './block-edit'; export { default as BlockFormatControls } from './block-format-controls'; export * from './colors'; export * from './font-sizes'; +export { default as AlignmentToolbar } from './alignment-toolbar'; export { default as InspectorControls } from './inspector-controls'; export { default as PlainText } from './plain-text'; export { diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 7431817743a6e9..9cff30df3ef13d 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -883,6 +883,7 @@ export class RichText extends Component { RichText.defaultProps = { formattingControls: [ 'bold', 'italic', 'link', 'strikethrough' ], format: 'string', + tagName: 'div', }; const RichTextContainer = compose( [ @@ -922,6 +923,11 @@ const RichTextContainer = compose( [ selectionStart.clientId === clientId && selectionStart.attributeKey === identifier ); + } else { + isSelected = isSelected && ( + selectionStart.clientId === clientId && + selectionStart.attributeKey === identifier + ); } return { diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index fbe7ad59bb3b2d..b3411411ee75fd 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -113,6 +113,7 @@ export const registerCoreBlocks = () => { nextpage, separator, list, + quote, ].forEach( ( { metadata, name, settings } ) => { registerBlockType( name, { ...metadata, diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 9c01310a3ade03..8955b78588bc3f 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -3,6 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/block-editor'; +import { BlockQuotation } from '@wordpress/components'; export default function QuoteEdit( { attributes, setAttributes, isSelected, mergeBlocks, onReplace, className } ) { const { align, value, citation } = attributes; @@ -16,7 +17,7 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg } } /> </BlockControls> - <blockquote className={ className } style={ { textAlign: align } }> + <BlockQuotation className={ className } style={ { textAlign: align } }> <RichText identifier="value" multiline @@ -54,7 +55,7 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg className="wp-block-quote__citation" /> ) } - </blockquote> + </BlockQuotation> </> ); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 729769e7a872c3..50165750d8a058 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Feature + +- Add new `BlockQuotation` block to the primitives folder to support blockquote in a multiplatform way. [#15482](https://github.com/WordPress/gutenberg/pull/15482). + ## 7.4.0 (2019-05-21) ### New Feature diff --git a/packages/components/src/primitives/block-quotation/README.md b/packages/components/src/primitives/block-quotation/README.md new file mode 100644 index 00000000000000..83d9f9057609ed --- /dev/null +++ b/packages/components/src/primitives/block-quotation/README.md @@ -0,0 +1,15 @@ +# HorizontalRule + +A drop-in replacement for the HTML [blockquote](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote) element that works both on the web and in the mobile apps. It indicates that the enclosed text is an extended quotation. + +## Usage + +```jsx +import { BlockQuotation } from '@wordpress/components'; + +const MyBlockQuotation = () => ( + <BlockQuotation> + ...Quote content + </BlockQuotation> +); +``` diff --git a/packages/components/src/primitives/block-quotation/index.js b/packages/components/src/primitives/block-quotation/index.js new file mode 100644 index 00000000000000..f1ccb4968c84e2 --- /dev/null +++ b/packages/components/src/primitives/block-quotation/index.js @@ -0,0 +1 @@ +export const BlockQuotation = 'blockquote'; diff --git a/packages/components/src/primitives/block-quotation/index.native.js b/packages/components/src/primitives/block-quotation/index.native.js new file mode 100644 index 00000000000000..eea86435b5ebcd --- /dev/null +++ b/packages/components/src/primitives/block-quotation/index.native.js @@ -0,0 +1,16 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +/** + * Internal dependencies + */ +import styles from './style.scss'; + +export const BlockQuotation = ( props ) => { + return ( + <View style={ styles.wpBlockQuote } > + { props.children } + </View> + ); +}; diff --git a/packages/components/src/primitives/block-quotation/style.native.scss b/packages/components/src/primitives/block-quotation/style.native.scss new file mode 100644 index 00000000000000..02d315e1560b6b --- /dev/null +++ b/packages/components/src/primitives/block-quotation/style.native.scss @@ -0,0 +1,7 @@ +.wpBlockQuote { + border-left-width: 4px; + border-left-color: $black; + border-left-style: solid; + padding-left: 8px; + margin-left: 8px; +} diff --git a/packages/components/src/primitives/block-quotation/style.scss b/packages/components/src/primitives/block-quotation/style.scss new file mode 100644 index 00000000000000..c1206d68958f7b --- /dev/null +++ b/packages/components/src/primitives/block-quotation/style.scss @@ -0,0 +1,19 @@ +.wp-block-quote { + &.is-style-large, + &.is-large { + margin: 0 0 16px; + padding: 0 1em; + + p { + font-size: 24px; + font-style: italic; + line-height: 1.6; + } + + cite, + footer { + font-size: 18px; + text-align: right; + } + } +} diff --git a/packages/components/src/primitives/index.js b/packages/components/src/primitives/index.js index 83d15090083940..ba5d2f6f2ab084 100644 --- a/packages/components/src/primitives/index.js +++ b/packages/components/src/primitives/index.js @@ -1,2 +1,3 @@ export * from './svg'; export * from './horizontal-rule'; +export * from './block-quotation'; diff --git a/packages/viewport/src/index.native.js b/packages/viewport/src/index.native.js index e69de29bb2d1d6..8b137891791fe9 100644 --- a/packages/viewport/src/index.native.js +++ b/packages/viewport/src/index.native.js @@ -0,0 +1 @@ + From 1d82e3822497e55149995c17a9bb902e5f7a3ebf Mon Sep 17 00:00:00 2001 From: mzorz <mariozorz@gmail.com> Date: Fri, 24 May 2019 08:52:58 -0300 Subject: [PATCH 181/664] add mobile Gutenberg team to contributors list :) (#15810) --- CONTRIBUTORS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cc8690aaeacebc..55b2954142873f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -131,3 +131,14 @@ This list is manually curated to include valuable contributions by volunteers th | @melchoyce | @melchoyce | | @sarahmonster | @tinkerbelly | | @kjellr | @kjellr | +| @etoledom | | +| @hypest | | +| @koke | | +| @pinarol | | +| @daniloercoli | | +| @marecar3 | | +| @Tug | | +| @diegoreymendez | | +| @mkevins | | +| @SergioEstevao | | +| @mzorz | @mzorz | From 5b1addd5a72106a206647efbdc005948bc4f4cb7 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 24 May 2019 04:54:07 -0700 Subject: [PATCH 182/664] Adjust my codeowner notifications (#15803) --- .github/CODEOWNERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 53702a4b85c26d..e606dc21e8074f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,5 +1,5 @@ # Documentation -/docs @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki +/docs @youknowriad @chrisvanpatten @ajitbohra @notnownikki /docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @notnownikki /docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki @@ -29,13 +29,13 @@ # Tooling /bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/docs/tool @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @nosolosw @notnownikki +/docs/tool @youknowriad @gziolo @chrisvanpatten @ajitbohra @nosolosw @notnownikki /packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/docgen @nosolosw @mkaz @gziolo +/packages/docgen @nosolosw @gziolo /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan /packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @@ -45,7 +45,7 @@ /packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw @mkaz +/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw # UI Components /packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @chrisvanpatten From 636b7bf072a4365d5c41fec07872a81fe0247516 Mon Sep 17 00:00:00 2001 From: andrei draganescu <andrei.draganescu@automattic.com> Date: Fri, 24 May 2019 15:31:47 +0300 Subject: [PATCH 183/664] proxied getEditorSettings in block-editor (#15807) * proxied getEditorSettings in block-editor * just do what Riad says * correct getSettings * added to the readme the new property in code editor default settings * docs auto generated --- packages/block-editor/README.md | 1 + .../src/components/block-settings-menu/block-mode-toggle.js | 4 ++-- packages/block-editor/src/store/defaults.js | 1 + packages/editor/src/components/provider/index.js | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index e2dcd26cd5095d..072bec27f51b9e 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -369,6 +369,7 @@ The default editor settings isRTL boolean Whether the editor is in RTL mode bodyPlaceholder string Empty post placeholder titlePlaceholder string Empty title placeholder + codeEditingEnabled string Whether or not the user can switch to the code editor <a name="SkipToSelectedBlock" href="#SkipToSelectedBlock">#</a> **SkipToSelectedBlock** diff --git a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js index 99de4ddf40e445..d36ab6974e2760 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js +++ b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js @@ -34,9 +34,9 @@ export function BlockModeToggle( { blockType, mode, onToggleMode, small = false, export default compose( [ withSelect( ( select, { clientId } ) => { - const { getBlock, getBlockMode } = select( 'core/block-editor' ); + const { getBlock, getBlockMode, getSettings } = select( 'core/block-editor' ); const block = getBlock( clientId ); - const isCodeEditingEnabled = select( 'core/editor' ).getEditorSettings().codeEditingEnabled; + const isCodeEditingEnabled = getSettings().codeEditingEnabled; return { mode: getBlockMode( clientId ), diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 14c114216f12a9..49cf289b58e0d6 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -26,6 +26,7 @@ export const PREFERENCES_DEFAULTS = { * isRTL boolean Whether the editor is in RTL mode * bodyPlaceholder string Empty post placeholder * titlePlaceholder string Empty title placeholder + * codeEditingEnabled string Whether or not the user can switch to the code editor */ export const SETTINGS_DEFAULTS = { alignWide: false, diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 31dbc721bf822f..2a4dd5ccbf26f5 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -78,6 +78,7 @@ class EditorProvider extends Component { 'allowedBlockTypes', 'availableLegacyWidgets', 'bodyPlaceholder', + 'codeEditingEnabled', 'colors', 'disableCustomColors', 'disableCustomFontSizes', From e81f19154eaa159819fd8bd852d0fb38f64b1170 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 24 May 2019 17:20:59 +0100 Subject: [PATCH 184/664] Bootstrap an automation tool to handle releases (#15699) --- bin/build-plugin-zip.sh | 66 ++--- bin/commander.js | 293 ++++++++++++++++++++ package-lock.json | 600 ++++++++++++++++++++++++++++++++++++---- package.json | 6 + 4 files changed, 879 insertions(+), 86 deletions(-) create mode 100755 bin/commander.js diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index 7e76a2032228ca..bcd431a3c62ac0 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -28,40 +28,42 @@ warning () { status "💃 Time to release Gutenberg 🕺" -# Make sure there are no changes in the working tree. Release builds should be -# traceable to a particular commit and reliably reproducible. (This is not -# totally true at the moment because we download nightly vendor scripts). -changed= -if ! git diff --exit-code > /dev/null; then - changed="file(s) modified" -elif ! git diff --cached --exit-code > /dev/null; then - changed="file(s) staged" -fi -if [ ! -z "$changed" ]; then - git status - error "ERROR: Cannot build plugin zip with dirty working tree. ☝️ - Commit your changes and try again." - exit 1 -fi - -# Do a dry run of the repository reset. Prompting the user for a list of all -# files that will be removed should prevent them from losing important files! -status "Resetting the repository to pristine condition. ✨" -to_clean=$(git clean -xdf --dry-run) -if [ ! -z "$to_clean" ]; then - echo $to_clean - warning "🚨 About to delete everything above! Is this okay? 🚨" - echo -n "[y]es/[N]o: " - read answer - if [ "$answer" != "${answer#[Yy]}" ]; then - # Remove ignored files to reset repository to pristine condition. Previous - # test ensures that changed files abort the plugin build. - status "Cleaning working directory... 🛀" - git clean -xdf - else - error "Fair enough; aborting. Tidy up your repo and try again. 🙂" +if [ -z "$NO_CHECKS" ]; then + # Make sure there are no changes in the working tree. Release builds should be + # traceable to a particular commit and reliably reproducible. (This is not + # totally true at the moment because we download nightly vendor scripts). + changed= + if ! git diff --exit-code > /dev/null; then + changed="file(s) modified" + elif ! git diff --cached --exit-code > /dev/null; then + changed="file(s) staged" + fi + if [ ! -z "$changed" ]; then + git status + error "ERROR: Cannot build plugin zip with dirty working tree. ☝️ + Commit your changes and try again." exit 1 fi + + # Do a dry run of the repository reset. Prompting the user for a list of all + # files that will be removed should prevent them from losing important files! + status "Resetting the repository to pristine condition. ✨" + to_clean=$(git clean -xdf --dry-run) + if [ ! -z "$to_clean" ]; then + echo $to_clean + warning "🚨 About to delete everything above! Is this okay? 🚨" + echo -n "[y]es/[N]o: " + read answer + if [ "$answer" != "${answer#[Yy]}" ]; then + # Remove ignored files to reset repository to pristine condition. Previous + # test ensures that changed files abort the plugin build. + status "Cleaning working directory... 🛀" + git clean -xdf + else + error "Fair enough; aborting. Tidy up your repo and try again. 🙂" + exit 1 + fi + fi fi # Download all vendor scripts diff --git a/bin/commander.js b/bin/commander.js new file mode 100755 index 00000000000000..087dcdf0540ac6 --- /dev/null +++ b/bin/commander.js @@ -0,0 +1,293 @@ +#!/usr/bin/env node + +/* eslint-disable no-console */ + +// Dependencies +const path = require( 'path' ); +const program = require( 'commander' ); +const inquirer = require( 'inquirer' ); +const semver = require( 'semver' ); +const chalk = require( 'chalk' ); +const fs = require( 'fs-extra' ); +const SimpleGit = require( 'simple-git/promise' ); +const childProcess = require( 'child_process' ); +const Octokit = require( '@octokit/rest' ); +const os = require( 'os' ); +const uuid = require( 'uuid/v4' ); + +// Common info +const workingDirectoryPath = path.join( os.tmpdir(), uuid() ); +const packageJsonPath = workingDirectoryPath + '/package.json'; +const packageLockPath = workingDirectoryPath + '/package-lock.json'; +const pluginFilePath = workingDirectoryPath + '/gutenberg.php'; +const gutenbergZipPath = workingDirectoryPath + '/gutenberg.zip'; +const repoOwner = 'WordPress'; +const repoURL = 'git@github.com:' + repoOwner + '/gutenberg.git'; + +// UI +const error = chalk.bold.red; +const warning = chalk.bold.keyword( 'orange' ); +const success = chalk.bold.green; + +// Utils + +/** + * Asks the user for a confirmation to continue or abort otherwise + * + * @param {string} message Confirmation message. + * @param {boolean} isDefault Default reply. + * @param {string} abortMessage Abort message. + */ +async function askForConfirmationToContinue( message, isDefault = true, abortMessage = 'Aborting.' ) { + const { isReady } = await inquirer.prompt( [ { + type: 'confirm', + name: 'isReady', + default: isDefault, + message, + } ] ); + + if ( ! isReady ) { + console.log( error( '\n' + abortMessage ) ); + process.exit( 1 ); + } +} + +/** + * Common logic wrapping a step in the process. + * + * @param {string} name Step name. + * @param {string} abortMessage Abort message. + * @param {function} handler Step logic. + */ +async function runStep( name, abortMessage, handler ) { + try { + await handler(); + } catch ( exception ) { + console.log( + error( 'The following error happened during the "' + warning( name ) + '" step:' ) + '\n\n', + exception, + error( '\n\n' + abortMessage ) + ); + + process.exit( 1 ); + } +} + +/** + * Command used to release an RC version of the plugin + */ +async function releasePluginRC() { + console.log( + chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), + 'Welcome! This tool is going to help you release a new RC version of the Gutenberg Plugin.\n', + 'It goes throught different steps : creating the release branch, bumping the plugin version, tagging and creating the github release, building the zip...\n', + 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' + ); + + // This is a variable that contains the abort message shown when the script is aborted. + let abortMessage = 'Aborting!'; + await askForConfirmationToContinue( 'Ready to go? ' ); + + // Cloning the repository + await runStep( 'Cloning the repository', abortMessage, async () => { + console.log( '>> Cloning the repository' ); + const simpleGit = SimpleGit(); + await simpleGit.clone( repoURL, workingDirectoryPath, [ '--depth=1' ] ); + console.log( '>> The gutenberg repository has been successfully cloned in the following temporary folder: ' + success( workingDirectoryPath ) ); + } ); + + // Creating the release branch + const simpleGit = SimpleGit( workingDirectoryPath ); + let nextVersion; + let nextVersionLabel; + let releaseBranch; + const packageJson = require( packageJsonPath ); + const packageLock = require( packageLockPath ); + await runStep( 'Creating the release branch', abortMessage, async () => { + const parsedVersion = semver.parse( packageJson.version ); + + // Follow the WordPress version guidelines to compute the version to be used + // By default, increase the "minor" number but if we reach 9, bump to the next major. + if ( parsedVersion.minor === 9 ) { + nextVersion = ( parsedVersion.major + 1 ) + '.0.0-rc.1'; + releaseBranch = 'release/' + ( parsedVersion.major + 1 ) + '.0'; + nextVersionLabel = ( parsedVersion.major + 1 ) + '.0.0 RC1'; + } else { + nextVersion = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0-rc.1'; + releaseBranch = 'release/' + parsedVersion.major + '.' + ( parsedVersion.minor + 1 ); + nextVersionLabel = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0 RC1'; + } + await askForConfirmationToContinue( + 'The RC Version to be applied is ' + success( nextVersion ) + '. Proceed with the creation of the release branch?', + true, + abortMessage + ); + + // Creating the release branch + await simpleGit.checkoutLocalBranch( releaseBranch ); + console.log( '>> The local release branch ' + success( releaseBranch ) + ' has been successfully created.' ); + } ); + + // Bumping the version in the different files (package.json, package-lock.json, gutenberg.php) + let commitHash; + await runStep( 'Updating the plugin version', abortMessage, async () => { + const newPackageJson = { + ...packageJson, + version: nextVersion, + }; + fs.writeFileSync( packageJsonPath, JSON.stringify( newPackageJson, null, '\t' ) + '\n' ); + const newPackageLock = { + ...packageLock, + version: nextVersion, + }; + fs.writeFileSync( packageLockPath, JSON.stringify( newPackageLock, null, '\t' ) + '\n' ); + const content = fs.readFileSync( pluginFilePath, 'utf8' ); + fs.writeFileSync( pluginFilePath, content.replace( ' * Version: ' + packageJson.version, ' * Version: ' + nextVersion ) ); + console.log( '>> The plugin version has been updated successfully.' ); + + // Commit the version bump + await askForConfirmationToContinue( + 'Please check the diff. Proceed with the version bump commit?', + true, + abortMessage + ); + await simpleGit.add( [ + packageJsonPath, + packageLockPath, + pluginFilePath, + ] ); + const commitData = await simpleGit.commit( 'Bump plugin version to ' + nextVersion ); + commitHash = commitData.commit; + console.log( '>> The plugin version bump has been commited succesfully.' ); + } ); + + // Plugin ZIP creation + await runStep( 'Plugin ZIP creation', abortMessage, async () => { + await askForConfirmationToContinue( + 'Proceed and build the plugin zip? (It takes a few minutes)', + true, + abortMessage + ); + childProcess.execSync( '/bin/bash bin/build-plugin-zip.sh', { + cwd: workingDirectoryPath, + env: { + NO_CHECKS: true, + PATH: process.env.PATH, + }, + stdio: [ 'inherit', 'ignore', 'inherit' ], + } ); + + console.log( '>> The plugin zip has been built succesfully. Path: ' + success( gutenbergZipPath ) ); + } ); + + // Creating the git tag + await runStep( 'Creating the git tag', abortMessage, async () => { + await askForConfirmationToContinue( + 'Proceed with the creation of the git tag?', + true, + abortMessage + ); + await simpleGit.addTag( 'v' + nextVersion ); + console.log( '>> The ' + success( 'v' + nextVersion ) + ' tag has been created succesfully.' ); + } ); + + await runStep( 'Pushing the release branch and the tag', abortMessage, async () => { + await askForConfirmationToContinue( + 'The release branch and the tag are going to be pushed to the remote repository. Continue?', + true, + abortMessage + ); + await simpleGit.push( 'origin', releaseBranch ); + await simpleGit.pushTags( 'origin' ); + } ); + abortMessage = 'Aborting! Make sure to remove remote release branch and tag.'; + + // Creating the GitHub Release + let octokit; + let release; + await runStep( 'Creating the GitHub release', abortMessage, async () => { + await askForConfirmationToContinue( + 'Proceed with the creation of the GitHub release?', + true, + abortMessage + ); + const { changelog } = await inquirer.prompt( [ { + type: 'editor', + name: 'changelog', + message: 'Please provide the CHANGELOG of the release (markdown)', + } ] ); + + const { token } = await inquirer.prompt( [ { + type: 'input', + name: 'token', + message: 'Please provide a GitHub personal authentication token. Navigate to ' + success( 'https://github.com/settings/tokens/new?scopes=repo,admin:org,write:packages' ) + ' to create one.', + } ] ); + + octokit = new Octokit( { + auth: token, + } ); + + const releaseData = await octokit.repos.createRelease( { + owner: repoOwner, + repo: 'gutenberg', + tag_name: 'v' + nextVersion, + name: nextVersionLabel, + body: changelog, + prerelease: true, + } ); + release = releaseData.data; + + console.log( '>> The GitHub release has been created succesfully.' ); + } ); + abortMessage = 'Aborting! Make sure to remove the remote release branch, the git tag and the GitHub release.'; + + // Uploading the Gutenberg Zip to the release + await runStep( 'Uploading the plugin zip', abortMessage, async () => { + const filestats = fs.statSync( gutenbergZipPath ); + await octokit.repos.uploadReleaseAsset( { + url: release.upload_url, + headers: { + 'content-length': filestats.size, + 'content-type': 'application/zip', + }, + name: 'gutenberg.zip', + file: fs.createReadStream( gutenbergZipPath ), + } ); + console.log( '>> The plugin zip has been succesfully uploaded.' ); + } ); + abortMessage = 'Aborting! Make sure to manually cherry-pick the ' + success( commitHash ) + ' commit to the master branch.'; + + // Cherry-picking the bump commit into master + await runStep( 'Cherry-picking the bump commit into master', abortMessage, async () => { + await askForConfirmationToContinue( + 'The plugin RC is now released. Proceed with the version bump in the master branch?', + true, + abortMessage + ); + await simpleGit.fetch(); + await simpleGit.checkout( 'master' ); + await simpleGit.reset( 'hard' ); + await simpleGit.pull( 'origin', 'master' ); + await simpleGit.raw( [ 'cherry-pick', commitHash ] ); + await simpleGit.push( 'origin', 'master' ); + } ); + + abortMessage = 'Aborting! The release is finished though.'; + await runStep( 'Cleaning the temporary folder', abortMessage, async () => { + await fs.remove( workingDirectoryPath ); + } ); + + console.log( + '\n>> 🎉 The Gutenberg ' + success( nextVersionLabel ) + ' has been successfully released.\n', + 'You can access the Github release here: ' + success( release.html_url ) + '\n', + 'Thanks for performing the release!' + ); +} + +program + .command( 'release-plugin-rc' ) + .alias( 'rc' ) + .description( 'Release an RC version of the plugin (supports only rc.1 for now)' ) + .action( releasePluginRC ); + +program.parse( process.argv ); diff --git a/package-lock.json b/package-lock.json index 2b1826f94d9290..4b62a1b7616bed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, @@ -840,6 +846,12 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.127.tgz", "integrity": "sha512-1o25iFRf/dbgauTWalEzmD1EmRN3a2CzP/K7UVpYLEBduk96LF0FyUdCcf4Ry2mAWJ1VxyblFjC93q6qlLwA2A==", "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, @@ -1211,6 +1223,14 @@ "p-map": "^1.2.0", "pacote": "^9.5.0", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@lerna/batch-packages": { @@ -1254,6 +1274,14 @@ "p-waterfall": "^1.0.0", "read-package-tree": "^5.1.6", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@lerna/changed": { @@ -1301,6 +1329,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -1384,6 +1420,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -1586,6 +1630,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -1642,6 +1694,17 @@ "semver": "^5.5.0" }, "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -1666,6 +1729,12 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, @@ -1701,12 +1770,29 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, "whatwg-url": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", @@ -1729,6 +1815,19 @@ "cmd-shim": "^2.0.2", "fs-extra": "^7.0.0", "npmlog": "^4.1.2" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/describe-ref": { @@ -1815,6 +1914,17 @@ "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -1874,6 +1984,14 @@ "requires": { "@lerna/child-process": "3.13.0", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@lerna/import": { @@ -1890,6 +2008,19 @@ "dedent": "^0.7.0", "fs-extra": "^7.0.0", "p-map-series": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/init": { @@ -1903,6 +2034,19 @@ "fs-extra": "^7.0.0", "p-map": "^1.2.0", "write-json-file": "^2.3.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/link": { @@ -1996,6 +2140,19 @@ "npmlog": "^4.1.2", "signal-exit": "^3.0.2", "write-pkg": "^3.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/npm-publish": { @@ -2014,6 +2171,17 @@ "read-package-json": "^2.0.13" }, "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -2143,6 +2311,14 @@ "@lerna/validation-error": "3.13.0", "npm-package-arg": "^6.1.0", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@lerna/project": { @@ -2283,6 +2459,25 @@ "p-reduce": "^1.0.0", "pacote": "^9.5.0", "semver": "^5.5.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@lerna/pulse-till-done": { @@ -2303,6 +2498,19 @@ "fs-extra": "^7.0.0", "npmlog": "^4.1.2", "read-cmd-shim": "^1.0.1" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/rimraf-dir": { @@ -2366,6 +2574,19 @@ "@lerna/package": "3.13.0", "fs-extra": "^7.0.0", "p-map": "^1.2.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/symlink-dependencies": { @@ -2381,6 +2602,19 @@ "p-finally": "^1.0.0", "p-map": "^1.2.0", "p-map-series": "^1.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "@lerna/timer": { @@ -2426,6 +2660,14 @@ "semver": "^5.5.0", "slash": "^1.0.0", "temp-write": "^3.4.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@lerna/write-log-file": { @@ -2455,14 +2697,14 @@ "dev": true }, "@octokit/endpoint": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-4.0.0.tgz", - "integrity": "sha512-b8sptNUekjREtCTJFpOfSIL4SKh65WaakcyxWzRcSPOk5RxkZJ/S8884NGZFxZ+jCB2rDURU66pSHn14cVgWVg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.1.2.tgz", + "integrity": "sha512-bBGGmcRFq1x0jrB29G/9KjYmO3cdHfk3476B2JOHRvLsNw1Pn3l+ZvbiqtcO9qAS4Ti+zFedLB84ziHZRZclQA==", "dev": true, "requires": { "deepmerge": "3.2.0", - "is-plain-object": "^2.0.4", - "universal-user-agent": "^2.0.1", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^2.1.0", "url-template": "^2.0.8" }, "dependencies": { @@ -2471,6 +2713,21 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.0.tgz", "integrity": "sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow==", "dev": true + }, + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true } } }, @@ -2481,38 +2738,65 @@ "dev": true }, "@octokit/request": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-3.0.0.tgz", - "integrity": "sha512-DZqmbm66tq+a9FtcKrn0sjrUpi0UaZ9QPUCxxyk/4CJ2rseTMpAWRf6gCwOSUCzZcx/4XVIsDk+kz5BVdaeenA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-4.1.0.tgz", + "integrity": "sha512-RvpQAba4i+BNH0z8i0gPRc1ShlHidj4puQjI/Tno6s+Q3/Mzb0XRSHJiOhpeFrZ22V7Mwjq1E7QS27P5CgpWYA==", "dev": true, "requires": { - "@octokit/endpoint": "^4.0.0", - "deprecation": "^1.0.1", - "is-plain-object": "^2.0.4", + "@octokit/endpoint": "^5.1.0", + "@octokit/request-error": "^1.0.1", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", "node-fetch": "^2.3.0", "once": "^1.4.0", - "universal-user-agent": "^2.0.1" + "universal-user-agent": "^2.1.0" }, "dependencies": { + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + }, "node-fetch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", - "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "dev": true } } }, + "@octokit/request-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.0.2.tgz", + "integrity": "sha512-T9swMS/Vc4QlfWrvyeSyp/GjhXtYaBzCcibjGywV4k4D2qVrQKfEMPy8OxMDEj7zkIIdpHwqdpVbKCvnUPqkXw==", + "dev": true, + "requires": { + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, "@octokit/rest": { - "version": "16.24.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.24.1.tgz", - "integrity": "sha512-V2GVL+cfuwNTcZ9qtBMOR9pIftWo1AiZIiGvWNmTcIQG5mkj83ZXC+g3w5g0cVXt7Hi+mSOrD2bZ7HJOuouUNg==", + "version": "16.26.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.26.0.tgz", + "integrity": "sha512-NBpzre44ZAQWZhlH+zUYTgqI0pHN+c9rNj4d+pCydGEiKTGc1HKmoTghEUyr9GxazDyoAvmpx9nL0I7QS1Olvg==", "dev": true, "requires": { - "@octokit/request": "3.0.0", + "@octokit/request": "^4.0.1", + "@octokit/request-error": "^1.0.2", "atob-lite": "^2.0.0", "before-after-hook": "^1.4.0", "btoa-lite": "^1.0.0", - "deprecation": "^1.0.1", + "deprecation": "^2.0.0", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "lodash.uniq": "^4.5.0", @@ -4833,6 +5117,14 @@ "dev": true, "requires": { "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } } } @@ -5133,6 +5425,12 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, @@ -6109,9 +6407,9 @@ "dev": true }, "commander": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", - "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", "dev": true }, "commondir": { @@ -6504,6 +6802,12 @@ "read-pkg": "^3.0.0" } }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -7666,9 +7970,9 @@ "dev": true }, "deprecation": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-1.0.1.tgz", - "integrity": "sha512-ccVHpE72+tcIKaGMql33x5MAjKQIZrk+3x2GbJ7TeraUCZWHoT+KSZpoC+JQFsUBlSTXUrBaGiF0j6zVTepPLg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.0.0.tgz", + "integrity": "sha512-lbQN037mB3VfA2JFuguM5GCJ+zPinMeCrFe+AfSZ6eqrnJA/Fs+EYMnd6Nb2mn9lf2jO9xwEd9o9lic+D4vkcw==", "dev": true }, "des.js": { @@ -9411,9 +9715,9 @@ "dev": true }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.0.1.tgz", + "integrity": "sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -10440,6 +10744,12 @@ "read-pkg": "^3.0.0" } }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -11122,42 +11432,76 @@ "semver": "2.x || 3.x || 4 || 5", "validate-npm-package-license": "^3.0.1", "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "inquirer": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", - "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", + "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^3.0.0", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.10", + "lodash": "^4.17.11", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^6.1.0", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", + "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^4.0.0" + "ansi-regex": "^4.1.0" } } } @@ -11731,6 +12075,14 @@ "@babel/types": "^7.0.0", "istanbul-lib-coverage": "^2.0.3", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } } } @@ -11763,6 +12115,14 @@ "@babel/types": "^7.0.0", "istanbul-lib-coverage": "^2.0.3", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "istanbul-lib-report": { @@ -11865,6 +12225,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -12096,6 +12464,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -12598,6 +12974,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -12814,6 +13198,14 @@ "natural-compare": "^1.4.0", "pretty-format": "^24.7.0", "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "jest-util": { @@ -13395,6 +13787,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -14578,6 +14978,14 @@ "railroad-diagrams": "^1.0.0", "randexp": "0.4.6", "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "negotiator": { @@ -14719,6 +15127,14 @@ "semver": "^5.5.0", "shellwords": "^0.1.1", "which": "^1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "node-releases": { @@ -14728,6 +15144,14 @@ "dev": true, "requires": { "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "node-sass": { @@ -15029,6 +15453,14 @@ "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "normalize-path": { @@ -15098,6 +15530,14 @@ "osenv": "^0.1.5", "semver": "^5.5.0", "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "npm-package-json-lint": { @@ -15212,6 +15652,14 @@ "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.0.0", "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "npm-registry-fetch": { @@ -16148,6 +16596,12 @@ "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", "dev": true }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -16746,6 +17200,14 @@ "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "@babel/generator": { @@ -18178,6 +18640,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -18796,6 +19266,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -19050,9 +19528,9 @@ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", "dev": true }, "semver-compare": { @@ -19258,9 +19736,9 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "simple-git": { - "version": "1.110.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.110.0.tgz", - "integrity": "sha512-UYY0rQkknk0P5eb+KW+03F4TevZ9ou0H+LoGaj7iiVgpnZH4wdj/HTViy/1tNNkmIPcmtxuBqXWiYt2YwlRKOQ==", + "version": "1.113.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.113.0.tgz", + "integrity": "sha512-i9WVsrK2u0G/cASI9nh7voxOk9mhanWY9eGtWBDSYql6m49Yk5/Fan6uZsDr/xmzv8n+eQ8ahKCoEr8cvU3h+g==", "dev": true, "requires": { "debug": "^4.0.1" @@ -21391,9 +21869,9 @@ } }, "universal-user-agent": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.3.tgz", - "integrity": "sha512-eRHEHhChCBHrZsA4WEhdgiOKgdvgrMIHwnwnqD0r5C6AO8kwKcG7qSku3iXdhvHL3YvsS9ZkSGN8h/hIpoFC8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.1.0.tgz", + "integrity": "sha512-8itiX7G05Tu3mGDTdNY2fB4KJ8MgZLS54RdG6PkkfwMAavrXu1mV/lls/GABx9O3Rw4PnTtasxrvbMQoBYY92Q==", "dev": true, "requires": { "os-name": "^3.0.0" @@ -21896,6 +22374,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "execa": { @@ -22219,6 +22705,12 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, diff --git a/package.json b/package.json index f43c1e4cf08645..36d46c8d9b3169 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@babel/plugin-syntax-jsx": "7.2.0", "@babel/runtime-corejs3": "7.4.4", "@babel/traverse": "7.4.4", + "@octokit/rest": "16.26.0", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", "@wordpress/babel-plugin-makepot": "file:packages/babel-plugin-makepot", "@wordpress/babel-preset-default": "file:packages/babel-preset-default", @@ -83,6 +84,7 @@ "benchmark": "2.1.4", "browserslist": "4.4.1", "chalk": "2.4.1", + "commander": "2.20.0", "concurrently": "3.5.0", "copy-webpack-plugin": "4.5.2", "core-js": "3.0.1", @@ -95,8 +97,10 @@ "eslint-plugin-jest": "21.5.0", "espree": "4.0.0", "fbjs": "0.8.17", + "fs-extra": "8.0.1", "glob": "7.1.2", "husky": "0.14.3", + "inquirer": "6.3.1", "is-equal-shallow": "0.1.3", "jsdom": "11.12.0", "lerna": "3.13.2", @@ -116,9 +120,11 @@ "rimraf": "2.6.2", "rtlcss": "2.4.0", "sass-loader": "6.0.7", + "semver": "6.0.0", "shallow-equal": "1.0.0", "shallow-equals": "1.0.0", "shallowequal": "1.1.0", + "simple-git": "1.113.0", "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", From 0e46e9ef92d27aa4af849c482c81216b9c579410 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 24 May 2019 17:43:56 +0100 Subject: [PATCH 185/664] Remove background color from heading (#15814) After some discussions, it was decided that for now only the text color option should be available. --- packages/block-library/src/heading/block.json | 6 --- packages/block-library/src/heading/edit.js | 49 +------------------ packages/block-library/src/heading/save.js | 6 --- packages/block-library/src/heading/style.scss | 10 ---- packages/block-library/src/style.scss | 1 - 5 files changed, 2 insertions(+), 70 deletions(-) delete mode 100644 packages/block-library/src/heading/style.scss diff --git a/packages/block-library/src/heading/block.json b/packages/block-library/src/heading/block.json index 5582c4bb402635..2c47b2c7096521 100644 --- a/packages/block-library/src/heading/block.json +++ b/packages/block-library/src/heading/block.json @@ -23,12 +23,6 @@ }, "customTextColor": { "type": "string" - }, - "backgroundColor": { - "type": "string" - }, - "customBackgroundColor": { - "type": "string" } } } diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 378078824fc4d9..e8a396a147df57 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -12,7 +12,7 @@ import HeadingToolbar from './heading-toolbar'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { PanelBody, withFallbackStyles } from '@wordpress/components'; +import { PanelBody } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { @@ -22,59 +22,26 @@ import { RichText, withColors, PanelColorSettings, - ContrastChecker, } from '@wordpress/block-editor'; import { memo } from '@wordpress/element'; -const { getComputedStyle } = window; -const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { - const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes; - const editableNode = node.querySelector( '[contenteditable="true"]' ); - //verify if editableNode is available, before using getComputedStyle. - const computedStyles = editableNode ? getComputedStyle( editableNode ) : null; - return { - fallbackBackgroundColor: backgroundColor || ! computedStyles ? undefined : computedStyles.backgroundColor, - fallbackTextColor: textColor || ! computedStyles ? undefined : computedStyles.color, - fallbackFontSize: fontSize || customFontSize || ! computedStyles ? undefined : parseInt( computedStyles.fontSize ) || undefined, - }; -} ); - const HeadingColorUI = memo( function( { - backgroundColorValue, - setBackgroundColor, textColorValue, setTextColor, - fallbackTextColor, - fallbackBackgroundColor, } ) { return ( <PanelColorSettings title={ __( 'Color Settings' ) } initialOpen={ false } colorSettings={ [ - { - value: backgroundColorValue, - onChange: setBackgroundColor, - label: __( 'Background Color' ), - }, { value: textColorValue, onChange: setTextColor, label: __( 'Text Color' ), }, ] } - > - <ContrastChecker - { ...{ - textColor: textColorValue, - backgroundColor: backgroundColorValue, - fallbackTextColor, - fallbackBackgroundColor, - } } - isLargeText - /> - </PanelColorSettings> + /> ); } ); @@ -85,12 +52,8 @@ function HeadingEdit( { mergeBlocks, onReplace, className, - backgroundColor, textColor, - setBackgroundColor, setTextColor, - fallbackBackgroundColor, - fallbackTextColor, } ) { const { align, content, level, placeholder } = attributes; const tagName = 'h' + level; @@ -113,10 +76,6 @@ function HeadingEdit( { /> </PanelBody> <HeadingColorUI - backgroundColorValue={ backgroundColor.color } - fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } - setBackgroundColor={ setBackgroundColor } setTextColor={ setTextColor } textColorValue={ textColor.color } /> @@ -141,14 +100,11 @@ function HeadingEdit( { onReplace={ onReplace } onRemove={ () => onReplace( [] ) } className={ classnames( className, { - 'has-background': backgroundColor.color, 'has-text-color': textColor.color, - [ backgroundColor.class ]: backgroundColor.class, [ textColor.class ]: textColor.class, } ) } placeholder={ placeholder || __( 'Write heading…' ) } style={ { - backgroundColor: backgroundColor.color, color: textColor.color, textAlign: align, } } @@ -159,5 +115,4 @@ function HeadingEdit( { export default compose( [ withColors( 'backgroundColor', { textColor: 'color' } ), - applyFallbackStyles, ] )( HeadingEdit ); diff --git a/packages/block-library/src/heading/save.js b/packages/block-library/src/heading/save.js index 84c0f33463bccc..f554ff815a121a 100644 --- a/packages/block-library/src/heading/save.js +++ b/packages/block-library/src/heading/save.js @@ -14,8 +14,6 @@ import { export default function save( { attributes } ) { const { align, - backgroundColor, - customBackgroundColor, level, content, textColor, @@ -24,12 +22,9 @@ export default function save( { attributes } ) { const tagName = 'h' + level; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); const className = classnames( { - 'has-background': backgroundColor || customBackgroundColor, [ textClass ]: textClass, - [ backgroundClass ]: backgroundClass, } ); return ( @@ -38,7 +33,6 @@ export default function save( { attributes } ) { tagName={ tagName } style={ { textAlign: align, - backgroundColor: backgroundClass ? undefined : customBackgroundColor, color: textClass ? undefined : customTextColor, } } value={ content } diff --git a/packages/block-library/src/heading/style.scss b/packages/block-library/src/heading/style.scss deleted file mode 100644 index 31a6989c39c817..00000000000000 --- a/packages/block-library/src/heading/style.scss +++ /dev/null @@ -1,10 +0,0 @@ -h1, -h2, -h3, -h4, -h5, -h6 { - &.has-background { - padding: $block-bg-padding--v $block-bg-padding--h; - } -} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index e0bce8b7fd4d91..73325d5d9167c1 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -9,7 +9,6 @@ @import "./embed/style.scss"; @import "./file/style.scss"; @import "./gallery/style.scss"; -@import "./heading/style.scss"; @import "./image/style.scss"; @import "./latest-comments/style.scss"; @import "./latest-posts/style.scss"; From a8fb376fad486d0eecd8d44358e46ed67755e2f3 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Fri, 24 May 2019 13:17:03 -0700 Subject: [PATCH 186/664] Update block attributes for clarification (#15818) * Update block attributes for clarification Edit introduction to attributes for clarification. Move up the object keys section since it was lost in the hpq paragraph, also the keys are specified first, so better to mention them first. Add example for using class as a selector * Additional edits per review --- .../developers/block-api/block-attributes.md | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/designers-developers/developers/block-api/block-attributes.md index fd61866f5286c4..203113b06f5f97 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/designers-developers/developers/block-api/block-attributes.md @@ -2,13 +2,18 @@ ## Common Sources -Attribute sources are used to define the strategy by which block attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block. +Attribute sources are used to define how the block attribute values are extracted from saved post content. They provide a mechanism to map from the saved markup to a JavaScript representation of a block. If no attribute source is specified, the attribute will be saved to (and read from) the block's [comment delimiter](/docs/designers-developers/key-concepts.md#delimiters-and-parsing-expression-grammar). -Each source accepts an optional selector as the first argument. If a selector is specified, the source behavior will be run against the corresponding element(s) contained within the block. Otherwise it will be run against the block's root node. +The keys specified in the attributes source object are named as you see fit. The result of the attribute source definition is assigned as a value to each key. + +If no selector argument is specified, the source definition runs against the block's root node. If a selector argument is specified, it will run against the specified element(s) contained within the block. + +The selector specified can be an HTML tag, or anything queryable such as a class or id attribute, see examples below. + +Under the hood, attribute sources are a superset of the functionality provided by [hpq](https://github.com/aduth/hpq), a small library used to parse and query HTML markup into an object shape. -Under the hood, attribute sources are a superset of functionality provided by [hpq](https://github.com/aduth/hpq), a small library used to parse and query HTML markup into an object shape. In an object of attributes sources, you can name the keys as you see fit. The resulting object will assign as a value to each key the result of its attribute source. ### `attribute` @@ -43,6 +48,19 @@ Use `text` to extract the inner text from markup. // { "content": "The inner text of the figcaption element" } ``` +Another example, using `text` as the source, and using `.my-content` class as the selector to extract text: + +```js +{ + content: { + type: 'string', + source: 'text', + selector: '.my-content', + } +} +// { "content": "The inner text of .my-content class" } +``` + ### `html` Use `html` to extract the inner HTML from markup. From 7269a37bcf111b1d9106f99c5cb5242a00f208ec Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Sun, 26 May 2019 23:22:19 +0200 Subject: [PATCH 187/664] [RNMobile] UI for invalid block content (#15789) * Imported BlockInvalidWarning component * Importing Warning component to be used on BlockInvalidWarning * Adding message and title to Warning component * Fix Warning component's icon color * Fix lint issues * Improved accessibility label for BlockInvalidWarning * Fix lint issues * Warning component: Style touches --- .../block-invalid-warning.native.js | 27 ++++++++++++ .../src/components/index.native.js | 1 + .../src/components/warning/index.native.js | 42 +++++++++++++++++++ .../src/components/warning/style.native.scss | 31 ++++++++++++++ .../src/primitives/svg/style.native.scss | 5 +++ 5 files changed, 106 insertions(+) create mode 100644 packages/block-editor/src/components/block-list/block-invalid-warning.native.js create mode 100644 packages/block-editor/src/components/warning/index.native.js create mode 100644 packages/block-editor/src/components/warning/style.native.scss diff --git a/packages/block-editor/src/components/block-list/block-invalid-warning.native.js b/packages/block-editor/src/components/block-list/block-invalid-warning.native.js new file mode 100644 index 00000000000000..38a03da3cf5eeb --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-invalid-warning.native.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Warning from '../warning'; + +export default function BlockInvalidWarning( { blockTitle, icon } ) { + const accessibilityLabel = sprintf( + /* translators: accessibility text for blocks with invalid content. %d: localized block title */ + __( '%s block. This block has invalid content' ), + blockTitle + ); + + return ( + <Warning + title={ blockTitle } + message={ __( 'Problem displaying block' ) } + icon={ icon } + accessible={ true } + accessibilityLabel={ accessibilityLabel } + /> + ); +} diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 18e029c6a47229..df605eb54da3f2 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -16,6 +16,7 @@ export { export { default as MediaPlaceholder } from './media-placeholder'; export { default as MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from './media-upload'; export { default as URLInput } from './url-input'; +export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; // Content Related Components export { default as DefaultBlockAppender } from './default-block-appender'; diff --git a/packages/block-editor/src/components/warning/index.native.js b/packages/block-editor/src/components/warning/index.native.js new file mode 100644 index 00000000000000..7d0cced3408cca --- /dev/null +++ b/packages/block-editor/src/components/warning/index.native.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { View, Text } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; +import { normalizeIconObject } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +function Warning( { title, message, icon, iconClass, ...viewProps } ) { + icon = icon && normalizeIconObject( icon ); + return ( + <View + style={ styles.container } + { ...viewProps } + > + { icon && ( + <View style={ styles.icon }> + <Icon + className={ iconClass || 'warning-icon' } + icon={ icon && icon.src ? icon.src : icon } + /> + </View> + ) } + { title && ( + <Text style={ styles.title }>{ title }</Text> + ) } + { message && ( + <Text style={ styles.message }>{ message }</Text> + ) } + </View> + ); +} + +export default Warning; diff --git a/packages/block-editor/src/components/warning/style.native.scss b/packages/block-editor/src/components/warning/style.native.scss new file mode 100644 index 00000000000000..a96045ada1fd55 --- /dev/null +++ b/packages/block-editor/src/components/warning/style.native.scss @@ -0,0 +1,31 @@ +/** @format */ + +.container { + background-color: $gray-lighten-30; + padding-top: 24; + padding-bottom: 24; + padding-left: 8; + padding-right: 8; + align-items: center; + justify-content: center; +} + +.icon { + width: 24px; + height: 24px; + margin-bottom: 4; + fill: $gray-dark; +} + +.title { + text-align: center; + margin-bottom: 6; + font-size: 14; + color: $gray-dark; +} + +.message { + text-align: center; + color: $gray-text-min; + font-size: 12; +} diff --git a/packages/components/src/primitives/svg/style.native.scss b/packages/components/src/primitives/svg/style.native.scss index 83a99c47bd35a3..e5a64ea140d0d8 100644 --- a/packages/components/src/primitives/svg/style.native.scss +++ b/packages/components/src/primitives/svg/style.native.scss @@ -17,3 +17,8 @@ color: $gray-dark; fill: currentColor; } + +.warning-icon { + color: $gray-dark; + fill: currentColor; +} From c464e9f7e1a3a20032b4aeb977ab7e14202ad6cf Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 27 May 2019 02:41:28 -0400 Subject: [PATCH 188/664] Framework: Bump Lerna to v3.14.1 (#15825) --- package-lock.json | 786 ++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 453 insertions(+), 335 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b62a1b7616bed..4f1b924def079f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1208,14 +1208,14 @@ } }, "@lerna/add": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.13.1.tgz", - "integrity": "sha512-cXk42YbuhzEnADCK8Qte5laC9Qo03eJLVnr0qKY85jQUM/T4URe3IIUemqpg0CpVATrB+Vz+iNdeqw9ng1iALw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.14.0.tgz", + "integrity": "sha512-Sa79Ju6HqF3heSVpBiYPNrGtuS56U/jMzVq4CcVvhNwB34USLrzJJncGFVcfnuUvsjKeFJv+jHxUycHeRE8XYw==", "dev": true, "requires": { - "@lerna/bootstrap": "3.13.1", - "@lerna/command": "3.13.1", - "@lerna/filter-options": "3.13.0", + "@lerna/bootstrap": "3.14.0", + "@lerna/command": "3.14.0", + "@lerna/filter-options": "3.14.0", "@lerna/npm-conf": "3.13.0", "@lerna/validation-error": "3.13.0", "dedent": "^0.7.0", @@ -1234,34 +1234,33 @@ } }, "@lerna/batch-packages": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.13.0.tgz", - "integrity": "sha512-TgLBTZ7ZlqilGnzJ3xh1KdAHcySfHytgNRTdG9YomfriTU6kVfp1HrXxKJYVGs7ClPUNt2CTFEOkw0tMBronjw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.14.0.tgz", + "integrity": "sha512-RlBkQVNTqk1qvn6PFWiWNiskllUHh6tXbTVm43mZRNd+vhAyvrQC8RWJxH0ECVvnFAt9rSNGRIVbEJ31WnNQLg==", "dev": true, "requires": { - "@lerna/package-graph": "3.13.0", - "@lerna/validation-error": "3.13.0", + "@lerna/package-graph": "3.14.0", "npmlog": "^4.1.2" } }, "@lerna/bootstrap": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.13.1.tgz", - "integrity": "sha512-mKdi5Ds5f82PZwEFyB9/W60I3iELobi1i87sTeVrbJh/um7GvqpSPy7kG/JPxyOdMpB2njX6LiJgw+7b6BEPWw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.14.0.tgz", + "integrity": "sha512-AvnuDp8b0kX4zZgqD3v7ItPABhUsN5CmTEvZBD2JqM+xkQKhzCfz5ABcHEwDwOPWnNQmtH+/2iQdwaD7xBcAXw==", "dev": true, "requires": { - "@lerna/batch-packages": "3.13.0", - "@lerna/command": "3.13.1", - "@lerna/filter-options": "3.13.0", - "@lerna/has-npm-version": "3.13.0", - "@lerna/npm-install": "3.13.0", - "@lerna/package-graph": "3.13.0", + "@lerna/batch-packages": "3.14.0", + "@lerna/command": "3.14.0", + "@lerna/filter-options": "3.14.0", + "@lerna/has-npm-version": "3.13.3", + "@lerna/npm-install": "3.13.3", + "@lerna/package-graph": "3.14.0", "@lerna/pulse-till-done": "3.13.0", - "@lerna/rimraf-dir": "3.13.0", - "@lerna/run-lifecycle": "3.13.0", + "@lerna/rimraf-dir": "3.13.3", + "@lerna/run-lifecycle": "3.14.0", "@lerna/run-parallel-batches": "3.13.0", - "@lerna/symlink-binary": "3.13.0", - "@lerna/symlink-dependencies": "3.13.0", + "@lerna/symlink-binary": "3.14.0", + "@lerna/symlink-dependencies": "3.14.0", "@lerna/validation-error": "3.13.0", "dedent": "^0.7.0", "get-port": "^3.2.0", @@ -1285,32 +1284,33 @@ } }, "@lerna/changed": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.13.2.tgz", - "integrity": "sha512-mcmkxUMR0J4ZyRyVUrdDJl4ZsdHDgdA1xQcbdB4LZvAE/E2lNlPcEfAfbfs08VnRiqvFOqcczbzBq10hvSFg4w==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.14.1.tgz", + "integrity": "sha512-G0RgYL/WLTFzbezRBLUO2J0v39EvgZIO5bHHUtYt7zUFSfzapkPfvpdpBj+5JlMtf0B2xfxwTk+lSA4LVnbfmA==", "dev": true, "requires": { - "@lerna/collect-updates": "3.13.0", - "@lerna/command": "3.13.1", - "@lerna/listable": "3.13.0", + "@lerna/collect-updates": "3.14.0", + "@lerna/command": "3.14.0", + "@lerna/listable": "3.14.0", "@lerna/output": "3.13.0", - "@lerna/version": "3.13.2" + "@lerna/version": "3.14.1" } }, "@lerna/check-working-tree": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.13.0.tgz", - "integrity": "sha512-dsdO15NXX5To+Q53SYeCrBEpiqv4m5VkaPZxbGQZNwoRen1MloXuqxSymJANQn+ZLEqarv5V56gydebeROPH5A==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.14.1.tgz", + "integrity": "sha512-ae/sdZPNh4SS+6c4UDuWP/QKbtIFAn/TvKsPncA1Jdo0PqXLBlug4DzkHTGkvZ5F0nj+0JrSxYteInakJV99vg==", "dev": true, "requires": { - "@lerna/describe-ref": "3.13.0", + "@lerna/collect-uncommitted": "3.14.1", + "@lerna/describe-ref": "3.13.3", "@lerna/validation-error": "3.13.0" } }, "@lerna/child-process": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.13.0.tgz", - "integrity": "sha512-0iDS8y2jiEucD4fJHEzKoc8aQJgm7s+hG+0RmDNtfT0MM3n17pZnf5JOMtS1FJp+SEXOjMKQndyyaDIPFsnp6A==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.13.3.tgz", + "integrity": "sha512-3/e2uCLnbU+bydDnDwyadpOmuzazS01EcnOleAnuj9235CU2U97DH6OyoG1EW/fU59x11J+HjIqovh5vBaMQjQ==", "dev": true, "requires": { "chalk": "^2.3.1", @@ -1329,14 +1329,6 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } } }, "execa": { @@ -1372,20 +1364,26 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, "@lerna/clean": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.13.1.tgz", - "integrity": "sha512-myGIaXv7RUO2qCFZXvx8SJeI+eN6y9SUD5zZ4/LvNogbOiEIlujC5lUAqK65rAHayQ9ltSa/yK6Xv510xhZXZQ==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.14.0.tgz", + "integrity": "sha512-wEuAqOS9VMqh2C20KD63IySzyEnyVDqDI3LUsXw+ByUf9AJDgEHv0TCOxbDjDYaaQw1tjSBNZMyYInNeoASwhA==", "dev": true, "requires": { - "@lerna/command": "3.13.1", - "@lerna/filter-options": "3.13.0", + "@lerna/command": "3.14.0", + "@lerna/filter-options": "3.14.0", "@lerna/prompt": "3.13.0", "@lerna/pulse-till-done": "3.13.0", - "@lerna/rimraf-dir": "3.13.0", + "@lerna/rimraf-dir": "3.13.3", "p-map": "^1.2.0", "p-map-series": "^1.0.0", "p-waterfall": "^1.0.0" @@ -1420,14 +1418,6 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } } }, "execa": { @@ -1556,6 +1546,12 @@ "once": "^1.3.1" } }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -1588,27 +1584,39 @@ } } }, + "@lerna/collect-uncommitted": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-3.14.1.tgz", + "integrity": "sha512-hQ67S+nlSJwsPylXbWlrQSZUcWa8tTNIdcMd9OY4+QxdJlZUG7CLbWSyaxi0g11WdoRJHT163mr9xQyAvIVT1A==", + "dev": true, + "requires": { + "@lerna/child-process": "3.13.3", + "chalk": "^2.3.1", + "figgy-pudding": "^3.5.1", + "npmlog": "^4.1.2" + } + }, "@lerna/collect-updates": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.13.0.tgz", - "integrity": "sha512-uR3u6uTzrS1p46tHQ/mlHog/nRJGBqskTHYYJbgirujxm6FqNh7Do+I1Q/7zSee407G4lzsNxZdm8IL927HemQ==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.14.0.tgz", + "integrity": "sha512-siRHo2atAwj5KpKVOo6QTVIYDYbNs7dzTG6ow9VcFMLKX5shuaEyFA22Z3LmnxQ3sakVFdgvvVeediEz6cM3VA==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", - "@lerna/describe-ref": "3.13.0", + "@lerna/child-process": "3.13.3", + "@lerna/describe-ref": "3.13.3", "minimatch": "^3.0.4", "npmlog": "^4.1.2", "slash": "^1.0.0" } }, "@lerna/command": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.13.1.tgz", - "integrity": "sha512-SYWezxX+iheWvzRoHCrbs8v5zHPaxAx3kWvZhqi70vuGsdOVAWmaG4IvHLn11ztS+Vpd5PM+ztBWSbnykpLFKQ==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.14.0.tgz", + "integrity": "sha512-PtFi5EtXB2VuSruoLsjfZdus56d7oKlZAI4iSRoaS/BBxE2Wyfn7//vW7Ow4hZCzuqb9tBcpDq+4u2pdXN1d2Q==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", - "@lerna/package-graph": "3.13.0", + "@lerna/child-process": "3.13.3", + "@lerna/package-graph": "3.14.0", "@lerna/project": "3.13.1", "@lerna/validation-error": "3.13.0", "@lerna/write-log-file": "3.13.0", @@ -1630,14 +1638,6 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } } }, "execa": { @@ -1673,13 +1673,19 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, "@lerna/conventional-commits": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.13.0.tgz", - "integrity": "sha512-BeAgcNXuocmLhPxnmKU2Vy8YkPd/Uo+vu2i/p3JGsUldzrPC8iF3IDxH7fuXpEFN2Nfogu7KHachd4tchtOppA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.14.0.tgz", + "integrity": "sha512-hGZ2qQZ9uEGf2eeIiIpEodSs9Qkkf/2uYEtNT7QN1RYISPUh6/lKGBssc5dpbCF64aEuxmemWLdlDf1ogG6++w==", "dev": true, "requires": { "@lerna/validation-error": "3.13.0", @@ -1739,13 +1745,13 @@ } }, "@lerna/create": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.13.1.tgz", - "integrity": "sha512-pLENMXgTkQuvKxAopjKeoLOv9fVUCnpTUD7aLrY5d95/1xqSZlnsOcQfUYcpMf3GpOvHc8ILmI5OXkPqjAf54g==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.14.0.tgz", + "integrity": "sha512-J4PeGnzVBOSV7Cih8Uhv9xIauljR9bGcfSDN9aMzFtJhSX0xFXNvmnpXRORp7xNHV2lbxk7mNxRQxzR9CQRMuw==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", - "@lerna/command": "3.13.1", + "@lerna/child-process": "3.13.3", + "@lerna/command": "3.14.0", "@lerna/npm-conf": "3.13.0", "@lerna/validation-error": "3.13.0", "camelcase": "^5.0.0", @@ -1807,9 +1813,9 @@ } }, "@lerna/create-symlink": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.13.0.tgz", - "integrity": "sha512-PTvg3jAAJSAtLFoZDsuTMv1wTOC3XYIdtg54k7uxIHsP8Ztpt+vlilY/Cni0THAqEMHvfiToel76Xdta4TU21Q==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.14.0.tgz", + "integrity": "sha512-Kw51HYOOi6UfCKncqkgEU1k/SYueSBXgkNL91FR8HAZH7EPSRTEtp9mnJo568g0+Hog5C+3cOaWySwhHpRG29A==", "dev": true, "requires": { "cmd-shim": "^2.0.2", @@ -1831,48 +1837,48 @@ } }, "@lerna/describe-ref": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.13.0.tgz", - "integrity": "sha512-UJefF5mLxLae9I2Sbz5RLYGbqbikRuMqdgTam0MS5OhXnyuuKYBUpwBshCURNb1dPBXTQhSwc7+oUhORx8ojCg==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.13.3.tgz", + "integrity": "sha512-5KcLTvjdS4gU5evW8ESbZ0BF44NM5HrP3dQNtWnOUSKJRgsES8Gj0lq9AlB2+YglZfjEftFT03uOYOxnKto4Uw==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", + "@lerna/child-process": "3.13.3", "npmlog": "^4.1.2" } }, "@lerna/diff": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.13.1.tgz", - "integrity": "sha512-cKqmpONO57mdvxtp8e+l5+tjtmF04+7E+O0QEcLcNUAjC6UR2OSM77nwRCXDukou/1h72JtWs0jjcdYLwAmApg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.14.0.tgz", + "integrity": "sha512-H6FSj0jOiQ6unVCwOK6ReT5uZN6ZIn/j/cx4YwuOtU3SMcs3UfuQRIFNeKg/tKmOcQGd39Mn9zDhmt3TAYGROA==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", - "@lerna/command": "3.13.1", + "@lerna/child-process": "3.13.3", + "@lerna/command": "3.14.0", "@lerna/validation-error": "3.13.0", "npmlog": "^4.1.2" } }, "@lerna/exec": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.13.1.tgz", - "integrity": "sha512-I34wEP9lrAqqM7tTXLDxv/6454WFzrnXDWpNDbiKQiZs6SIrOOjmm6I4FiQsx+rU3o9d+HkC6tcUJRN5mlJUgA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.14.0.tgz", + "integrity": "sha512-cNFO8hWsBVLeqVQ7LsQ4rYKbbQ2eN+Ne+hWKTlUQoyRbYzgJ22TXhjKR6IMr68q0xtclcDlasfcNO+XEWESh0g==", "dev": true, "requires": { - "@lerna/batch-packages": "3.13.0", - "@lerna/child-process": "3.13.0", - "@lerna/command": "3.13.1", - "@lerna/filter-options": "3.13.0", - "@lerna/run-parallel-batches": "3.13.0", - "@lerna/validation-error": "3.13.0" + "@lerna/child-process": "3.13.3", + "@lerna/command": "3.14.0", + "@lerna/filter-options": "3.14.0", + "@lerna/run-topologically": "3.14.0", + "@lerna/validation-error": "3.13.0", + "p-map": "^1.2.0" } }, "@lerna/filter-options": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.13.0.tgz", - "integrity": "sha512-SRp7DCo9zrf+7NkQxZMkeyO1GRN6GICoB9UcBAbXhLbWisT37Cx5/6+jh49gYB63d/0/WYHSEPMlheUrpv1Srw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.14.0.tgz", + "integrity": "sha512-ZmNZK9m8evxHc+2ZnDyCm8XFIKVDKpIASG1wtizr3R14t49fuYE7nR+rm4t82u9oSSmER8gb8bGzh0SKZme/jg==", "dev": true, "requires": { - "@lerna/collect-updates": "3.13.0", + "@lerna/collect-updates": "3.14.0", "@lerna/filter-packages": "3.13.0", "dedent": "^0.7.0" } @@ -1958,12 +1964,12 @@ } }, "@lerna/github-client": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.13.1.tgz", - "integrity": "sha512-iPLUp8FFoAKGURksYEYZzfuo9TRA+NepVlseRXFaWlmy36dCQN20AciINpoXiXGoHcEUHXUKHQvY3ARFdMlf3w==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.13.3.tgz", + "integrity": "sha512-fcJkjab4kX0zcLLSa/DCUNvU3v8wmy2c1lhdIbL7s7gABmDcV0QZq93LhnEee3VkC9UpnJ6GKG4EkD7eIifBnA==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", + "@lerna/child-process": "3.13.3", "@octokit/plugin-enterprise-rest": "^2.1.1", "@octokit/rest": "^16.16.0", "git-url-parse": "^11.1.2", @@ -1977,12 +1983,12 @@ "dev": true }, "@lerna/has-npm-version": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.13.0.tgz", - "integrity": "sha512-Oqu7DGLnrMENPm+bPFGOHnqxK8lCnuYr6bk3g/CoNn8/U0qgFvHcq6Iv8/Z04TsvleX+3/RgauSD2kMfRmbypg==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.13.3.tgz", + "integrity": "sha512-mQzoghRw4dBg0R9FFfHrj0TH0glvXyzdEZmYZ8Isvx5BSuEEwpsryoywuZSdppcvLu8o7NAdU5Tac8cJ/mT52w==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", + "@lerna/child-process": "3.13.3", "semver": "^5.5.0" }, "dependencies": { @@ -1995,13 +2001,13 @@ } }, "@lerna/import": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.13.1.tgz", - "integrity": "sha512-A1Vk1siYx1XkRl6w+zkaA0iptV5TIynVlHPR9S7NY0XAfhykjztYVvwtxarlh6+VcNrO9We6if0+FXCrfDEoIg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.14.0.tgz", + "integrity": "sha512-j8z/m85FX1QYPgl5TzMNupdxsQF/NFZSmdCR19HQzqiVKC8ULGzF30WJEk66+KeZ94wYMSakINtYD+41s34pNQ==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", - "@lerna/command": "3.13.1", + "@lerna/child-process": "3.13.3", + "@lerna/command": "3.14.0", "@lerna/prompt": "3.13.0", "@lerna/pulse-till-done": "3.13.0", "@lerna/validation-error": "3.13.0", @@ -2024,13 +2030,13 @@ } }, "@lerna/init": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.13.1.tgz", - "integrity": "sha512-M59WACqim8WkH5FQEGOCEZ89NDxCKBfFTx4ZD5ig3LkGyJ8RdcJq5KEfpW/aESuRE9JrZLzVr0IjKbZSxzwEMA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.14.0.tgz", + "integrity": "sha512-X3PQkQZds5ozA1xiarmVzAK6LPLNK3bBu24Api0w2KJXO7Ccs9ob/VcGdevZuzqdJo1Xg2H6oBhEqIClU9Uqqw==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", - "@lerna/command": "3.13.1", + "@lerna/child-process": "3.13.3", + "@lerna/command": "3.14.0", "fs-extra": "^7.0.0", "p-map": "^1.2.0", "write-json-file": "^2.3.0" @@ -2050,37 +2056,37 @@ } }, "@lerna/link": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.13.1.tgz", - "integrity": "sha512-N3h3Fj1dcea+1RaAoAdy4g2m3fvU7m89HoUn5X/Zcw5n2kPoK8kTO+NfhNAatfRV8VtMXst8vbNrWQQtfm0FFw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.14.0.tgz", + "integrity": "sha512-xlwQhWTVOZrgAuoONY3/OIBWehDfZXmf5qFhnOy7lIxByRhEX5Vwx0ApaGxHTv3Flv7T+oI4s8UZVq5F6dT8Aw==", "dev": true, "requires": { - "@lerna/command": "3.13.1", - "@lerna/package-graph": "3.13.0", - "@lerna/symlink-dependencies": "3.13.0", + "@lerna/command": "3.14.0", + "@lerna/package-graph": "3.14.0", + "@lerna/symlink-dependencies": "3.14.0", "p-map": "^1.2.0", "slash": "^1.0.0" } }, "@lerna/list": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.13.1.tgz", - "integrity": "sha512-635iRbdgd9gNvYLLIbYdQCQLr+HioM5FGJLFS0g3DPGygr6iDR8KS47hzCRGH91LU9NcM1mD1RoT/AChF+QbiA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.14.0.tgz", + "integrity": "sha512-Gp+9gaIkBfXBwc9Ng0Y74IEfAqpQpLiXwOP4IOpdINxOeDpllhMaYP6SzLaMvrfSyHRayM7Cq5/PRnHkXQ5uuQ==", "dev": true, "requires": { - "@lerna/command": "3.13.1", - "@lerna/filter-options": "3.13.0", - "@lerna/listable": "3.13.0", + "@lerna/command": "3.14.0", + "@lerna/filter-options": "3.14.0", + "@lerna/listable": "3.14.0", "@lerna/output": "3.13.0" } }, "@lerna/listable": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.13.0.tgz", - "integrity": "sha512-liYJ/WBUYP4N4MnSVZuLUgfa/jy3BZ02/1Om7xUY09xGVSuNVNEeB8uZUMSC+nHqFHIsMPZ8QK9HnmZb1E/eTA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.14.0.tgz", + "integrity": "sha512-ZK44Mo8xf/N97eQZ236SPSq0ek6+gk4HqHIx05foEMZVV1iIDH4a/nblLsJNjGQVsIdMYFPaqNJ0z+ZQfiJazQ==", "dev": true, "requires": { - "@lerna/batch-packages": "3.13.0", + "@lerna/query-graph": "3.14.0", "chalk": "^2.3.1", "columnify": "^1.5.4" } @@ -2116,11 +2122,12 @@ } }, "@lerna/npm-dist-tag": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.13.0.tgz", - "integrity": "sha512-mcuhw34JhSRFrbPn0vedbvgBTvveG52bR2lVE3M3tfE8gmR/cKS/EJFO4AUhfRKGCTFn9rjaSEzlFGYV87pemQ==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.14.0.tgz", + "integrity": "sha512-DEyYEdufTGIC6E4RTJUsYPgqlz1Bs/XPeEQ5fd+ojWnICevj7dRrr2DfHucPiUCADlm2jbAraAQc3QPU0dXRhw==", "dev": true, "requires": { + "@lerna/otplease": "3.14.0", "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.1.0", "npm-registry-fetch": "^3.9.0", @@ -2128,12 +2135,12 @@ } }, "@lerna/npm-install": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.13.0.tgz", - "integrity": "sha512-qNyfts//isYQxore6fsPorNYJmPVKZ6tOThSH97tP0aV91zGMtrYRqlAoUnDwDdAjHPYEM16hNujg2wRmsqqIw==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.13.3.tgz", + "integrity": "sha512-7Jig9MLpwAfcsdQ5UeanAjndChUjiTjTp50zJ+UZz4CbIBIDhoBehvNMTCL2G6pOEC7sGEg6sAqJINAqred6Tg==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", + "@lerna/child-process": "3.13.3", "@lerna/get-npm-exec-opts": "3.13.0", "fs-extra": "^7.0.0", "npm-package-arg": "^6.1.0", @@ -2156,12 +2163,13 @@ } }, "@lerna/npm-publish": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.13.2.tgz", - "integrity": "sha512-HMucPyEYZfom5tRJL4GsKBRi47yvSS2ynMXYxL3kO0ie+j9J7cb0Ir8NmaAMEd3uJWJVFCPuQarehyfTDZsSxg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.14.0.tgz", + "integrity": "sha512-ShG0qEnGkWxtjQvIRATgm/CzeoVaSyyoNRag5t8gDSR/r2u9ux72oROKQUEaE8OwcTS4rL2cyBECts8XMNmyYw==", "dev": true, "requires": { - "@lerna/run-lifecycle": "3.13.0", + "@lerna/otplease": "3.14.0", + "@lerna/run-lifecycle": "3.14.0", "figgy-pudding": "^3.5.1", "fs-extra": "^7.0.0", "libnpmpublish": "^1.1.1", @@ -2191,16 +2199,26 @@ } }, "@lerna/npm-run-script": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.13.0.tgz", - "integrity": "sha512-hiL3/VeVp+NFatBjkGN8mUdX24EfZx9rQlSie0CMgtjc7iZrtd0jCguLomSCRHYjJuvqgbp+LLYo7nHVykfkaQ==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.13.3.tgz", + "integrity": "sha512-qR4o9BFt5hI8Od5/DqLalOJydnKpiQFEeN0h9xZi7MwzuX1Ukwh3X22vqsX4YRbipIelSFtrDzleNVUm5jj0ow==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", + "@lerna/child-process": "3.13.3", "@lerna/get-npm-exec-opts": "3.13.0", "npmlog": "^4.1.2" } }, + "@lerna/otplease": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/otplease/-/otplease-3.14.0.tgz", + "integrity": "sha512-rYAWzaYZ81bwnrmTkYWGgcc13bl/6DlG7pjWQWNGAJNLzO5zzj0xmXN5sMFJnNvDpSiS/ZS1sIuPvb4xnwLUkg==", + "dev": true, + "requires": { + "@lerna/prompt": "3.13.0", + "figgy-pudding": "^3.5.1" + } + }, "@lerna/output": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.13.0.tgz", @@ -2211,14 +2229,14 @@ } }, "@lerna/pack-directory": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.13.1.tgz", - "integrity": "sha512-kXnyqrkQbCIZOf1054N88+8h0ItC7tUN5v9ca/aWpx298gsURpxUx/1TIKqijL5TOnHMyIkj0YJmnH/PyBVLKA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.14.0.tgz", + "integrity": "sha512-E9PmC1oWYjYN8Z0Oeoj7X98NruMg/pcdDiRxnwJ5awnB0d/kyfoquHXCYwCQQFCnWUfto7m5lM4CSostcolEVQ==", "dev": true, "requires": { "@lerna/get-packed": "3.13.0", "@lerna/package": "3.13.0", - "@lerna/run-lifecycle": "3.13.0", + "@lerna/run-lifecycle": "3.14.0", "figgy-pudding": "^3.5.1", "npm-packlist": "^1.4.1", "npmlog": "^4.1.2", @@ -2303,13 +2321,32 @@ } }, "@lerna/package-graph": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.13.0.tgz", - "integrity": "sha512-3mRF1zuqFE1HEFmMMAIggXy+f+9cvHhW/jzaPEVyrPNLKsyfJQtpTNzeI04nfRvbAh+Gd2aNksvaW/w3xGJnnw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.14.0.tgz", + "integrity": "sha512-dNpA/64STD5YXhaSlg4gT6Z474WPJVCHoX1ibsVIFu0fVgH609Y69bsdmbvTRdI7r6Dcu4ZfGxdR636RTrH+Eg==", "dev": true, "requires": { + "@lerna/prerelease-id-from-version": "3.14.0", "@lerna/validation-error": "3.13.0", "npm-package-arg": "^6.1.0", + "npmlog": "^4.1.2", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "@lerna/prerelease-id-from-version": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.14.0.tgz", + "integrity": "sha512-Ap3Z/dNhqQuSrKmK+JmzYvQYI2vowxHvUVxZJiDVilW8dyNnxkCsYFmkuZytk5sxVz4VeGLNPS2RSsU5eeSS+Q==", + "dev": true, + "requires": { "semver": "^5.5.0" }, "dependencies": { @@ -2342,14 +2379,14 @@ }, "dependencies": { "cosmiconfig": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.0.tgz", - "integrity": "sha512-nxt+Nfc3JAqf4WIWd0jXLjTJZmsPLrA9DDc4nRw2KFJQJK7DNooqSXrNI7tzLG50CF8axczly5UV929tBmh/7g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", "dev": true, "requires": { "import-fresh": "^2.0.0", "is-directory": "^0.3.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, @@ -2424,29 +2461,29 @@ } }, "@lerna/publish": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.13.2.tgz", - "integrity": "sha512-L8iceC3Z2YJnlV3cGbfk47NSh1+iOo1tD65z+BU3IYLRpPnnSf8i6BORdKV8rECDj6kjLYvL7//2yxbHy7shhA==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.14.1.tgz", + "integrity": "sha512-p+By/P84XJkndBzrmcnVLMcFpGAE+sQZCQK4e3aKQrEMLDrEwXkWt/XJxzeQskPxInFA/7Icj686LOADO7p0qg==", "dev": true, "requires": { - "@lerna/batch-packages": "3.13.0", - "@lerna/check-working-tree": "3.13.0", - "@lerna/child-process": "3.13.0", - "@lerna/collect-updates": "3.13.0", - "@lerna/command": "3.13.1", - "@lerna/describe-ref": "3.13.0", + "@lerna/check-working-tree": "3.14.1", + "@lerna/child-process": "3.13.3", + "@lerna/collect-updates": "3.14.0", + "@lerna/command": "3.14.0", + "@lerna/describe-ref": "3.13.3", "@lerna/log-packed": "3.13.0", "@lerna/npm-conf": "3.13.0", - "@lerna/npm-dist-tag": "3.13.0", - "@lerna/npm-publish": "3.13.2", + "@lerna/npm-dist-tag": "3.14.0", + "@lerna/npm-publish": "3.14.0", "@lerna/output": "3.13.0", - "@lerna/pack-directory": "3.13.1", + "@lerna/pack-directory": "3.14.0", + "@lerna/prerelease-id-from-version": "3.14.0", "@lerna/prompt": "3.13.0", "@lerna/pulse-till-done": "3.13.0", - "@lerna/run-lifecycle": "3.13.0", - "@lerna/run-parallel-batches": "3.13.0", + "@lerna/run-lifecycle": "3.14.0", + "@lerna/run-topologically": "3.14.0", "@lerna/validation-error": "3.13.0", - "@lerna/version": "3.13.2", + "@lerna/version": "3.14.1", "figgy-pudding": "^3.5.1", "fs-extra": "^7.0.0", "libnpmaccess": "^3.0.1", @@ -2456,7 +2493,6 @@ "p-finally": "^1.0.0", "p-map": "^1.2.0", "p-pipe": "^1.2.0", - "p-reduce": "^1.0.0", "pacote": "^9.5.0", "semver": "^5.5.0" }, @@ -2489,6 +2525,16 @@ "npmlog": "^4.1.2" } }, + "@lerna/query-graph": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-3.14.0.tgz", + "integrity": "sha512-6YTh3vDMW2hUxHdKeRvx4bosc9lZClKaN+DzC1XKTkwDbWrsjmEzLcemKL6QnyyeuryN2f/eto7P9iSe3z3pQQ==", + "dev": true, + "requires": { + "@lerna/package-graph": "3.14.0", + "figgy-pudding": "^3.5.1" + } + }, "@lerna/resolve-symlink": { "version": "3.13.0", "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.13.0.tgz", @@ -2514,43 +2560,42 @@ } }, "@lerna/rimraf-dir": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.13.0.tgz", - "integrity": "sha512-kte+pMemulre8cmPqljxIYjCmdLByz8DgHBHXB49kz2EiPf8JJ+hJFt0PzEubEyJZ2YE2EVAx5Tv5+NfGNUQyQ==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.13.3.tgz", + "integrity": "sha512-d0T1Hxwu3gpYVv73ytSL+/Oy8JitsmvOYUR5ouRSABsmqS7ZZCh5t6FgVDDGVXeuhbw82+vuny1Og6Q0k4ilqw==", "dev": true, "requires": { - "@lerna/child-process": "3.13.0", + "@lerna/child-process": "3.13.3", "npmlog": "^4.1.2", "path-exists": "^3.0.0", "rimraf": "^2.6.2" } }, "@lerna/run": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.13.1.tgz", - "integrity": "sha512-nv1oj7bsqppWm1M4ifN+/IIbVu9F4RixrbQD2okqDGYne4RQPAXyb5cEZuAzY/wyGTWWiVaZ1zpj5ogPWvH0bw==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.14.0.tgz", + "integrity": "sha512-kGGFGLYPKozAN07CSJ7kOyLY6W3oLCQcxCathg1isSkBqQH29tWUg8qNduOlhIFLmnq/nf1JEJxxoXnF6IRLjQ==", "dev": true, "requires": { - "@lerna/batch-packages": "3.13.0", - "@lerna/command": "3.13.1", - "@lerna/filter-options": "3.13.0", - "@lerna/npm-run-script": "3.13.0", + "@lerna/command": "3.14.0", + "@lerna/filter-options": "3.14.0", + "@lerna/npm-run-script": "3.13.3", "@lerna/output": "3.13.0", - "@lerna/run-parallel-batches": "3.13.0", + "@lerna/run-topologically": "3.14.0", "@lerna/timer": "3.13.0", "@lerna/validation-error": "3.13.0", "p-map": "^1.2.0" } }, "@lerna/run-lifecycle": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.13.0.tgz", - "integrity": "sha512-oyiaL1biZdjpmjh6X/5C4w07wNFyiwXSSHH5GQB4Ay4BPwgq9oNhCcxRoi0UVZlZ1YwzSW8sTwLgj8emkIo3Yg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.14.0.tgz", + "integrity": "sha512-GUM3L9MzGRSW0WQ8wbLW1+SYStU1OFjW0GBzShhBnFrO4nGRrU7VchsLpcLu0hk2uCzyhsrDKzifEdOdUyMoEQ==", "dev": true, "requires": { "@lerna/npm-conf": "3.13.0", "figgy-pudding": "^3.5.1", - "npm-lifecycle": "^2.1.0", + "npm-lifecycle": "^2.1.1", "npmlog": "^4.1.2" } }, @@ -2564,13 +2609,24 @@ "p-map-series": "^1.0.0" } }, + "@lerna/run-topologically": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-3.14.0.tgz", + "integrity": "sha512-y+KBpC1YExFzGynovt9MY4O/bc3RrJaKeuXieiPfKGKxrdtmZe/r33oj/xePTXZq65jnw3SaU3H8S5CrrdkwDg==", + "dev": true, + "requires": { + "@lerna/query-graph": "3.14.0", + "figgy-pudding": "^3.5.1", + "p-queue": "^4.0.0" + } + }, "@lerna/symlink-binary": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.13.0.tgz", - "integrity": "sha512-obc4Y6jxywkdaCe+DB0uTxYqP0IQ8mFWvN+k/YMbwH4G2h7M7lCBWgPy8e7xw/50+1II9tT2sxgx+jMus1sTJg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.14.0.tgz", + "integrity": "sha512-AHFb4NlazxYmC+7guoamM3laIRbMSeKERMooKHJ7moe0ayGPBWsCGOH+ZFKZ+eXSDek+FnxdzayR3wf8B3LkTg==", "dev": true, "requires": { - "@lerna/create-symlink": "3.13.0", + "@lerna/create-symlink": "3.14.0", "@lerna/package": "3.13.0", "fs-extra": "^7.0.0", "p-map": "^1.2.0" @@ -2590,14 +2646,14 @@ } }, "@lerna/symlink-dependencies": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.13.0.tgz", - "integrity": "sha512-7CyN5WYEPkbPLbqHBIQg/YiimBzb5cIGQB0E9IkLs3+racq2vmUNQZn38LOaazQacAA83seB+zWSxlI6H+eXSg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.14.0.tgz", + "integrity": "sha512-kuSXxwAWiVZqFcXfUBKH4yLUH3lrnGyZmCYon7UnZitw3AK3LQY7HvV2LNNw/oatfjOAiKhPBxnYjYijKiV4oA==", "dev": true, "requires": { - "@lerna/create-symlink": "3.13.0", + "@lerna/create-symlink": "3.14.0", "@lerna/resolve-symlink": "3.13.0", - "@lerna/symlink-binary": "3.13.0", + "@lerna/symlink-binary": "3.14.0", "fs-extra": "^7.0.0", "p-finally": "^1.0.0", "p-map": "^1.2.0", @@ -2633,21 +2689,23 @@ } }, "@lerna/version": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.13.2.tgz", - "integrity": "sha512-85AEn6Cx5p1VOejEd5fpTyeDCx6yejSJCgbILkx+gXhLhFg2XpFzLswMd+u71X7RAttWHvhzeKJAw4tWTXDvpQ==", - "dev": true, - "requires": { - "@lerna/batch-packages": "3.13.0", - "@lerna/check-working-tree": "3.13.0", - "@lerna/child-process": "3.13.0", - "@lerna/collect-updates": "3.13.0", - "@lerna/command": "3.13.1", - "@lerna/conventional-commits": "3.13.0", - "@lerna/github-client": "3.13.1", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.14.1.tgz", + "integrity": "sha512-H/jykoxVIt4oDEYkBgwDfO5dmZFl3G6vP1UEttRVP1FIkI+gCN+olby8S0Qd8XprDuR5OrLboiDWQs3p7nJhLw==", + "dev": true, + "requires": { + "@lerna/batch-packages": "3.14.0", + "@lerna/check-working-tree": "3.14.1", + "@lerna/child-process": "3.13.3", + "@lerna/collect-updates": "3.14.0", + "@lerna/command": "3.14.0", + "@lerna/conventional-commits": "3.14.0", + "@lerna/github-client": "3.13.3", "@lerna/output": "3.13.0", + "@lerna/prerelease-id-from-version": "3.14.0", "@lerna/prompt": "3.13.0", - "@lerna/run-lifecycle": "3.13.0", + "@lerna/run-lifecycle": "3.14.0", + "@lerna/run-topologically": "3.14.0", "@lerna/validation-error": "3.13.0", "chalk": "^2.3.1", "dedent": "^0.7.0", @@ -6629,13 +6687,13 @@ } }, "conventional-changelog-core": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.1.6.tgz", - "integrity": "sha512-5teTAZOtJ4HLR6384h50nPAaKdDr+IaU0rnD2Gg2C3MS7hKsEPH8pZxrDNqam9eOSPQg9tET6uZY79zzgSz+ig==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.2.2.tgz", + "integrity": "sha512-cssjAKajxaOX5LNAJLB+UOcoWjAIBvXtDMedv/58G+YEmAXMNfC16mmPl0JDOuVJVfIqM0nqQiZ8UCm8IXbE0g==", "dev": true, "requires": { - "conventional-changelog-writer": "^4.0.3", - "conventional-commits-parser": "^3.0.1", + "conventional-changelog-writer": "^4.0.5", + "conventional-commits-parser": "^3.0.2", "dateformat": "^3.0.0", "get-pkg-repo": "^1.0.0", "git-raw-commits": "2.0.0", @@ -6646,7 +6704,7 @@ "q": "^1.5.1", "read-pkg": "^3.0.0", "read-pkg-up": "^3.0.0", - "through2": "^2.0.0" + "through2": "^3.0.0" }, "dependencies": { "load-json-file": { @@ -6703,6 +6761,15 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } } } }, @@ -6713,21 +6780,21 @@ "dev": true }, "conventional-changelog-writer": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.3.tgz", - "integrity": "sha512-bIlpSiQtQZ1+nDVHEEh798Erj2jhN/wEjyw9sfxY9es6h7pREE5BNJjfv0hXGH/FTrAsEpHUq4xzK99eePpwuA==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.6.tgz", + "integrity": "sha512-ou/sbrplJMM6KQpR5rKFYNVQYesFjN7WpNGdudQSWNi6X+RgyFUcSv871YBYkrUYV9EX8ijMohYVzn9RUb+4ag==", "dev": true, "requires": { "compare-func": "^1.3.1", - "conventional-commits-filter": "^2.0.1", + "conventional-commits-filter": "^2.0.2", "dateformat": "^3.0.0", "handlebars": "^4.1.0", "json-stringify-safe": "^5.0.1", "lodash": "^4.2.1", "meow": "^4.0.0", - "semver": "^5.5.0", + "semver": "^6.0.0", "split": "^1.0.0", - "through2": "^2.0.0" + "through2": "^3.0.0" }, "dependencies": { "load-json-file": { @@ -6802,42 +6869,45 @@ "read-pkg": "^3.0.0" } }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } } } }, "conventional-commits-filter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.1.tgz", - "integrity": "sha512-92OU8pz/977udhBjgPEbg3sbYzIxMDFTlQT97w7KdhR9igNqdJvy8smmedAAgn4tPiqseFloKkrVfbXCVd+E7A==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz", + "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==", "dev": true, "requires": { - "is-subset": "^0.1.1", + "lodash.ismatch": "^4.4.0", "modify-values": "^1.0.0" } }, "conventional-commits-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.1.tgz", - "integrity": "sha512-P6U5UOvDeidUJ8ebHVDIoXzI7gMlQ1OF/id6oUvp8cnZvOXMt1n8nYl74Ey9YMn0uVQtxmCtjPQawpsssBWtGg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.3.tgz", + "integrity": "sha512-KaA/2EeUkO4bKjinNfGUyqPTX/6w9JGshuQRik4r/wJz7rUw3+D3fDG6sZSEqJvKILzKXFQuFkpPLclcsAuZcg==", "dev": true, "requires": { "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", + "is-text-path": "^2.0.0", "lodash": "^4.2.1", "meow": "^4.0.0", "split2": "^2.0.0", - "through2": "^2.0.0", + "through2": "^3.0.0", "trim-off-newlines": "^1.0.0" }, "dependencies": { @@ -6918,6 +6988,15 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } } } }, @@ -6949,31 +7028,6 @@ "typedarray": "^0.0.6" } }, - "conventional-commits-filter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz", - "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==", - "dev": true, - "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.2.tgz", - "integrity": "sha512-y5eqgaKR0F6xsBNVSQ/5cI5qIF3MojddSUi1vKIggRkqUTbkqFKH9P5YX/AT1BVZp9DtSzBTIkvjyVLotLsVog==", - "dev": true, - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^3.0.0", - "trim-off-newlines": "^1.0.0" - } - }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -7062,15 +7116,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "dev": true, - "requires": { - "readable-stream": "2 || 3" - } } } }, @@ -8962,6 +9007,12 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true + }, "events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", @@ -9726,9 +9777,9 @@ } }, "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", "dev": true, "requires": { "minipass": "^2.2.1" @@ -11954,12 +12005,12 @@ "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" }, "is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", "dev": true, "requires": { - "text-extensions": "^1.0.0" + "text-extensions": "^2.0.0" } }, "is-touch-device": { @@ -13597,26 +13648,26 @@ "dev": true }, "lerna": { - "version": "3.13.2", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.13.2.tgz", - "integrity": "sha512-2iliiFVAMNqaKsVSJ90p49dur93d5RlktotAJNp+uuHsCuIIAvwceqmSgDQCmWu4GkgAom+5uy//KV6F9t8fLA==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.14.1.tgz", + "integrity": "sha512-lQxmGeEECjOMI3pRh2+I6jazoEWhEfvZNIs7XaX71op33AVwyjlY/nQ1GJGrPhxYBuQnlPgH0vH/nC/lcLaVkw==", "dev": true, "requires": { - "@lerna/add": "3.13.1", - "@lerna/bootstrap": "3.13.1", - "@lerna/changed": "3.13.2", - "@lerna/clean": "3.13.1", + "@lerna/add": "3.14.0", + "@lerna/bootstrap": "3.14.0", + "@lerna/changed": "3.14.1", + "@lerna/clean": "3.14.0", "@lerna/cli": "3.13.0", - "@lerna/create": "3.13.1", - "@lerna/diff": "3.13.1", - "@lerna/exec": "3.13.1", - "@lerna/import": "3.13.1", - "@lerna/init": "3.13.1", - "@lerna/link": "3.13.1", - "@lerna/list": "3.13.1", - "@lerna/publish": "3.13.2", - "@lerna/run": "3.13.1", - "@lerna/version": "3.13.2", + "@lerna/create": "3.14.0", + "@lerna/diff": "3.14.0", + "@lerna/exec": "3.14.0", + "@lerna/import": "3.14.0", + "@lerna/init": "3.14.0", + "@lerna/link": "3.14.0", + "@lerna/list": "3.14.0", + "@lerna/publish": "3.14.1", + "@lerna/run": "3.14.0", + "@lerna/version": "3.14.1", "import-local": "^1.0.0", "npmlog": "^4.1.2" } @@ -14230,9 +14281,9 @@ }, "dependencies": { "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true }, "cacache": { @@ -14275,9 +14326,9 @@ "dev": true }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -15497,14 +15548,14 @@ "dev": true }, "npm-lifecycle": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-2.1.0.tgz", - "integrity": "sha512-QbBfLlGBKsktwBZLj6AviHC6Q9Y3R/AY4a2PYSIRhSKSS0/CxRyD/PfxEX6tPeOCXQgMSNdwGeECacstgptc+g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-2.1.1.tgz", + "integrity": "sha512-+Vg6I60Z75V/09pdcH5iUo/99Q/vop35PaI99elvxk56azSVVsdsSsS/sXqKDNwbRRNN1qSxkcO45ZOu0yOWew==", "dev": true, "requires": { "byline": "^5.0.0", - "graceful-fs": "^4.1.11", - "node-gyp": "^3.8.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^4.0.0", "resolve-from": "^4.0.0", "slide": "^1.1.6", "uid-number": "0.0.6", @@ -15512,11 +15563,69 @@ "which": "^1.3.1" }, "dependencies": { + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "node-gyp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", + "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", + "dev": true, + "requires": { + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^4.4.8", + "which": "1" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true } } }, @@ -16069,6 +16178,15 @@ "integrity": "sha1-SxoROZoRUgpneQ7loMHViB1r7+k=", "dev": true }, + "p-queue": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-4.0.0.tgz", + "integrity": "sha512-3cRXXn3/O0o3+eVmUroJPSj/esxoEFIm0ZOno/T+NzG/VZgPOqQ8WKmlNqubSEpZmCIngEy34unkHGg83ZIBmg==", + "dev": true, + "requires": { + "eventemitter3": "^3.1.0" + } + }, "p-reduce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", @@ -16125,9 +16243,9 @@ }, "dependencies": { "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true }, "cacache": { @@ -16168,9 +16286,9 @@ } }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -20424,7 +20542,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -21218,9 +21336,9 @@ } }, "text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.0.0.tgz", + "integrity": "sha512-F91ZqLgvi1E0PdvmxMgp+gcf6q8fMH7mhdwWfzXnl1k+GbpQDmi8l7DzLC5JTASKbwpY3TfxajAUzAXcv2NmsQ==", "dev": true }, "text-table": { @@ -22778,9 +22896,9 @@ "dev": true }, "write-file-atomic": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", - "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "dev": true, "requires": { "graceful-fs": "^4.1.11", diff --git a/package.json b/package.json index 36d46c8d9b3169..39f59d21e9ffc1 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "inquirer": "6.3.1", "is-equal-shallow": "0.1.3", "jsdom": "11.12.0", - "lerna": "3.13.2", + "lerna": "3.14.1", "lint-staged": "8.1.5", "lodash": "4.17.11", "mkdirp": "0.5.1", From 3a26e3bf8647c9e348b43a3984699224ec8068ec Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Mon, 27 May 2019 09:57:24 +0200 Subject: [PATCH 189/664] Add missing release date and version to CHANGELOG files --- packages/block-library/CHANGELOG.md | 2 +- packages/blocks/CHANGELOG.md | 2 +- packages/core-data/CHANGELOG.md | 2 +- packages/e2e-tests/CHANGELOG.md | 2 +- packages/editor/CHANGELOG.md | 2 +- packages/element/CHANGELOG.md | 2 +- packages/rich-text/CHANGELOG.md | 2 +- packages/scripts/CHANGELOG.md | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index d61e87cb5bd485..b0576be7c47fdd 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.5.0 (Unreleased) +## 2.5.0 (2019-05-21) - Add vertical alignment controls to Columns Block ([#13899](https://github.com/WordPress/gutenberg/pull/13899/)). - Add vertical alignment controls to Media & Text Block ([#13989](https://github.com/WordPress/gutenberg/pull/13989)). diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 55def7da9db4ce..b13c58601e1e99 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,4 +1,4 @@ -## x.x.x (Unreleased) +## 6.3.0 (2019-05-21) ### New Feature diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index c1466915ec294a..07134508762725 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.1.0 (Unreleased) +## 2.3.0 (2019-05-21) ### New features - The `getAutosave`, `getAutosaves` and `getCurrentUser` selectors have been added. diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 397c64f9d17a80..48652e79f44e98 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 1.2.0 (2019-05-21) ### New features diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index d37f22323ec95e..3840d850ed599a 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## 9.2.0 (Unreleased) +## 9.3.0 (2019-05-21) ### Deprecations - The `getAutosave`, `getAutosaveAttribute`, and `hasAutosave` selectors are deprecated. Please use the `getAutosave` selector in the `@wordpress/core-data` package. diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md index db8c82c55972d4..c08444f058e2ed 100644 --- a/packages/element/CHANGELOG.md +++ b/packages/element/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.4.0 (Unreleased) +## 2.4.0 (2019-05-21) ### New Features diff --git a/packages/rich-text/CHANGELOG.md b/packages/rich-text/CHANGELOG.md index c80223634d1e82..9c969cf333ed29 100644 --- a/packages/rich-text/CHANGELOG.md +++ b/packages/rich-text/CHANGELOG.md @@ -1,4 +1,4 @@ -## 3.2.0 (Unreleased) +## 3.3.0 (2019-05-21) ### Internal diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 7469b3455680bc..30aed0c902b09b 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 3.2.0 (2019-05-21) ### New Feature From cb127c93baa22222d9c31eac8ea25efec8ac6cba Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 27 May 2019 04:10:31 -0400 Subject: [PATCH 190/664] Block Editor: Partition attributes updates to avoid conflating meta and blocks attributes (#15781) --- .../src/components/block-list/block.js | 40 +++++++++++++------ .../plugins/meta-attribute-block.test.js | 12 +++++- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index cccebd02de640f..a9b28f618daf5f 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -644,18 +644,34 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { setAttributes( newAttributes ) { const { name, clientId } = ownProps; const type = getBlockType( name ); - updateBlockAttributes( clientId, newAttributes ); - const metaAttributes = reduce( - newAttributes, - ( result, value, key ) => { - if ( get( type, [ 'attributes', key, 'source' ] ) === 'meta' ) { - result[ type.attributes[ key ].meta ] = value; - } - - return result; - }, - {} - ); + + function isMetaAttribute( key ) { + return get( type, [ 'attributes', key, 'source' ] ) === 'meta'; + } + + // Partition new attributes to delegate update behavior by source. + // + // TODO: A consolidated approach to external attributes sourcing + // should be devised to avoid specific handling for meta, enable + // additional attributes sources. + // + // See: https://github.com/WordPress/gutenberg/issues/2759 + const { + blockAttributes, + metaAttributes, + } = reduce( newAttributes, ( result, value, key ) => { + if ( isMetaAttribute( key ) ) { + result.metaAttributes[ type.attributes[ key ].meta ] = value; + } else { + result.blockAttributes[ key ] = value; + } + + return result; + }, { blockAttributes: {}, metaAttributes: {} } ); + + if ( size( blockAttributes ) ) { + updateBlockAttributes( clientId, blockAttributes ); + } if ( size( metaAttributes ) ) { const { getSettings } = select( 'core/block-editor' ); diff --git a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js index 5de9ea1f6c619b..0e87fdeefea616 100644 --- a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js +++ b/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js @@ -8,6 +8,7 @@ import { getEditedPostContent, insertBlock, saveDraft, + pressKeyTimes, } from '@wordpress/e2e-test-utils'; describe( 'Block with a meta attribute', () => { @@ -25,7 +26,16 @@ describe( 'Block with a meta attribute', () => { it( 'Should persist the meta attribute properly', async () => { await insertBlock( 'Test Meta Attribute Block' ); - await page.keyboard.type( 'Meta Value' ); + await page.keyboard.type( 'Value' ); + + // Regression Test: Previously the caret would wrongly reset to the end + // of any input for meta-sourced attributes, due to syncing behavior of + // meta attribute updates. + // + // See: https://github.com/WordPress/gutenberg/issues/15739 + await pressKeyTimes( 'ArrowLeft', 5 ); + await page.keyboard.type( 'Meta ' ); + await saveDraft(); await page.reload(); From de1960a900d7e03472dd987af017f1e35f7df58a Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Mon, 27 May 2019 11:15:43 +0300 Subject: [PATCH 191/664] Remove unregistration of video block (#15808) --- packages/edit-post/src/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index a1afa49808b038..6ada9eb995eadd 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -23,7 +23,6 @@ export function initializeEditor() { if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); unregisterBlockType( 'core/more' ); - unregisterBlockType( 'core/video' ); } } From 74129942b24c6e3b26717e9941b5894af348baa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 27 May 2019 10:19:04 +0200 Subject: [PATCH 192/664] Chore: Fix vulnerabilities found in installed packages (#15837) * Chore: Run npm audit fix fixed 8 of 12 vulnerabilities in 1865619 scanned packages * Chore: Fix more dependencies --- package-lock.json | 98 ++++++++++++++--------------------- packages/scripts/package.json | 2 +- 2 files changed, 41 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f1b924def079f..64b5313e90e486 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3470,24 +3470,6 @@ "requires": { "webpack": "^4.8.3", "webpack-sources": "^1.3.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - } } }, "@wordpress/deprecated": { @@ -3839,7 +3821,7 @@ "stylelint-config-wordpress": "^13.1.0", "thread-loader": "^2.1.2", "webpack": "4.8.3", - "webpack-bundle-analyzer": "^3.0.3", + "webpack-bundle-analyzer": "^3.3.2", "webpack-cli": "^3.1.2", "webpack-livereload-plugin": "^2.2.0" } @@ -3948,6 +3930,12 @@ "acorn": "^5.0.3" } }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, "agent-base": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", @@ -10352,9 +10340,9 @@ } }, "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -11005,17 +10993,23 @@ } }, "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "dev": true, "requires": { - "async": "^2.5.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" }, "dependencies": { + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -20542,7 +20536,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -21140,13 +21134,13 @@ "dev": true }, "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", "dev": true, "requires": { "block-stream": "*", - "fstream": "^1.0.2", + "fstream": "^1.0.12", "inherits": "2" } }, @@ -21661,23 +21655,16 @@ "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" }, "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", + "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", "dev": true, "optional": true, "requires": { - "commander": "~2.17.1", + "commander": "~2.20.0", "source-map": "~0.6.1" }, "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -22424,12 +22411,13 @@ } }, "webpack-bundle-analyzer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz", - "integrity": "sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.3.2.tgz", + "integrity": "sha512-7qvJLPKB4rRWZGjVp5U1KEjwutbDHSKboAl0IfafnrdXMrgC0tOtZbQD6Rw0u4cmpgRN4O02Fc0t8eAT+FgGzA==", "dev": true, "requires": { - "acorn": "^5.7.3", + "acorn": "^6.0.7", + "acorn-walk": "^6.1.1", "bfj": "^6.1.1", "chalk": "^2.4.1", "commander": "^2.18.0", @@ -22444,15 +22432,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } @@ -22674,9 +22656,9 @@ } }, "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", "dev": true, "requires": { "source-list-map": "^2.0.0", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 6da22a30e26511..712b5edde2b3f5 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -53,7 +53,7 @@ "stylelint-config-wordpress": "^13.1.0", "thread-loader": "^2.1.2", "webpack": "4.8.3", - "webpack-bundle-analyzer": "^3.0.3", + "webpack-bundle-analyzer": "^3.3.2", "webpack-cli": "^3.1.2", "webpack-livereload-plugin": "^2.2.0" }, From 562d061e261db89a72e76776fcdd75b7d9440590 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 27 May 2019 09:30:11 +0100 Subject: [PATCH 193/664] Bump plugin version to 5.8.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 7b121cc8970fae..1d166f153faacf 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.7.0 + * Version: 5.8.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 64b5313e90e486..8d51cc11b6bb2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.7.0", + "version": "5.8.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 39f59d21e9ffc1..78d57862f8c228 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.7.0", + "version": "5.8.0-rc.1", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 9007d1943f1e90bdae243e427f7a6b42a1e36008 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Mon, 27 May 2019 12:18:54 +0300 Subject: [PATCH 194/664] Don't set isSelected unless undefined (#15833) --- .../block-editor/src/components/rich-text/index.native.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 9cff30df3ef13d..13fcc41771e1c7 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -923,11 +923,6 @@ const RichTextContainer = compose( [ selectionStart.clientId === clientId && selectionStart.attributeKey === identifier ); - } else { - isSelected = isSelected && ( - selectionStart.clientId === clientId && - selectionStart.attributeKey === identifier - ); } return { From c4cd4d8877087d08a80bbba9d4bf5f4f45cd6e83 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Mon, 27 May 2019 11:42:39 +0200 Subject: [PATCH 195/664] [RNMobile] Fix link "open in new tab" switch (#15812) * Fix open in a new tab option * Fix lint issues --- packages/format-library/src/link/modal.native.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index d796dd2bc53a9d..6f985edd932051 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -53,10 +53,13 @@ class ModalLinkUI extends Component { return; } + const { activeAttributes: { url, target } } = this.props; + const opensInNewWindow = target === '_blank'; + this.setState( { - inputValue: this.props.activeAttributes.url || '', + inputValue: url || '', text: getTextContent( slice( this.props.value ) ), - opensInNewWindow: false, + opensInNewWindow, } ); } From 118f7f9bf140048a28d0ee6baf73d8437e9213c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 27 May 2019 11:58:38 +0200 Subject: [PATCH 196/664] Docs: Remove the note about using OTP env variable when publishing packages (#15835) --- packages/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/README.md b/packages/README.md index 3c595fc8f8c25b..5733bded205edd 100644 --- a/packages/README.md +++ b/packages/README.md @@ -102,10 +102,10 @@ You can check your package configs by running `npm run lint-pkg-json`. #### Development Release -Run the following command to release a dev version of the outdated packages, replacing `123456` with your 2FA code. Make sure you're using a freshly generated 2FA code, rather than one that's about to timeout. This is a little cumbersome but helps to prevent the release process from dying mid-deploy. +Run the following command to release a dev version of the outdated packages. ```bash -NPM_CONFIG_OTP=123456 npm run publish:dev +npm run publish:dev ``` Lerna will ask you which version number you want to choose for each package. For a `dev` release, you'll more likely want to choose the "prerelease" option. Repeat the same for all the outdated packages and confirm your version updates. @@ -114,10 +114,10 @@ Lerna will then publish to [npm], commit the `package.json` changes and create t #### Production Release -To release a production version for the outdated packages, run the following command, replacing `123456` with your (freshly generated, as above) 2FA code: +To release a production version for the outdated packages, run the following command: ```bash -NPM_CONFIG_OTP=123456 npm run publish:prod +npm run publish:prod ``` Choose the correct version based on `CHANGELOG.md` files, confirm your choices and let Lerna do its magic. From 70154bb16959f1cce73d707300b7eaeb01a2ec88 Mon Sep 17 00:00:00 2001 From: Nitish Kaila <kailanitish90@gmail.com> Date: Mon, 27 May 2019 15:48:21 +0530 Subject: [PATCH 197/664] Docs: Fix 404 link in filters document (#15836) * Fix 404 link Fix 404 link * Update with correct URL Update with correct URL --- docs/designers-developers/developers/filters/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/filters/README.md b/docs/designers-developers/developers/filters/README.md index c110d2ed3a5543..a3877df5558547 100644 --- a/docs/designers-developers/developers/filters/README.md +++ b/docs/designers-developers/developers/filters/README.md @@ -4,4 +4,4 @@ There are two types of hooks: [Actions](https://developer.wordpress.org/plugins/hooks/actions/) and [Filters](https://developer.wordpress.org/plugins/hooks/filters/). In addition to PHP actions and filters, WordPress also provides a mechanism for registering and executing hooks in JavaScript. This functionality is also available on npm as the [@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks) package, for general purpose use. -You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](/packages/tree/master/packages/hooks). +You can also learn more about both APIs: [PHP](https://codex.wordpress.org/Plugin_API/) and [JavaScript](/packages/hooks/README.md). From 325b26c46489b8d09ba89878493c5fa58a87af19 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 27 May 2019 12:56:43 +0100 Subject: [PATCH 198/664] Reset the current branch before changing branches when releasing Gutenberg (#15840) --- bin/commander.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/commander.js b/bin/commander.js index 087dcdf0540ac6..ce92015e2216fe 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -265,8 +265,8 @@ async function releasePluginRC() { abortMessage ); await simpleGit.fetch(); - await simpleGit.checkout( 'master' ); await simpleGit.reset( 'hard' ); + await simpleGit.checkout( 'master' ); await simpleGit.pull( 'origin', 'master' ); await simpleGit.raw( [ 'cherry-pick', commitHash ] ); await simpleGit.push( 'origin', 'master' ); From 700121d1c40e205c21e623ea0a52fb666b93385d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 27 May 2019 15:10:17 +0200 Subject: [PATCH 199/664] Chore: Remove wrongly added block quotation component style (#15845) --- .../src/primitives/block-quotation/style.scss | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 packages/components/src/primitives/block-quotation/style.scss diff --git a/packages/components/src/primitives/block-quotation/style.scss b/packages/components/src/primitives/block-quotation/style.scss deleted file mode 100644 index c1206d68958f7b..00000000000000 --- a/packages/components/src/primitives/block-quotation/style.scss +++ /dev/null @@ -1,19 +0,0 @@ -.wp-block-quote { - &.is-style-large, - &.is-large { - margin: 0 0 16px; - padding: 0 1em; - - p { - font-size: 24px; - font-style: italic; - line-height: 1.6; - } - - cite, - footer { - font-size: 18px; - text-align: right; - } - } -} From ce78cd88e7088cb60cb2b9395e756c9452fd9ebf Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Mon, 27 May 2019 09:44:48 -0400 Subject: [PATCH 200/664] @wordpress/data: Introduce useSelect custom hook. (#15737) A new `useSelect` hook for the `wp.data` api! This hook allows for an alternative way to interact with data kept in a store state via registered selectors following a similar pattern to `useEffect` and other react hooks. Other notable changes in this pull: - exposing the custom `useRegistry` react hook for retrieving the `registry` object from components in a registry provider tree. - `withSelect` implements `useSelect` under the hood. Many thanks also to @epiqueras for contributions to the work in this pull. --- packages/data/CHANGELOG.md | 10 + packages/data/README.md | 130 +++++- .../components/async-mode-provider/context.js | 12 + .../components/async-mode-provider/index.js | 12 +- .../async-mode-provider/use-async-mode.js | 13 + .../components/registry-provider/context.js | 54 +++ .../src/components/registry-provider/index.js | 17 +- .../registry-provider/use-registry.js | 52 +++ .../data/src/components/use-select/index.js | 167 ++++++++ .../src/components/use-select/test/index.js | 136 ++++++ .../components/with-dispatch/test/index.js | 2 +- .../data/src/components/with-select/index.js | 182 +------- .../src/components/with-select/test/index.js | 397 ++++++++++++------ packages/data/src/index.js | 11 +- .../viewport/src/test/if-viewport-matches.js | 18 +- 15 files changed, 875 insertions(+), 338 deletions(-) create mode 100644 packages/data/src/components/async-mode-provider/context.js create mode 100644 packages/data/src/components/async-mode-provider/use-async-mode.js create mode 100644 packages/data/src/components/registry-provider/context.js create mode 100644 packages/data/src/components/registry-provider/use-registry.js create mode 100644 packages/data/src/components/use-select/index.js create mode 100644 packages/data/src/components/use-select/test/index.js diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 533101d02e8a87..391d68abe4c463 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,3 +1,13 @@ +## Master + +### New Feature + +- Expose `useSelect` hook for usage in functional components. ([#15737](https://github.com/WordPress/gutenberg/pull/15737)) + +### Enhancements + +- `withSelect` internally uses the new `useSelect` hook. ([#15737](https://github.com/WordPress/gutenberg/pull/15737). **Note:** This _could_ impact performance of code using `withSelect` in edge-cases. To avoid impact, memoize passed in `mapSelectToProps` callbacks or implement `useSelect` directly with dependencies. + ## 4.5.0 (2019-05-21) ### Bug Fix diff --git a/packages/data/README.md b/packages/data/README.md index 65b525d404099d..8984e86b1c5770 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -379,11 +379,43 @@ _Returns_ <a name="RegistryConsumer" href="#RegistryConsumer">#</a> **RegistryConsumer** -Undocumented declaration. +A custom react Context consumer exposing the provided `registry` to +children components. Used along with the RegistryProvider. + +You can read more about the react context api here: +<https://reactjs.org/docs/context.html#contextprovider> + +_Usage_ + +````js +const { + RegistryProvider, + RegistryConsumer, + createRegistry +} = wp.data; + +const registry = createRegistry( {} ); + +const App = ( { props } ) => { + return <RegistryProvider value={ registry }> + <div>Hello There</div> + <RegistryConsumer> + { ( registry ) => ( + <ComponentUsingRegistry + { ...props } + registry={ registry } + ) } + </RegistryConsumer> + </RegistryProvider> +} <a name="RegistryProvider" href="#RegistryProvider">#</a> **RegistryProvider** -Undocumented declaration. +A custom Context provider for exposing the provided `registry` to children +components via a consumer. + +See <a name="#RegistryConsumer">RegistryConsumer</a> documentation for +example. <a name="select" href="#select">#</a> **select** @@ -391,13 +423,13 @@ Given the name of a registered store, returns an object of the store's selectors The selector functions are been pre-bound to pass the current state automatically. As a consumer, you need only pass arguments of the selector, if applicable. -_Usage_ +*Usage* ```js const { select } = wp.data; select( 'my-shop' ).getPrice( 'hammer' ); -``` +```` _Parameters_ @@ -435,6 +467,91 @@ _Parameters_ Undocumented declaration. +<a name="useRegistry" href="#useRegistry">#</a> **useRegistry** + +A custom react hook exposing the registry context for use. + +This exposes the `registry` value provided via the +<a href="#RegistryProvider">Registry Provider</a> to a component implementing +this hook. + +It acts similarly to the `useContext` react hook. + +Note: Generally speaking, `useRegistry` is a low level hook that in most cases +won't be needed for implementation. Most interactions with the wp.data api +can be performed via the `useSelect` hook, or the `withSelect` and +`withDispatch` higher order components. + +_Usage_ + +```js +const { + RegistryProvider, + createRegistry, + useRegistry, +} = wp.data + +const registry = createRegistry( {} ); + +const SomeChildUsingRegistry = ( props ) => { + const registry = useRegistry( registry ); + // ...logic implementing the registry in other react hooks. +}; + + +const ParentProvidingRegistry = ( props ) => { + return <RegistryProvider value={ registry }> + <SomeChildUsingRegistry { ...props } /> + </RegistryProvider> +}; +``` + +_Returns_ + +- `Function`: A custom react hook exposing the registry context value. + +<a name="useSelect" href="#useSelect">#</a> **useSelect** + +Custom react hook for retrieving props from registered selectors. + +In general, this custom React hook follows the +[rules of hooks](https://reactjs.org/docs/hooks-rules.html). + +_Usage_ + +```js +const { useSelect } = wp.data; + +function HammerPriceDisplay( { currency } ) { + const price = useSelect( ( select ) => { + return select( 'my-shop' ).getPrice( 'hammer', currency ) + }, [ currency ] ); + return new Intl.NumberFormat( 'en-US', { + style: 'currency', + currency, + } ).format( price ); +} + +// Rendered in the application: +// <HammerPriceDisplay currency="USD" /> +``` + +In the above example, when `HammerPriceDisplay` is rendered into an +application, the price will be retrieved from the store state using the +`mapSelect` callback on `useSelect`. If the currency prop changes then +any price in the state for that currency is retrieved. If the currency prop +doesn't change and other props are passed in that do change, the price will +not change because the dependency is just the currency. + +_Parameters_ + +- _\_mapSelect_ `Function`: Function called on every state change. The returned value is exposed to the component implementing this hook. The function receives the `registry.select` method on the first argument and the `registry` on the second argument. +- _deps_ `Array`: If provided, this memoizes the mapSelect so the same `mapSelect` is invoked on every state change unless the dependencies change. + +_Returns_ + +- `Function`: A custom react hook. + <a name="withDispatch" href="#withDispatch">#</a> **withDispatch** Higher-order component used to add dispatch props using registered action creators. @@ -545,7 +662,10 @@ const HammerPriceDisplay = withSelect( ( select, ownProps ) => { // <HammerPriceDisplay currency="USD" /> ``` -In the above example, when `HammerPriceDisplay` is rendered into an application, it will pass the price into the underlying `PriceDisplay` component and update automatically if the price of a hammer ever changes in the store. +In the above example, when `HammerPriceDisplay` is rendered into an +application, it will pass the price into the underlying `PriceDisplay` +component and update automatically if the price of a hammer ever changes in +the store. _Parameters_ diff --git a/packages/data/src/components/async-mode-provider/context.js b/packages/data/src/components/async-mode-provider/context.js new file mode 100644 index 00000000000000..c0e59ef7138183 --- /dev/null +++ b/packages/data/src/components/async-mode-provider/context.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +export const Context = createContext( false ); + +const { Consumer, Provider } = Context; + +export const AsyncModeConsumer = Consumer; + +export default Provider; diff --git a/packages/data/src/components/async-mode-provider/index.js b/packages/data/src/components/async-mode-provider/index.js index 30e877d0f1ed3c..778dce8fc6eb23 100644 --- a/packages/data/src/components/async-mode-provider/index.js +++ b/packages/data/src/components/async-mode-provider/index.js @@ -1,10 +1,2 @@ -/** - * WordPress dependencies - */ -import { createContext } from '@wordpress/element'; - -const { Consumer, Provider } = createContext( false ); - -export const AsyncModeConsumer = Consumer; - -export default Provider; +export { default as useAsyncMode } from './use-async-mode'; +export { default as AsyncModeProvider, AsyncModeConsumer } from './context'; diff --git a/packages/data/src/components/async-mode-provider/use-async-mode.js b/packages/data/src/components/async-mode-provider/use-async-mode.js new file mode 100644 index 00000000000000..2f53c34ae2e07d --- /dev/null +++ b/packages/data/src/components/async-mode-provider/use-async-mode.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { Context } from './context'; + +export default function useAsyncMode() { + return useContext( Context ); +} diff --git a/packages/data/src/components/registry-provider/context.js b/packages/data/src/components/registry-provider/context.js new file mode 100644 index 00000000000000..a1fe1a9e56183c --- /dev/null +++ b/packages/data/src/components/registry-provider/context.js @@ -0,0 +1,54 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import defaultRegistry from '../../default-registry'; + +export const Context = createContext( defaultRegistry ); + +const { Consumer, Provider } = Context; + +/** + * A custom react Context consumer exposing the provided `registry` to + * children components. Used along with the RegistryProvider. + * + * You can read more about the react context api here: + * https://reactjs.org/docs/context.html#contextprovider + * + * @example + * ```js + * const { + * RegistryProvider, + * RegistryConsumer, + * createRegistry + * } = wp.data; + * + * const registry = createRegistry( {} ); + * + * const App = ( { props } ) => { + * return <RegistryProvider value={ registry }> + * <div>Hello There</div> + * <RegistryConsumer> + * { ( registry ) => ( + * <ComponentUsingRegistry + * { ...props } + * registry={ registry } + * ) } + * </RegistryConsumer> + * </RegistryProvider> + * } + */ +export const RegistryConsumer = Consumer; + +/** + * A custom Context provider for exposing the provided `registry` to children + * components via a consumer. + * + * See <a name="#RegistryConsumer">RegistryConsumer</a> documentation for + * example. + */ +export default Provider; diff --git a/packages/data/src/components/registry-provider/index.js b/packages/data/src/components/registry-provider/index.js index c296f46a1ab1a8..e4f8d99bec597f 100644 --- a/packages/data/src/components/registry-provider/index.js +++ b/packages/data/src/components/registry-provider/index.js @@ -1,15 +1,2 @@ -/** - * WordPress dependencies - */ -import { createContext } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import defaultRegistry from '../../default-registry'; - -const { Consumer, Provider } = createContext( defaultRegistry ); - -export const RegistryConsumer = Consumer; - -export default Provider; +export { default as RegistryProvider, RegistryConsumer } from './context'; +export { default as useRegistry } from './use-registry'; diff --git a/packages/data/src/components/registry-provider/use-registry.js b/packages/data/src/components/registry-provider/use-registry.js new file mode 100644 index 00000000000000..846cb5627fd2f3 --- /dev/null +++ b/packages/data/src/components/registry-provider/use-registry.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { Context } from './context'; + +/** + * A custom react hook exposing the registry context for use. + * + * This exposes the `registry` value provided via the + * <a href="#RegistryProvider">Registry Provider</a> to a component implementing + * this hook. + * + * It acts similarly to the `useContext` react hook. + * + * Note: Generally speaking, `useRegistry` is a low level hook that in most cases + * won't be needed for implementation. Most interactions with the wp.data api + * can be performed via the `useSelect` hook, or the `withSelect` and + * `withDispatch` higher order components. + * + * @example + * ```js + * const { + * RegistryProvider, + * createRegistry, + * useRegistry, + * } = wp.data + * + * const registry = createRegistry( {} ); + * + * const SomeChildUsingRegistry = ( props ) => { + * const registry = useRegistry( registry ); + * // ...logic implementing the registry in other react hooks. + * }; + * + * + * const ParentProvidingRegistry = ( props ) => { + * return <RegistryProvider value={ registry }> + * <SomeChildUsingRegistry { ...props } /> + * </RegistryProvider> + * }; + * ``` + * + * @return {Function} A custom react hook exposing the registry context value. + */ +export default function useRegistry() { + return useContext( Context ); +} diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js new file mode 100644 index 00000000000000..d71f72b21c917f --- /dev/null +++ b/packages/data/src/components/use-select/index.js @@ -0,0 +1,167 @@ +/** + * WordPress dependencies + */ +import { createQueue } from '@wordpress/priority-queue'; +import { + useLayoutEffect, + useRef, + useMemo, + useCallback, + useEffect, + useReducer, +} from '@wordpress/element'; +import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; + +/** + * Internal dependencies + */ +import useRegistry from '../registry-provider/use-registry'; +import useAsyncMode from '../async-mode-provider/use-async-mode'; + +/** + * Favor useLayoutEffect to ensure the store subscription callback always has + * the selector from the latest render. If a store update happens between render + * and the effect, this could cause missed/stale updates or inconsistent state. + * + * Fallback to useEffect for server rendered components because currently React + * throws a warning when using useLayoutEffect in that environment. + */ +const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +const renderQueue = createQueue(); + +/** + * Custom react hook for retrieving props from registered selectors. + * + * In general, this custom React hook follows the + * [rules of hooks](https://reactjs.org/docs/hooks-rules.html). + * + * @param {Function} _mapSelect Function called on every state change. The + * returned value is exposed to the component + * implementing this hook. The function receives + * the `registry.select` method on the first + * argument and the `registry` on the second + * argument. + * @param {Array} deps If provided, this memoizes the mapSelect so the + * same `mapSelect` is invoked on every state + * change unless the dependencies change. + * + * @example + * ```js + * const { useSelect } = wp.data; + * + * function HammerPriceDisplay( { currency } ) { + * const price = useSelect( ( select ) => { + * return select( 'my-shop' ).getPrice( 'hammer', currency ) + * }, [ currency ] ); + * return new Intl.NumberFormat( 'en-US', { + * style: 'currency', + * currency, + * } ).format( price ); + * } + * + * // Rendered in the application: + * // <HammerPriceDisplay currency="USD" /> + * ``` + * + * In the above example, when `HammerPriceDisplay` is rendered into an + * application, the price will be retrieved from the store state using the + * `mapSelect` callback on `useSelect`. If the currency prop changes then + * any price in the state for that currency is retrieved. If the currency prop + * doesn't change and other props are passed in that do change, the price will + * not change because the dependency is just the currency. + * + * @return {Function} A custom react hook. + */ +export default function useSelect( _mapSelect, deps ) { + const mapSelect = useCallback( _mapSelect, deps ); + const registry = useRegistry(); + const isAsync = useAsyncMode(); + const queueContext = useMemo( () => ( { queue: true } ), [ registry ] ); + const [ , forceRender ] = useReducer( ( s ) => s + 1, 0 ); + + const latestMapSelect = useRef(); + const latestIsAsync = useRef( isAsync ); + const latestMapOutput = useRef(); + const latestMapOutputError = useRef(); + const isMounted = useRef(); + + let mapOutput; + + try { + if ( + latestMapSelect.current !== mapSelect || + latestMapOutputError.current + ) { + mapOutput = mapSelect( registry.select, registry ); + } else { + mapOutput = latestMapOutput.current; + } + } catch ( error ) { + let errorMessage = `An error occurred while running 'mapSelect': ${ error.message }`; + + if ( latestMapOutputError.current ) { + errorMessage += `\nThe error may be correlated with this previous error:\n`; + errorMessage += `${ latestMapOutputError.current.stack }\n\n`; + errorMessage += 'Original stack trace:'; + + throw new Error( errorMessage ); + } + } + + useIsomorphicLayoutEffect( () => { + latestMapSelect.current = mapSelect; + if ( latestIsAsync.current !== isAsync ) { + latestIsAsync.current = isAsync; + renderQueue.flush( queueContext ); + } + latestMapOutput.current = mapOutput; + latestMapOutputError.current = undefined; + isMounted.current = true; + } ); + + useIsomorphicLayoutEffect( () => { + const onStoreChange = () => { + if ( isMounted.current ) { + try { + const newMapOutput = latestMapSelect.current( + registry.select, + registry + ); + if ( isShallowEqualObjects( latestMapOutput.current, newMapOutput ) ) { + return; + } + latestMapOutput.current = newMapOutput; + } catch ( error ) { + latestMapOutputError.current = error; + } + forceRender( {} ); + } + }; + + // catch any possible state changes during mount before the subscription + // could be set. + if ( latestIsAsync.current ) { + renderQueue.add( queueContext, onStoreChange ); + } else { + onStoreChange(); + } + + const unsubscribe = registry.subscribe( () => { + if ( latestIsAsync.current ) { + renderQueue.add( queueContext, onStoreChange ); + } else { + onStoreChange(); + } + } ); + + return () => { + isMounted.current = false; + unsubscribe(); + renderQueue.flush( queueContext ); + }; + }, [ registry ] ); + + return mapOutput; +} diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js new file mode 100644 index 00000000000000..ce8d15f3aa0707 --- /dev/null +++ b/packages/data/src/components/use-select/test/index.js @@ -0,0 +1,136 @@ +/** + * External dependencies + */ +import TestRenderer, { act } from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import { createRegistry } from '../../../registry'; +import { RegistryProvider } from '../../registry-provider'; +import useSelect from '../index'; + +describe( 'useSelect', () => { + let registry; + beforeEach( () => { + registry = createRegistry(); + } ); + + const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => { + const dependencies = props[ dependencyKey ]; + mapSelectSpy.mockImplementation( + ( select ) => ( { + results: select( 'testStore' ).testSelector( props.keyName ), + } ) + ); + const data = useSelect( mapSelectSpy, [ dependencies ] ); + return <div>{ data.results }</div>; + }; + + it( 'passes the relevant data to the component', () => { + registry.registerStore( 'testStore', { + reducer: () => ( { foo: 'bar' } ), + selectors: { + testSelector: ( state, key ) => state[ key ], + }, + } ); + const selectSpy = jest.fn(); + const TestComponent = jest.fn().mockImplementation( + getTestComponent( selectSpy, 'keyName' ) + ); + let renderer; + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent keyName="foo" /> + </RegistryProvider> + ); + } ); + const testInstance = renderer.root; + // 2 times expected + // - 1 for initial mount + // - 1 for after mount before subscription set. + expect( selectSpy ).toHaveBeenCalledTimes( 2 ); + expect( TestComponent ).toHaveBeenCalledTimes( 1 ); + + // ensure expected state was rendered + expect( testInstance.findByType( 'div' ).props ).toEqual( { + children: 'bar', + } ); + } ); + + it( 'uses memoized selector if dependencies do not change', () => { + registry.registerStore( 'testStore', { + reducer: () => ( { foo: 'bar' } ), + selectors: { + testSelector: ( state, key ) => state[ key ], + }, + } ); + + const selectSpyFoo = jest.fn().mockImplementation( () => 'foo' ); + const selectSpyBar = jest.fn().mockImplementation( () => 'bar' ); + const TestComponent = jest.fn().mockImplementation( + ( props ) => { + const mapSelect = props.change ? selectSpyFoo : selectSpyBar; + const data = useSelect( mapSelect, [ props.keyName ] ); + return <div>{ data }</div>; + } + ); + let renderer; + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent keyName="foo" change={ true } /> + </RegistryProvider> + ); + } ); + const testInstance = renderer.root; + + expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 ); + expect( selectSpyBar ).toHaveBeenCalledTimes( 0 ); + expect( TestComponent ).toHaveBeenCalledTimes( 1 ); + + // ensure expected state was rendered + expect( testInstance.findByType( 'div' ).props ).toEqual( { + children: 'foo', + } ); + + //rerender with non dependency changed + act( () => { + renderer.update( + <RegistryProvider value={ registry }> + <TestComponent keyName="foo" change={ false } /> + </RegistryProvider> + ); + } ); + + expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 ); + expect( selectSpyBar ).toHaveBeenCalledTimes( 0 ); + expect( TestComponent ).toHaveBeenCalledTimes( 2 ); + + // ensure expected state was rendered + expect( testInstance.findByType( 'div' ).props ).toEqual( { + children: 'foo', + } ); + + // rerender with dependency changed + // rerender with non dependency changed + act( () => { + renderer.update( + <RegistryProvider value={ registry }> + <TestComponent keyName="bar" change={ false } /> + </RegistryProvider> + ); + } ); + + expect( selectSpyFoo ).toHaveBeenCalledTimes( 2 ); + expect( selectSpyBar ).toHaveBeenCalledTimes( 1 ); + expect( TestComponent ).toHaveBeenCalledTimes( 3 ); + + // ensure expected state was rendered + expect( testInstance.findByType( 'div' ).props ).toEqual( { + children: 'bar', + } ); + } ); +} ); + diff --git a/packages/data/src/components/with-dispatch/test/index.js b/packages/data/src/components/with-dispatch/test/index.js index 98f225fad2c406..4bde9b30810ac1 100644 --- a/packages/data/src/components/with-dispatch/test/index.js +++ b/packages/data/src/components/with-dispatch/test/index.js @@ -8,7 +8,7 @@ import TestRenderer from 'react-test-renderer'; */ import withDispatch from '../'; import { createRegistry } from '../../../registry'; -import RegistryProvider from '../../registry-provider'; +import { RegistryProvider } from '../../registry-provider'; describe( 'withDispatch', () => { let registry; diff --git a/packages/data/src/components/with-select/index.js b/packages/data/src/components/with-select/index.js index d2fd4ae462b3bb..f9659e7e865a8f 100644 --- a/packages/data/src/components/with-select/index.js +++ b/packages/data/src/components/with-select/index.js @@ -1,18 +1,12 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; -import { createHigherOrderComponent } from '@wordpress/compose'; -import { createQueue } from '@wordpress/priority-queue'; +import { createHigherOrderComponent, pure } from '@wordpress/compose'; /** * Internal dependencies */ -import { RegistryConsumer } from '../registry-provider'; -import { AsyncModeConsumer } from '../async-mode-provider'; - -const renderQueue = createQueue(); +import useSelect from '../use-select'; /** * Higher-order component used to inject state-derived props using registered @@ -46,163 +40,27 @@ const renderQueue = createQueue(); * // * // <HammerPriceDisplay currency="USD" /> * ``` - * In the above example, when `HammerPriceDisplay` is rendered into an application, it will pass the price into the underlying `PriceDisplay` component and update automatically if the price of a hammer ever changes in the store. + * In the above example, when `HammerPriceDisplay` is rendered into an + * application, it will pass the price into the underlying `PriceDisplay` + * component and update automatically if the price of a hammer ever changes in + * the store. * * @return {Component} Enhanced component with merged state data props. */ -const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( WrappedComponent ) => { - /** - * Default merge props. A constant value is used as the fallback since it - * can be more efficiently shallow compared in case component is repeatedly - * rendered without its own merge props. - * - * @type {Object} - */ - const DEFAULT_MERGE_PROPS = {}; - - /** - * Given a props object, returns the next merge props by mapSelectToProps. - * - * @param {Object} props Props to pass as argument to mapSelectToProps. - * - * @return {Object} Props to merge into rendered wrapped element. - */ - function getNextMergeProps( props ) { - return ( - mapSelectToProps( props.registry.select, props.ownProps, props.registry ) || - DEFAULT_MERGE_PROPS - ); - } - - class ComponentWithSelect extends Component { - constructor( props ) { - super( props ); - - this.onStoreChange = this.onStoreChange.bind( this ); - - this.subscribe( props.registry ); - - this.mergeProps = getNextMergeProps( props ); - } - - componentDidMount() { - this.canRunSelection = true; - - // A state change may have occurred between the constructor and - // mount of the component (e.g. during the wrapped component's own - // constructor), in which case selection should be rerun. - if ( this.hasQueuedSelection ) { - this.hasQueuedSelection = false; - this.onStoreChange(); - } - } - - componentWillUnmount() { - this.canRunSelection = false; - this.unsubscribe(); - renderQueue.flush( this ); - } - - shouldComponentUpdate( nextProps, nextState ) { - // Cycle subscription if registry changes. - const hasRegistryChanged = nextProps.registry !== this.props.registry; - const hasSyncRenderingChanged = nextProps.isAsync !== this.props.isAsync; - - if ( hasRegistryChanged ) { - this.unsubscribe(); - this.subscribe( nextProps.registry ); - } - - if ( hasSyncRenderingChanged ) { - renderQueue.flush( this ); - } - - // Treat a registry change as equivalent to `ownProps`, to reflect - // `mergeProps` to rendered component if and only if updated. - const hasPropsChanged = ( - hasRegistryChanged || - ! isShallowEqualObjects( this.props.ownProps, nextProps.ownProps ) - ); - - // Only render if props have changed or merge props have been updated - // from the store subscriber. - if ( this.state === nextState && ! hasPropsChanged && ! hasSyncRenderingChanged ) { - return false; - } - - if ( hasPropsChanged || hasSyncRenderingChanged ) { - const nextMergeProps = getNextMergeProps( nextProps ); - if ( ! isShallowEqualObjects( this.mergeProps, nextMergeProps ) ) { - // If merge props change as a result of the incoming props, - // they should be reflected as such in the upcoming render. - // While side effects are discouraged in lifecycle methods, - // this component is used heavily, and prior efforts to use - // `getDerivedStateFromProps` had demonstrated miserable - // performance. - this.mergeProps = nextMergeProps; - } - - // Regardless whether merge props are changing, fall through to - // incur the render since the component will need to receive - // the changed `ownProps`. - } - - return true; +const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( + ( WrappedComponent ) => pure( + ( ownProps ) => { + const mapSelect = + ( select, registry ) => mapSelectToProps( + select, + ownProps, + registry + ); + const mergeProps = useSelect( mapSelect ); + return <WrappedComponent { ...ownProps } { ...mergeProps } />; } - - onStoreChange() { - if ( ! this.canRunSelection ) { - this.hasQueuedSelection = true; - return; - } - - const nextMergeProps = getNextMergeProps( this.props ); - if ( isShallowEqualObjects( this.mergeProps, nextMergeProps ) ) { - return; - } - - this.mergeProps = nextMergeProps; - - // Schedule an update. Merge props are not assigned to state since - // derivation of merge props from incoming props occurs within - // shouldComponentUpdate, where setState is not allowed. setState - // is used here instead of forceUpdate because forceUpdate bypasses - // shouldComponentUpdate altogether, which isn't desireable if both - // state and props change within the same render. Unfortunately, - // this requires that next merge props are generated twice. - this.setState( {} ); - } - - subscribe( registry ) { - this.unsubscribe = registry.subscribe( () => { - if ( this.props.isAsync ) { - renderQueue.add( this, this.onStoreChange ); - } else { - this.onStoreChange(); - } - } ); - } - - render() { - return <WrappedComponent { ...this.props.ownProps } { ...this.mergeProps } />; - } - } - - return ( ownProps ) => ( - <AsyncModeConsumer> - { ( isAsync ) => ( - <RegistryConsumer> - { ( registry ) => ( - <ComponentWithSelect - ownProps={ ownProps } - registry={ registry } - isAsync={ isAsync } - /> - ) } - </RegistryConsumer> - ) } - </AsyncModeConsumer> - ); -}, 'withSelect' ); + ), + 'withSelect' +); export default withSelect; diff --git a/packages/data/src/components/with-select/test/index.js b/packages/data/src/components/with-select/test/index.js index f42926e02266f7..3b2049826c63b5 100644 --- a/packages/data/src/components/with-select/test/index.js +++ b/packages/data/src/components/with-select/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import TestRenderer from 'react-test-renderer'; +import TestRenderer, { act } from 'react-test-renderer'; /** * WordPress dependencies @@ -15,7 +15,7 @@ import { Component } from '@wordpress/element'; import withSelect from '../'; import withDispatch from '../../with-dispatch'; import { createRegistry } from '../../../registry'; -import RegistryProvider from '../../registry-provider'; +import { RegistryProvider } from '../../registry-provider'; describe( 'withSelect', () => { let registry; @@ -47,15 +47,19 @@ describe( 'withSelect', () => { ) ); const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); - - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent keyName="reactKey" /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent keyName="reactKey" /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; - - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // Expected two times: + // - Once on initial render. + // - Once on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); // Wrapper is the enhanced component. Find props on the rendered child. @@ -100,31 +104,46 @@ describe( 'withSelect', () => { withDispatch( mapDispatchToProps ), ] )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( mapDispatchToProps ).toHaveBeenCalledTimes( 1 ); // Simulate a click on the button - testInstance.findByType( 'button' ).props.onClick(); + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); expect( testInstance.findByType( 'button' ).props.children ).toBe( 1 ); // 2 times = // 1. Initial mount // 2. When click handler is called expect( mapDispatchToProps ).toHaveBeenCalledTimes( 2 ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + // 4 times + // - 1 on initial render + // - 1 on effect before subscription set. + // - 1 on click triggering subscription firing. + // - 1 on rerender. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 4 ); + // verifies component only renders twice. expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); } ); - it( 'should rerun if had dispatched action during mount', () => { - registry.registerStore( 'counter', { + describe( 'expected behaviour when dispatching actions during mount', () => { + const testRegistry = createRegistry(); + testRegistry.registerStore( 'counter', { reducer: ( state = 0, action ) => { if ( action.type === 'increment' ) { return state + 1; @@ -140,6 +159,10 @@ describe( 'withSelect', () => { }, } ); + // @todo, Should we allow this behaviour? Side-effects + // on mount are discouraged in React (breaks Suspense and React Async Mode) + // leaving in place for now under the assumption there's current usage + // of withSelect in GB that expects support. class OriginalComponent extends Component { constructor( props ) { super( ...arguments ); @@ -156,10 +179,10 @@ describe( 'withSelect', () => { } } - jest.spyOn( OriginalComponent.prototype, 'render' ); + const renderSpy = jest.spyOn( OriginalComponent.prototype, 'render' ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select, ownProps ) => ( { - count: _select( 'counter' ).getCount( ownProps.offset ), + const mapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( { + count: _select( 'counter' ).getCount(), } ) ); const mapDispatchToProps = jest.fn().mockImplementation( ( _dispatch ) => ( { @@ -171,16 +194,39 @@ describe( 'withSelect', () => { withDispatch( mapDispatchToProps ), ] )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> + let testRenderer, testInstance; + const createTestRenderer = () => TestRenderer.create( + <RegistryProvider value={ testRegistry }> <DataBoundComponent /> </RegistryProvider> ); - const testInstance = testRenderer.root; - - expect( testInstance.findByType( 'div' ).props.children ).toBe( 2 ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); - expect( OriginalComponent.prototype.render ).toHaveBeenCalledTimes( 2 ); + act( () => { + testRenderer = createTestRenderer(); + } ); + testInstance = testRenderer.root; + it( 'should rerun if had dispatched action during mount', () => { + expect( testInstance.findByType( 'div' ).props.children ).toBe( 2 ); + // Expected 3 times because: + // - 1 on initial render + // - 1 on effect before subscription set. + // - 1 for the rerender because of the mapOutput change detected. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); + expect( renderSpy ).toHaveBeenCalledTimes( 2 ); + } ); + it( 'should rerun on unmount and mount', () => { + act( () => { + testRenderer.unmount(); + testRenderer = createTestRenderer(); + } ); + testInstance = testRenderer.root; + expect( testInstance.findByType( 'div' ).props.children ).toBe( 4 ); + // Expected an additional 3 times because of the unmount and remount: + // - 1 on initial render + // - 1 on effect before subscription set. + // - once for the rerender because of the mapOutput change detected. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 6 ); + expect( renderSpy ).toHaveBeenCalledTimes( 4 ); + } ); } ); it( 'should rerun selection on props changes', () => { @@ -207,24 +253,32 @@ describe( 'withSelect', () => { const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent offset={ 0 } /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent offset={ 0 } /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); - testRenderer.update( - <RegistryProvider value={ registry }> - <DataBoundComponent offset={ 10 } /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <DataBoundComponent offset={ 10 } /> + </RegistryProvider> + ); + } ); expect( testInstance.findByType( 'div' ).props.children ).toBe( 10 ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); } ); @@ -246,26 +300,34 @@ describe( 'withSelect', () => { const Parent = ( props ) => <DataBoundComponent propName={ props.propName } />; - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <Parent propName="foo" /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <Parent propName="foo" /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); - testRenderer.update( - <RegistryProvider value={ registry }> - <Parent propName="foo" /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <Parent propName="foo" /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); } ); - it( 'should not run selection if state has changed but merge props the same', () => { + it( 'should not rerender if state has changed but merge props the same', () => { registry.registerStore( 'demo', { reducer: () => ( {} ), selectors: { @@ -284,18 +346,23 @@ describe( 'withSelect', () => { const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); - TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent /> - </RegistryProvider> - ); + act( () => { + TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); registry.dispatch( 'demo' ).update(); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); } ); @@ -315,22 +382,30 @@ describe( 'withSelect', () => { withSelect( mapSelectToProps ), ] )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); - testRenderer.update( - <RegistryProvider value={ registry }> - <DataBoundComponent propName="foo" /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <DataBoundComponent propName="foo" /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); } ); @@ -350,18 +425,23 @@ describe( 'withSelect', () => { withSelect( mapSelectToProps ), ] )( OriginalComponent ); - TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent /> - </RegistryProvider> - ); + act( () => { + TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); store.dispatch( { type: 'dummy' } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); } ); @@ -384,26 +464,34 @@ describe( 'withSelect', () => { const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent propName="foo" /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent propName="foo" /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); expect( JSON.parse( testInstance.findByType( 'div' ).props.children ) ) .toEqual( { foo: 'OK', propName: 'foo' } ); - testRenderer.update( - <RegistryProvider value={ registry }> - <DataBoundComponent propName="bar" /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <DataBoundComponent propName="bar" /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); expect( JSON.parse( testInstance.findByType( 'div' ).props.children ) ) .toEqual( { bar: 'OK', propName: 'bar' } ); @@ -431,39 +519,49 @@ describe( 'withSelect', () => { const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <DataBoundComponent pass={ false } /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <DataBoundComponent pass={ false } /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); expect( testInstance.findByType( 'div' ).props.children ).toBe( 'Unknown' ); - testRenderer.update( - <RegistryProvider value={ registry }> - <DataBoundComponent pass /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <DataBoundComponent pass /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); expect( testInstance.findByType( 'div' ).props.children ).toBe( 'OK' ); - testRenderer.update( - <RegistryProvider value={ registry }> - <DataBoundComponent pass={ false } /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <DataBoundComponent pass={ false } /> + </RegistryProvider> + ); + } ); - expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); + expect( mapSelectToProps ).toHaveBeenCalledTimes( 4 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 3 ); expect( testInstance.findByType( 'div' ).props.children ).toBe( 'Unknown' ); } ); - it( 'should run selections on parents before its children', () => { + it( 'should limit unnecessary selections run on children', () => { registry.registerStore( 'childRender', { reducer: ( state = true, action ) => ( action.type === 'TOGGLE_RENDER' ? ! state : state @@ -477,9 +575,9 @@ describe( 'withSelect', () => { } ); const childMapSelectToProps = jest.fn(); - const parentMapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( { - isRenderingChild: _select( 'childRender' ).getValue(), - } ) ); + const parentMapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( + { isRenderingChild: _select( 'childRender' ).getValue() } + ) ); const ChildOriginalComponent = jest.fn().mockImplementation( () => <div /> ); const ParentOriginalComponent = jest.fn().mockImplementation( ( props ) => ( @@ -489,21 +587,32 @@ describe( 'withSelect', () => { const Child = withSelect( childMapSelectToProps )( ChildOriginalComponent ); const Parent = withSelect( parentMapSelectToProps )( ParentOriginalComponent ); - TestRenderer.create( - <RegistryProvider value={ registry }> - <Parent /> - </RegistryProvider> - ); + act( () => { + TestRenderer.create( + <RegistryProvider value={ registry }> + <Parent /> + </RegistryProvider> + ); + } ); - expect( childMapSelectToProps ).toHaveBeenCalledTimes( 1 ); - expect( parentMapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( childMapSelectToProps ).toHaveBeenCalledTimes( 2 ); + expect( parentMapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( ChildOriginalComponent ).toHaveBeenCalledTimes( 1 ); expect( ParentOriginalComponent ).toHaveBeenCalledTimes( 1 ); - registry.dispatch( 'childRender' ).toggleRender(); + act( () => { + registry.dispatch( 'childRender' ).toggleRender(); + } ); - expect( childMapSelectToProps ).toHaveBeenCalledTimes( 1 ); - expect( parentMapSelectToProps ).toHaveBeenCalledTimes( 2 ); + // 3 times because + // - 1 on initial render + // - 1 on effect before subscription set. + // - 1 child subscription fires. + expect( childMapSelectToProps ).toHaveBeenCalledTimes( 3 ); + expect( parentMapSelectToProps ).toHaveBeenCalledTimes( 4 ); expect( ChildOriginalComponent ).toHaveBeenCalledTimes( 1 ); expect( ParentOriginalComponent ).toHaveBeenCalledTimes( 2 ); } ); @@ -527,14 +636,20 @@ describe( 'withSelect', () => { const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ firstRegistry }> - <DataBoundComponent /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ firstRegistry }> + <DataBoundComponent /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; - expect( mapSelectToProps ).toHaveBeenCalledTimes( 1 ); + // 2 times: + // - 1 on initial render + // - 1 on effect before subscription set. + expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); expect( testInstance.findByType( 'div' ).props ).toEqual( { @@ -549,13 +664,19 @@ describe( 'withSelect', () => { }, } ); - testRenderer.update( - <RegistryProvider value={ secondRegistry }> - <DataBoundComponent /> - </RegistryProvider> - ); - - expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); + act( () => { + testRenderer.update( + <RegistryProvider value={ secondRegistry }> + <DataBoundComponent /> + </RegistryProvider> + ); + } ); + // 4 times: + // - 1 on initial render + // - 1 on effect before subscription set. + // - 1 on re-render + // - 1 on effect before new subscription set (because registry has changed) + expect( mapSelectToProps ).toHaveBeenCalledTimes( 4 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); expect( testInstance.findByType( 'div' ).props ).toEqual( { diff --git a/packages/data/src/index.js b/packages/data/src/index.js index c6b941d2cec954..46c830289b91d7 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -12,8 +12,15 @@ import * as plugins from './plugins'; export { default as withSelect } from './components/with-select'; export { default as withDispatch } from './components/with-dispatch'; export { default as withRegistry } from './components/with-registry'; -export { default as RegistryProvider, RegistryConsumer } from './components/registry-provider'; -export { default as __experimentalAsyncModeProvider } from './components/async-mode-provider'; +export { + RegistryProvider, + RegistryConsumer, + useRegistry, +} from './components/registry-provider'; +export { default as useSelect } from './components/use-select'; +export { + AsyncModeProvider as __experimentalAsyncModeProvider, +} from './components/async-mode-provider'; export { createRegistry } from './registry'; export { plugins }; export { createRegistrySelector, createRegistryControl } from './factory'; diff --git a/packages/viewport/src/test/if-viewport-matches.js b/packages/viewport/src/test/if-viewport-matches.js index c5eca13c0ea14d..4a57039e9c7f1b 100644 --- a/packages/viewport/src/test/if-viewport-matches.js +++ b/packages/viewport/src/test/if-viewport-matches.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import TestRenderer from 'react-test-renderer'; +import TestRenderer, { act } from 'react-test-renderer'; /** * WordPress dependencies @@ -20,16 +20,24 @@ describe( 'ifViewportMatches()', () => { it( 'should not render if query does not match', () => { dispatch( 'core/viewport' ).setIsMatching( { '> wide': false } ); const EnhancedComponent = ifViewportMatches( '> wide' )( Component ); - const testRenderer = TestRenderer.create( <EnhancedComponent /> ); + + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( <EnhancedComponent /> ); + } ); expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( 0 ); } ); it( 'should render if query does match', () => { - dispatch( 'core/viewport' ).setIsMatching( { '> wide': true } ); + act( () => { + dispatch( 'core/viewport' ).setIsMatching( { '> wide': true } ); + } ); const EnhancedComponent = ifViewportMatches( '> wide' )( Component ); - const testRenderer = TestRenderer.create( <EnhancedComponent /> ); - + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( <EnhancedComponent /> ); + } ); expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( 1 ); } ); } ); From d2e4c57d9d02fbffc0c756b1937451c43bb81e75 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 27 May 2019 22:59:48 -0700 Subject: [PATCH 201/664] Add ESNext example in i18n docs (#15828) --- .../developers/internationalization.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/designers-developers/developers/internationalization.md b/docs/designers-developers/developers/internationalization.md index 084459697cff3b..fa30d0b14cc7e9 100644 --- a/docs/designers-developers/developers/internationalization.md +++ b/docs/designers-developers/developers/internationalization.md @@ -36,6 +36,8 @@ add_action( 'init', 'myguten_block_init' ); In your code, you can include the i18n functions. The most common function is **__** (a double underscore) which provides translation of a simple string. Here is a basic static block example, this is in a file called `block.js`: +{% codetabs %} +{% ES5 %} ```js const { __ } = wp.i18n; const el = wp.element.createElement; @@ -62,6 +64,33 @@ registerBlockType( 'myguten/simple', { } }); ``` +{% ESNext %} +```js +const { __ } = wp.i18n; +const { registerBlockType } = wp.blocks; + +registerBlockType( 'myguten/simple', { + title: __('Simple Block', 'myguten'), + category: 'widgets', + + edit: () => { + return ( + <p style="color:red"> + { __('Hello World', 'myguten') } + </p> + ); + }, + + save: () => { + return ( + <p style="color:red"> + { __('Hello World', 'myguten') } + </p> + ); + } +}); +``` +{% end %} In the above example, the function will use the first argument for the string to be translated. The second argument is the text domain which must match the text domain slug specified by your plugin. From 81a8b1a07a2feccbeb21ef40fead241a2ea61020 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Tue, 28 May 2019 11:39:34 +0300 Subject: [PATCH 202/664] Disable video, quote blocks but for development (#15856) --- packages/edit-post/src/index.native.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 6ada9eb995eadd..cc5dfaec90ae1d 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -23,6 +23,8 @@ export function initializeEditor() { if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); unregisterBlockType( 'core/more' ); + unregisterBlockType( 'core/video' ); + unregisterBlockType( 'core/quote' ); } } From b14d70c3529d0f4864e16ac48255cbdef4793a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 28 May 2019 14:23:32 +0200 Subject: [PATCH 203/664] Performance: add e2e tests for load and typing time (#14506) --- package.json | 1 + packages/e2e-tests/assets/large-post.html | 3989 +++++++++++++++++ .../e2e-tests/config/performance-reporter.js | 29 + packages/e2e-tests/jest.config.js | 3 + packages/e2e-tests/jest.performance.config.js | 23 + packages/e2e-tests/specs/.gitignore | 1 + packages/e2e-tests/specs/performance.test.js | 72 + 7 files changed, 4118 insertions(+) create mode 100644 packages/e2e-tests/assets/large-post.html create mode 100644 packages/e2e-tests/config/performance-reporter.js create mode 100644 packages/e2e-tests/jest.performance.config.js create mode 100644 packages/e2e-tests/specs/.gitignore create mode 100644 packages/e2e-tests/specs/performance.test.js diff --git a/package.json b/package.json index 78d57862f8c228..2d4e39666f7fbf 100644 --- a/package.json +++ b/package.json @@ -193,6 +193,7 @@ "pretest-e2e": "cross-env SCRIPT_DEBUG=false ./bin/reset-local-e2e-tests.sh", "test-e2e": "wp-scripts test-e2e --config packages/e2e-tests/jest.config.js", "test-e2e:watch": "npm run test-e2e -- --watch", + "test-performance": "wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js", "test-php": "npm run lint-php && npm run test-unit-php", "test-unit": "wp-scripts test-unit-js --config test/unit/jest.config.js", "test-unit:update": "npm run test-unit -- --updateSnapshot", diff --git a/packages/e2e-tests/assets/large-post.html b/packages/e2e-tests/assets/large-post.html new file mode 100644 index 00000000000000..16a314c5ba60f3 --- /dev/null +++ b/packages/e2e-tests/assets/large-post.html @@ -0,0 +1,3989 @@ +<!-- wp:heading {"level":1} --> +<h1>At iam decimum annum in spelunca iacet.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sit enim idem caecus, debilis.</a> Potius inflammat, ut coercendi magis quam dedocendi esse videantur. Quis non odit sordidos, vanos, leves, futtiles? Age sane, inquam. Duo Reges: constructio interrete. Nam ante Aristippus, et ille melius. Sed quamvis comis in amicis tuendis fuerit, tamen, si haec vera sunt-nihil enim affirmo-, non satis acutus fuit. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>At habetur! Et ego id scilicet nesciebam! Sed ut sit, etiamne post mortem coletur?</li><li>Totum genus hoc Zeno et qui ab eo sunt aut non potuerunt aut noluerunt, certe reliquerunt.</li><li>At quicum ioca seria, ut dicitur, quicum arcana, quicum occulta omnia?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non igitur bene.</a> Quod si ita se habeat, non possit beatam praestare vitam sapientia. Universa enim illorum ratione cum tota vestra confligendum puto. Atqui reperies, inquit, in hoc quidem pertinacem; Haec para/doca illi, nos admirabilia dicamus. Quod cum ille dixisset et satis disputatum videretur, in oppidum ad Pomponium perreximus omnes. <strong>Estne, quaeso, inquam, sitienti in bibendo voluptas?</strong> Egone non intellego, quid sit don Graece, Latine voluptas? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Nummus in Croesi divitiis obscuratur, pars est tamen divitiarum.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. De vacuitate doloris eadem sententia erit. Ratio quidem vestra sic cogit. Sit, inquam, tam facilis, quam vultis, comparatio voluptatis, quid de dolore dicemus? Si longus, levis dictata sunt. <em>Sed ea mala virtuti magnitudine obruebantur.</em> Non quam nostram quidem, inquit Pomponius iocans; Tecum optime, deinde etiam cum mediocri amico. At ille pellit, qui permulcet sensum voluptate. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Haec quo modo conveniant, non sane intellego. Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia? <strong>Duo enim genera quae erant, fecit tria.</strong> Quae contraria sunt his, malane? Sed hoc sane concedamus. Re mihi non aeque satisfacit, et quidem locis pluribus. Quantam rem agas, ut Circeis qui habitet totum hunc mundum suum municipium esse existimet? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nulla erit controversia.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quid enim necesse est, tamquam meretricem in matronarum coetum, sic voluptatem in virtutum concilium adducere? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":5} --> +<h5>Atqui reperies, inquit, in hoc quidem pertinacem;</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Immo istud quidem, inquam, quo loco quidque, nisi iniquum postulo, arbitratu meo. Nunc haec primum fortasse audientis servire debemus. Tamen a proposito, inquam, aberramus. <em>Quae est igitur causa istarum angustiarum?</em> Pollicetur certe. Nihil enim hoc differt. <em>Iam enim adesse poterit.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nobis aliter videtur, recte secusne, postea;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Efficiens dici potest.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Prodest, inquit, mihi eo esse animo. Est enim effectrix multarum et magnarum voluptatum. Quo tandem modo? Sed tu istuc dixti bene Latine, parum plane. Quo invento omnis ab eo quasi capite de summo bono et malo disputatio ducitur. <em>Quantum Aristoxeni ingenium consumptum videmus in musicis?</em> Tecum optime, deinde etiam cum mediocri amico. Nos paucis ad haec additis finem faciamus aliquando; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Non enim ipsa genuit hominem, sed accepit a natura inchoatum.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Memini me adesse P. Qua igitur re ab deo vincitur, si aeternitate non vincitur? Atque his de rebus et splendida est eorum et illustris oratio. Quod non faceret, si in voluptate summum bonum poneret. Cur post Tarentum ad Archytam? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis negat? Quod autem satis est, eo quicquid accessit, nimium est; <em>Est enim effectrix multarum et magnarum voluptatum.</em> Sin ea non neglegemus neque tamen ad finem summi boni referemus, non multum ab Erilli levitate aberrabimus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">An vero, inquit, quisquam potest probare, quod perceptfum, quod.</a> <strong>At coluit ipse amicitias.</strong> Tecum optime, deinde etiam cum mediocri amico. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid de Pythagora?</a> Itaque hic ipse iam pridem est reiectus; Quae cum praeponunt, ut sit aliqua rerum selectio, naturam videntur sequi; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quia dolori non voluptas contraria est, sed doloris privatio.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Inde sermone vario sex illa a Dipylo stadia confecimus. Cuius quidem, quoniam Stoicus fuit, sententia condemnata mihi videtur esse inanitas ista verborum. Bonum valitudo: miser morbus. Itaque rursus eadem ratione, qua sum paulo ante usus, haerebitis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Zenonis est, inquam, hoc Stoici.</a> Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta. Si est nihil nisi corpus, summa erunt illa: valitudo, vacuitas doloris, pulchritudo, cetera. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ita relinquet duas, de quibus etiam atque etiam consideret.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Mihi enim erit isdem istis fortasse iam utendum.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>An potest, inquit ille, quicquam esse suavius quam nihil dolere? Venit enim mihi Platonis in mentem, quem accepimus primum hic disputare solitum; Quae quidem sapientes sequuntur duce natura tamquam videntes; Sed tamen intellego quid velit. <em>Sed ad rem redeamus;</em> Ea possunt paria non esse. Mene ergo et Triarium dignos existimas, apud quos turpiter loquare? Summus dolor plures dies manere non potest? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nihil opus est exemplis hoc facere longius. Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? Itaque in rebus minime obscuris non multus est apud eos disserendi labor. Sed in rebus apertissimis nimium longi sumus. Nam, ut paulo ante docui, augendae voluptatis finis est doloris omnis amotio. Iam id ipsum absurdum, maximum malum neglegi. <strong>Sed haec omittamus;</strong> Quo modo autem philosophus loquitur? Te ipsum, dignissimum maioribus tuis, voluptasne induxit, ut adolescentulus eriperes P. Terram, mihi crede, ea lanx et maria deprimet. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nam et a te perfici istam disputationem volo, nec tua mihi oratio longa videri potest. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Alterum significari idem, ut si diceretur, officia media omnia aut pleraque servantem vivere.</li><li>Quid enim tanto opus est instrumento in optimis artibus comparandis?</li><li>At cum de plurimis eadem dicit, tum certe de maximis.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Scio enim esse quosdam, qui quavis lingua philosophari possint; Theophrastus mediocriterne delectat, cum tractat locos ab Aristotele ante tractatos? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ille incendat?</a> Ex eorum enim scriptis et institutis cum omnis doctrina liberalis, omnis historia. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae duo sunt, unum facit.</a> Deinde prima illa, quae in congressu solemus: Quid tu, inquit, huc? </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Praeclare, inquit, facis, cum et eorum memoriam tenes, quorum uterque tibi testamento liberos suos commendavit, et puerum diligis. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed nimis multa.</a> Tu enim ista lenius, hic Stoicorum more nos vexat. <em>Primum in nostrane potestate est, quid meminerimus?</em> Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. Sed quid attinet de rebus tam apertis plura requirere? Cur igitur, inquam, res tam dissimiles eodem nomine appellas? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Magna laus.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Dici enim nihil potest verius. Satisne ergo pudori consulat, si quis sine teste libidini pareat? Scio enim esse quosdam, qui quavis lingua philosophari possint; Quod dicit Epicurus etiam de voluptate, quae minime sint voluptates, eas obscurari saepe et obrui. Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur. Quamquam te quidem video minime esse deterritum. Quae adhuc, Cato, a te dicta sunt, eadem, inquam, dicere posses, si sequerere Pyrrhonem aut Aristonem. Mihi, inquam, qui te id ipsum rogavi? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis est, qui non oderit libidinosam, protervam adolescentiam? Quae animi affectio suum cuique tribuens atque hanc, quam dico. Faceres tu quidem, Torquate, haec omnia; An tu me de L. Quamquam te quidem video minime esse deterritum. Me igitur ipsum ames oportet, non mea, si veri amici futuri sumus. Ita ne hoc quidem modo paria peccata sunt. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod quidem iam fit etiam in Academia.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>An dolor longissimus quisque miserrimus, voluptatem non optabiliorem diuturnitas facit?</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Miserum hominem! Si dolor summum malum est, dici aliter non potest. Aut, Pylades cum sis, dices te esse Orestem, ut moriare pro amico? Sed quanta sit alias, nunc tantum possitne esse tanta. Ac tamen hic mallet non dolere. Atque haec coniunctio confusioque virtutum tamen a philosophis ratione quadam distinguitur. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Completur enim et ex eo genere vitae, quod virtute fruitur, et ex iis rebus, quae sunt secundum naturam neque sunt in nostra potestate. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":4} --> +<h4>Hoc est dicere: Non reprehenderem asotos, si non essent asoti.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">At enim hic etiam dolore.</a> Hanc ergo intuens debet institutum illud quasi signum absolvere. Certe, nisi voluptatem tanti aestimaretis. At quanta conantur! Mundum hunc omnem oppidum esse nostrum! Incendi igitur eos, qui audiunt, vides. Et non ex maxima parte de tota iudicabis? <em>Sed ego in hoc resisto;</em> Roges enim Aristonem, bonane ei videantur haec: vacuitas doloris, divitiae, valitudo; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Stulti autem malorum memoria torquentur, sapientes bona praeterita grata recordatione renovata delectant. Sunt autem, qui dicant foedus esse quoddam sapientium, ut ne minus amicos quam se ipsos diligant. Nescio quo modo praetervolavit oratio. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Memini vero, inquam;</a> Quae hic rei publicae vulnera inponebat, eadem ille sanabat. Omnis enim est natura diligens sui. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Etsi qui potest intellegi aut cogitari esse aliquod animal, quod se oderit?</li><li>Summum ením bonum exposuit vacuitatem doloris;</li><li>Satisne ergo pudori consulat, si quis sine teste libidini pareat?</li><li>Quamquam haec quidem praeposita recte et reiecta dicere licebit.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Quem si tenueris, non modo meum Ciceronem, sed etiam me ipsum abducas licebit. Quem ad modum quis ambulet, sedeat, qui ductus oris, qui vultus in quoque sit? Sint ista Graecorum; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At hoc in eo M.</a> <strong>Qui est in parvis malis.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Qui ita affectus, beatum esse numquam probabis;</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Multoque hoc melius nos veriusque quam Stoici.</strong> Eaedem res maneant alio modo. Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. Hic ego: Pomponius quidem, inquam, noster iocari videtur, et fortasse suo iure. Hinc ceteri particulas arripere conati suam quisque videro voluit afferre sententiam. Cum autem venissemus in Academiae non sine causa nobilitata spatia, solitudo erat ea, quam volueramus. Neque enim civitas in seditione beata esse potest nec in discordia dominorum domus; Sed erat aequius Triarium aliquid de dissensione nostra iudicare. Morbo gravissimo affectus, exul, orbus, egens, torqueatur eculeo: quem hunc appellas, Zeno? Ea possunt paria non esse. Itaque sensibus rationem adiunxit et ratione effecta sensus non reliquit. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Si est nihil nisi corpus, summa erunt illa: valitudo, vacuitas doloris, pulchritudo, cetera. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Utrum igitur percurri omnem Epicuri disciplinam placet an de una voluptate quaeri, de qua omne certamen est? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Atqui reperiemus asotos primum ita non religiosos, ut edint de patella, deinde ita mortem non timentes, ut illud in ore habeant ex Hymnide: Mihi sex menses satis sunt vitae, septimum Orco spondeo. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><strong>Quorum altera prosunt, nocent altera.</strong> Virtutis, magnitudinis animi, patientiae, fortitudinis fomentis dolor mitigari solet. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur deinde Metrodori liberos commendas?</a> Quam ob rem tandem, inquit, non satisfacit? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Pauca mutat vel plura sane;</a> Et quidem Arcesilas tuus, etsi fuit in disserendo pertinacior, tamen noster fuit; Velut ego nunc moveor. Ergo ita: non posse honeste vivi, nisi honeste vivatur? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Multoque hoc melius nos veriusque quam Stoici.</a> Mihi, inquam, qui te id ipsum rogavi? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nam quid possumus facere melius?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qua tu etiam inprudens utebare non numquam.</a> <strong>Sit sane ista voluptas.</strong> Non est enim vitium in oratione solum, sed etiam in moribus. Nec mihi illud dixeris: Haec enim ipsa mihi sunt voluptati, et erant illa Torquatis. <strong>Non dolere, inquam, istud quam vim habeat postea videro;</strong> Tu quidem reddes; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nonne videmus quanta perturbatio rerum omnium consequatur, quanta confusio?</a> <em>Falli igitur possumus.</em> Non ego tecum iam ita iocabor, ut isdem his de rebus, cum L. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Tibi hoc incredibile, quod beatissimum.</em> <em>Quid censes in Latino fore?</em> Aut unde est hoc contritum vetustate proverbium: quicum in tenebris? Aliter enim explicari, quod quaeritur, non potest. Uterque enim summo bono fruitur, id est voluptate. Dolere malum est: in crucem qui agitur, beatus esse non potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Miserum hominem! Si dolor summum malum est, dici aliter non potest. Quis est, qui non oderit libidinosam, protervam adolescentiam? Verum tamen cum de rebus grandioribus dicas, ipsae res verba rapiunt; <em>Sit enim idem caecus, debilis.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quare istam quoque aggredere tractatam praesertim et ab aliis et a te ipso saepe, ut tibi deesse non possit oratio. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Sin autem est in ea, quod quidam volunt, nihil impedit hanc nostram comprehensionem summi boni. Nec vero pietas adversus deos nec quanta iis gratia debeatur sine explicatione naturae intellegi potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Istam voluptatem perpetuam quis potest praestare sapienti? <em>Et ille ridens: Video, inquit, quid agas;</em> Primum divisit ineleganter; Traditur, inquit, ab Epicuro ratio neglegendi doloris. Aliter homines, aliter philosophos loqui putas oportere? In schola desinis. Aliter enim explicari, quod quaeritur, non potest. Ergo opifex plus sibi proponet ad formarum quam civis excellens ad factorum pulchritudinem? Proclivi currit oratio. Quod autem ratione actum est, id officium appellamus. Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur post Tarentum ad Archytam?</a> Quae quidem sapientes sequuntur duce natura tamquam videntes; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Gloriosa ostentatio in constituendo summo bono.</a> Tecum optime, deinde etiam cum mediocri amico. Philosophi autem in suis lectulis plerumque moriuntur. Haec igitur Epicuri non probo, inquam. Ad quorum et cognitionem et usum iam corroborati natura ipsa praeeunte deducimur. <strong>Quid ait Aristoteles reliquique Platonis alumni?</strong> Audax negotium, dicerem impudens, nisi hoc institutum postea translatum ad philosophos nostros esset. At miser, si in flagitiosa et vitiosa vita afflueret voluptatibus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quodsi ipsam honestatem undique pertectam atque absolutam. Eam tum adesse, cum dolor omnis absit; Polycratem Samium felicem appellabant. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Ubi ut eam caperet aut quando?</li><li>Quod autem ratione actum est, id officium appellamus.</li><li>Tu enim ista lenius, hic Stoicorum more nos vexat.</li><li>Qui igitur convenit ab alia voluptate dicere naturam proficisci, in alia summum bonum ponere?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si qua in iis corrigere voluit, deteriora fecit.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae cum dixisset paulumque institisset, Quid est?</a> Primum cur ista res digna odio est, nisi quod est turpis? Beatus autem esse in maximarum rerum timore nemo potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nos cum te, M. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Me igitur ipsum ames oportet, non mea, si veri amici futuri sumus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur deinde Metrodori liberos commendas?</a> Que Manilium, ab iisque M. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum incolumis acies: misera caecitas.</a> Quodsi ipsam honestatem undique pertectam atque absolutam. Collatio igitur ista te nihil iuvat. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Beatus autem esse in maximarum rerum timore nemo potest.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. Cetera illa adhibebat, quibus demptis negat se Epicurus intellegere quid sit bonum. Nam si amitti vita beata potest, beata esse non potest. Illa tamen simplicia, vestra versuta. Neminem videbis ita laudatum, ut artifex callidus comparandarum voluptatum diceretur. Sed in rebus apertissimis nimium longi sumus. Peccata paria. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">An haec ab eo non dicuntur?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tu autem negas fortem esse quemquam posse, qui dolorem malum putet. <strong>Nunc vides, quid faciat.</strong> Mihi enim satis est, ipsis non satis. Atque his de rebus et splendida est eorum et illustris oratio. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Vestri haec verecundius, illi fortasse constantius.</a> Quid enim necesse est, tamquam meretricem in matronarum coetum, sic voluptatem in virtutum concilium adducere? <em>Nihil opus est exemplis hoc facere longius.</em> Sed ad rem redeamus; Sed ea mala virtuti magnitudine obruebantur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae cum dixisset, finem ille.</a> Ut aliquid scire se gaudeant? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Hoc simile tandem est?</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Idem iste, inquam, de voluptate quid sentit? Omnia contraria, quos etiam insanos esse vultis. Scio enim esse quosdam, qui quavis lingua philosophari possint; Multoque hoc melius nos veriusque quam Stoici. Utrum igitur tibi litteram videor an totas paginas commovere? <strong>Dici enim nihil potest verius.</strong> Ex ea difficultate illae fallaciloquae, ut ait Accius, malitiae natae sunt. Rhetorice igitur, inquam, nos mavis quam dialectice disputare? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>An potest, inquit ille, quicquam esse suavius quam nihil dolere? <strong>Ea possunt paria non esse.</strong> Nos quidem Virtutes sic natae sumus, ut tibi serviremus, aliud negotii nihil habemus. Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit? Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Nam illud vehementer repugnat, eundem beatum esse et multis malis oppressum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Hoc dixerit potius Ennius: Nimium boni est, cui nihil est mali.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quorum altera prosunt, nocent altera. Dat enim intervalla et relaxat. Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. Hanc quoque iucunditatem, si vis, transfer in animum; Quare attende, quaeso. Eam tum adesse, cum dolor omnis absit; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Fortasse id optimum, sed ubi illud: Plus semper voluptatis?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Minime vero, inquit ille, consentit.</a> <strong>Confecta res esset.</strong> Idne consensisse de Calatino plurimas gentis arbitramur, primarium populi fuisse, quod praestantissimus fuisset in conficiendis voluptatibus? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Post enim Chrysippum eum non sane est disputatum.</a> Pudebit te, inquam, illius tabulae, quam Cleanthes sane commode verbis depingere solebat. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed tu istuc dixti bene Latine, parum plane.</a> <strong>Falli igitur possumus.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Semper enim ex eo, quod maximas partes continet latissimeque funditur, tota res appellatur. Sed haec nihil sane ad rem; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Confecta res esset.</a> Quare conare, quaeso. Quae autem natura suae primae institutionis oblita est? Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>In quo etsi est magnus, tamen nova pleraque et perpauca de moribus. Te ipsum, dignissimum maioribus tuis, voluptasne induxit, ut adolescentulus eriperes P. Hoc est dicere: Non reprehenderem asotos, si non essent asoti. An me, inquis, tam amentem putas, ut apud imperitos isto modo loquar? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Cuius quidem, quoniam Stoicus fuit, sententia condemnata mihi videtur esse inanitas ista verborum. <strong>Sed ego in hoc resisto;</strong> At, si voluptas esset bonum, desideraret. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta. Hoc loco tenere se Triarius non potuit. Cum id quoque, ut cupiebat, audivisset, evelli iussit eam, qua erat transfixus, hastam. Beatus sibi videtur esse moriens. Ergo id est convenienter naturae vivere, a natura discedere. Primum in nostrane potestate est, quid meminerimus? </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ex quo intellegi debet homini id esse in bonis ultimum, secundum naturam vivere, quod ita interpretemur: vivere ex hominis natura undique perfecta et nihil requirente. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Itaque rursus eadem ratione, qua sum paulo ante usus, haerebitis. Hoc loco tenere se Triarius non potuit. Quae similitudo in genere etiam humano apparet. Quid ergo hoc loco intellegit honestum? Satis est ad hoc responsum. <em>Quod ea non occurrentia fingunt, vincunt Aristonem;</em> Eam si varietatem diceres, intellegerem, ut etiam non dicente te intellego; Quae cum essent dicta, discessimus. Neminem videbis ita laudatum, ut artifex callidus comparandarum voluptatum diceretur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Est enim effectrix multarum et magnarum voluptatum.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Themistocles quidem, cum ei Simonides an quis alius artem memoriae polliceretur, Oblivionis, inquit, mallem.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tria genera bonorum;</a> Neminem videbis ita laudatum, ut artifex callidus comparandarum voluptatum diceretur. Nam de isto magna dissensio est. <em>Dicimus aliquem hilare vivere;</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Universa enim illorum ratione cum tota vestra confligendum puto.</li><li>Iam enim adesse poterit.</li><li>Haec et tu ita posuisti, et verba vestra sunt.</li><li>Utrum igitur tibi litteram videor an totas paginas commovere?</li><li>Haec bene dicuntur, nec ego repugno, sed inter sese ipsa pugnant.</li><li>Quod si ita sit, cur opera philosophiae sit danda nescio.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nihil opus est exemplis hoc facere longius.</a> Aliena dixit in physicis nec ea ipsa, quae tibi probarentur; Quid, quod res alia tota est? Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? <em>Nullus est igitur cuiusquam dies natalis.</em> Aut haec tibi, Torquate, sunt vituperanda aut patrocinium voluptatis repudiandum. Sed ille, ut dixi, vitiose. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quonam, inquit, modo?</a> Eodem modo is enim tibi nemo dabit, quod, expetendum sit, id esse laudabile. Et ego: Piso, inquam, si est quisquam, qui acute in causis videre soleat quae res agatur. <em>Immo videri fortasse.</em> Etenim semper illud extra est, quod arte comprehenditur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">De quibus cupio scire quid sentias.</a> At ego quem huic anteponam non audeo dicere; Quid, cum fictas fabulas, e quibus utilitas nulla elici potest, cum voluptate legimus? Nam Metrodorum non puto ipsum professum, sed, cum appellaretur ab Epicuro, repudiare tantum beneficium noluisse; Falli igitur possumus. Magni enim aestimabat pecuniam non modo non contra leges, sed etiam legibus partam. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Idemne, quod iucunde?</a> Si alia sentit, inquam, alia loquitur, numquam intellegam quid sentiat; Et quod est munus, quod opus sapientiae? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At, si voluptas esset bonum, desideraret.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Etsi qui potest intellegi aut cogitari esse aliquod animal, quod se oderit? Huic mori optimum esse propter desperationem sapientiae, illi propter spem vivere. Non pugnem cum homine, cur tantum habeat in natura boni; Sed ea mala virtuti magnitudine obruebantur. Ille incendat? Suam denique cuique naturam esse ad vivendum ducem. Nam de isto magna dissensio est. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Verum hoc idem saepe faciamus. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed fortuna fortis;</a> Nam diligi et carum esse iucundum est propterea, quia tutiorem vitam et voluptatem pleniorem efficit. Hoc ipsum elegantius poni meliusque potuit. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Quid igitur dubitamus in tota eius natura quaerere quid sit effectum?</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Sed nonne merninisti licere mihi ista probare, quae sunt a te dicta? Beatus autem esse in maximarum rerum timore nemo potest. Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. <strong>Minime vero istorum quidem, inquit.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Odium autem et invidiam facile vitabis. Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. <strong>Ego vero isti, inquam, permitto.</strong> <strong>Comprehensum, quod cognitum non habet?</strong> Quod non faceret, si in voluptate summum bonum poneret. Non enim, si omnia non sequebatur, idcirco non erat ortus illinc. Rapior illuc, revocat autem Antiochus, nec est praeterea, quem audiamus. Aliter enim explicari, quod quaeritur, non potest. <em>Itaque hic ipse iam pridem est reiectus;</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quod idem cum vestri faciant, non satis magnam tribuunt inventoribus gratiam.</li><li>Quae enim adhuc protulisti, popularia sunt, ego autem a te elegantiora desidero.</li><li>Si longus, levis dictata sunt.</li><li>Sed virtutem ipsam inchoavit, nihil amplius.</li><li>Suo genere perveniant ad extremum;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Omnia contraria, quos etiam insanos esse vultis. Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. Murenam te accusante defenderem. Quam tu ponis in verbis, ego positam in re putabam. Est enim tanti philosophi tamque nobilis audacter sua decreta defendere. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sit enim idem caecus, debilis.</a> Eaedem enim utilitates poterunt eas labefactare atque pervertere. Quid enim de amicitia statueris utilitatis causa expetenda vides. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si id dicis, vicimus.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod dicit Epicurus etiam de voluptate, quae minime sint voluptates, eas obscurari saepe et obrui. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Omnesque eae sunt genere quattuor, partibus plures, aegritudo, formido, libido, quamque Stoici communi nomine corporis et animi ¾donÆn appellant, ego malo laetitiam appellare, quasi gestientis animi elationem voluptariam. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nec enim figura corporis nec ratio excellens ingenii humani significat ad unam hanc rem natum hominem, ut frueretur voluptatibus. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Quod si ita sit, cur opera philosophiae sit danda nescio. Scrupulum, inquam, abeunti; Superiores tres erant, quae esse possent, quarum est una sola defensa, eaque vehementer. Tria genera cupiditatum, naturales et necessariae, naturales et non necessariae, nec naturales nec necessariae. In qua quid est boni praeter summam voluptatem, et eam sempiternam? <em>Mihi enim erit isdem istis fortasse iam utendum.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Utilitatis causa amicitia est quaesita.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Videmus igitur ut conquiescere ne infantes quidem possint.</a> Sic enim maiores nostri labores non fugiendos tristissimo tamen verbo aerumnas etiam in deo nominaverunt. Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. <strong>Sed haec omittamus;</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae ista amicitia est?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod enim testimonium maius quaerimus, quae honesta et recta sint, ipsa esse optabilia per sese, cum videamus tanta officia morientis? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Tamen a proposito, inquam, aberramus. Id mihi magnum videtur. Non risu potius quam oratione eiciendum? Idem iste, inquam, de voluptate quid sentit? Ego quoque, inquit, didicerim libentius si quid attuleris, quam te reprehenderim. At ille pellit, qui permulcet sensum voluptate. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tamen a proposito, inquam, aberramus. Gerendus est mos, modo recte sentiat. Cum praesertim illa perdiscere ludus esset. <em>Sed quanta sit alias, nunc tantum possitne esse tanta.</em> Itaque hic ipse iam pridem est reiectus; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nihil opus est exemplis hoc facere longius. Eam tum adesse, cum dolor omnis absit; Inscite autem medicinae et gubernationis ultimum cum ultimo sapientiae comparatur. Vide igitur ne non debeas verbis nostris uti, sententiis tuis. Ad eas enim res ab Epicuro praecepta dantur. Eodem modo is enim tibi nemo dabit, quod, expetendum sit, id esse laudabile. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nam Pyrrho, Aristo, Erillus iam diu abiecti. Itaque ad tempus ad Pisonem omnes. Omnia contraria, quos etiam insanos esse vultis. At certe gravius. Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At, si voluptas esset bonum, desideraret.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Sed erat aequius Triarium aliquid de dissensione nostra iudicare.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quasi ego id curem, quid ille aiat aut neget. Bestiarum vero nullum iudicium puto. <em>Restatis igitur vos;</em> <em>Summum ením bonum exposuit vacuitatem doloris;</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quare ad ea primum, si videtur;</a> Erat enim Polemonis. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Disserendi artem nullam habuit.</li><li>Ita relinquet duas, de quibus etiam atque etiam consideret.</li><li>Et certamen honestum et disputatio splendida! omnis est enim de virtutis dignitate contentio.</li><li>Transfer idem ad modestiam vel temperantiam, quae est moderatio cupiditatum rationi oboediens.</li><li>Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus;</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nemo enim est, qui aliter dixerit quin omnium naturarum simile esset id, ad quod omnia referrentur, quod est ultimum rerum appetendarum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":3} --> +<h3>Qui enim existimabit posse se miserum esse beatus non erit.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quod non faceret, si in voluptate summum bonum poneret. Qui enim voluptatem ipsam contemnunt, iis licet dicere se acupenserem maenae non anteponere. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Inde sermone vario sex illa a Dipylo stadia confecimus.</a> Mihi, inquam, qui te id ipsum rogavi? Nemo nostrum istius generis asotos iucunde putat vivere. An me, inquis, tam amentem putas, ut apud imperitos isto modo loquar? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Neque enim civitas in seditione beata esse potest nec in discordia dominorum domus; Eam stabilem appellas. Quacumque enim ingredimur, in aliqua historia vestigium ponimus. Quamquam id quidem licebit iis existimare, qui legerint. Sed ad haec, nisi molestum est, habeo quae velim. Num igitur eum postea censes anxio animo aut sollicito fuisse? Aut haec tibi, Torquate, sunt vituperanda aut patrocinium voluptatis repudiandum. Graece donan, Latine voluptatem vocant. Quamquam te quidem video minime esse deterritum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Reguli reiciendam;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Non potes ergo ista tueri, Torquate, mihi crede, si te ipse et tuas cogitationes et studia perspexeris;</li><li>Quod si ita sit, cur opera philosophiae sit danda nescio.</li><li>Occultum facinus esse potuerit, gaudebit;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Hoc enim identidem dicitis, non intellegere nos quam dicatis voluptatem. Prodest, inquit, mihi eo esse animo. Non igitur de improbo, sed de callido improbo quaerimus, qualis Q. An vero, inquit, quisquam potest probare, quod perceptfum, quod. Intellegi quidem, ut propter aliam quampiam rem, verbi gratia propter voluptatem, nos amemus; Quamquam id quidem, infinitum est in hac urbe; Quae hic rei publicae vulnera inponebat, eadem ille sanabat. Quamquam ab iis philosophiam et omnes ingenuas disciplinas habemus; Quid enim me prohiberet Epicureum esse, si probarem, quae ille diceret? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae ista amicitia est?</a> Portenta haec esse dicit, neque ea ratione ullo modo posse vivi; Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Suam denique cuique naturam esse ad vivendum ducem. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Summae mihi videtur inscitiae.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Non quam nostram quidem, inquit Pomponius iocans;</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quod autem ratione actum est, id officium appellamus. <em>Quorum sine causa fieri nihil putandum est.</em> Idemque diviserunt naturam hominis in animum et corpus. Facile est hoc cernere in primis puerorum aetatulis. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Ad eas enim res ab Epicuro praecepta dantur.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo. Sed tempus est, si videtur, et recta quidem ad me. <em>Si quidem, inquit, tollerem, sed relinquo.</em> Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Sin aliud quid voles, postea. Tum Triarius: Posthac quidem, inquit, audacius. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Utilitatis causa amicitia est quaesita.</li><li>Cuius similitudine perspecta in formarum specie ac dignitate transitum est ad honestatem dictorum atque factorum.</li><li>Mihi quidem Homerus huius modi quiddam vidisse videatur in iis, quae de Sirenum cantibus finxerit.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Quamquam te quidem video minime esse deterritum. Tum mihi Piso: Quid ergo? Que Manilium, ab iisque M. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Peccata paria.</a> Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. <strong>Beatum, inquit.</strong> Ne vitationem quidem doloris ipsam per se quisquam in rebus expetendis putavit, nisi etiam evitare posset. Et quidem saepe quaerimus verbum Latinum par Graeco et quod idem valeat; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Duae sunt enim res quoque, ne tu verba solum putes. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quo tandem modo?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque contra est, ac dicitis;</a> Dicet pro me ipsa virtus nec dubitabit isti vestro beato M. Quam ob rem tandem, inquit, non satisfacit? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Hunc vos beatum;</a> Nunc omni virtuti vitium contrario nomine opponitur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Quorum sine causa fieri nihil putandum est.</strong> Ego vero isti, inquam, permitto. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Illa videamus, quae a te de amicitia dicta sunt.</a> Illa argumenta propria videamus, cur omnia sint paria peccata. Aufidio, praetorio, erudito homine, oculis capto, saepe audiebam, cum se lucis magis quam utilitatis desiderio moveri diceret. Itaque his sapiens semper vacabit. Num quid tale Democritus? </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quam ob rem utique idem faciunt, ut si laevam partem neglegerent, dexteram tuerentur, aut ipsius animi, ut fecit Erillus, cognitionem amplexarentur, actionem relinquerent. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Isto modo, ne si avia quidem eius nata non esset. An, partus ancillae sitne in fructu habendus, disseretur inter principes civitatis, P. Quod idem cum vestri faciant, non satis magnam tribuunt inventoribus gratiam. Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? Graece donan, Latine voluptatem vocant. Expressa vero in iis aetatibus, quae iam confirmatae sunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sit hoc ultimum bonorum, quod nunc a me defenditur;</li><li>Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere.</li><li>Ipse Epicurus fortasse redderet, ut Sextus Peducaeus, Sex.</li><li>Non est igitur summum malum dolor.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><strong>Primum in nostrane potestate est, quid meminerimus?</strong> Isto modo, ne si avia quidem eius nata non esset. Illa argumenta propria videamus, cur omnia sint paria peccata. Quae si potest singula consolando levare, universa quo modo sustinebit? Bona autem corporis huic sunt, quod posterius posui, similiora. Hoc loco discipulos quaerere videtur, ut, qui asoti esse velint, philosophi ante fiant. Sapientem locupletat ipsa natura, cuius divitias Epicurus parabiles esse docuit. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quis Aristidem non mortuum diligit?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Non igitur de improbo, sed de callido improbo quaerimus, qualis Q. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ratio quidem vestra sic cogit.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Venit ad extremum;</a> <em>Conferam avum tuum Drusum cum C.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed quot homines, tot sententiae;</a> Sed quod proximum fuit non vidit. Quid de Platone aut de Democrito loquar? <em>Idem adhuc;</em> Quonam modo? Materiam vero rerum et copiam apud hos exilem, apud illos uberrimam reperiemus. <strong>At eum nihili facit;</strong> Nondum autem explanatum satis, erat, quid maxime natura vellet. Tibi hoc incredibile, quod beatissimum. <strong>Sed haec nihil sane ad rem;</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? Quid ait Aristoteles reliquique Platonis alumni? Qui autem diffidet perpetuitati bonorum suorum, timeat necesse est, ne aliquando amissis illis sit miser. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Earum etiam rerum, quas terra gignit, educatio quaedam et perfectio est non dissimilis animantium. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Sed nimis multa. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At enim hic etiam dolore.</a> Nihilo magis. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt. Ita similis erit ei finis boni, atque antea fuerat, neque idem tamen; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sedulo, inquam, faciam.</a> Is ita vivebat, ut nulla tam exquisita posset inveniri voluptas, qua non abundaret. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ut id aliis narrare gestiant? Quam multa vitiosa! summum enim bonum et malum vagiens puer utra voluptate diiudicabit, stante an movente? Videsne quam sit magna dissensio? In eo enim positum est id, quod dicimus esse expetendum. Eadem nunc mea adversum te oratio est. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Ad eos igitur converte te, quaeso.</em> <strong>Ut aliquid scire se gaudeant?</strong> Itaque e contrario moderati aequabilesque habitus, affectiones ususque corporis apti esse ad naturam videntur. <em>Universa enim illorum ratione cum tota vestra confligendum puto.</em> Videamus animi partes, quarum est conspectus illustrior; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quae quo sunt excelsiores, eo dant clariora indicia naturae.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Igitur neque stultorum quisquam beatus neque sapientium non beatus. Cur igitur, inquam, res tam dissimiles eodem nomine appellas? <em>Immo alio genere;</em> Pauca mutat vel plura sane; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Equidem, sed audistine modo de Carneade?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bestiarum vero nullum iudicium puto.</a> Quae cum dixisset paulumque institisset, Quid est? Haec quo modo conveniant, non sane intellego. Iam in altera philosophiae parte. Nec vero sum nescius esse utilitatem in historia, non modo voluptatem. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ut enim consuetudo loquitur, id solum dicitur honestum, quod est populari fama gloriosum. Simus igitur contenti his. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Utrum enim sit voluptas in iis rebus, quas primas secundum naturam esse diximus, necne sit ad id, quod agimus, nihil interest. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nam si dicent ab illis has res esse tractatas, ne ipsos quidem Graecos est cur tam multos legant, quam legendi sunt. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Quae cum essent dicta, discessimus.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Experiamur igitur, inquit, etsi habet haec Stoicorum ratio difficilius quiddam et obscurius. Non est ista, inquam, Piso, magna dissensio. Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. Ergo opifex plus sibi proponet ad formarum quam civis excellens ad factorum pulchritudinem? Tu enim ista lenius, hic Stoicorum more nos vexat. Res enim concurrent contrariae. Scientiam pollicentur, quam non erat mirum sapientiae cupido patria esse cariorem. Eiuro, inquit adridens, iniquum, hac quidem de re; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Itaque et manendi in vita et migrandi ratio omnis iis rebus, quas supra dixi, metienda. Non quaeritur autem quid naturae tuae consentaneum sit, sed quid disciplinae. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Traditur, inquit, ab Epicuro ratio neglegendi doloris.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Aeque enim contingit omnibus fidibus, ut incontentae sint.</a> Iis igitur est difficilius satis facere, qui se Latina scripta dicunt contemnere. <strong>Sedulo, inquam, faciam.</strong> Eorum enim omnium multa praetermittentium, dum eligant aliquid, quod sequantur, quasi curta sententia; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Honesta oratio, Socratica, Platonis etiam.</h2> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Tanta vis admonitionis inest in locis; Miserum hominem! Si dolor summum malum est, dici aliter non potest. Atqui reperies, inquit, in hoc quidem pertinacem; Ergo hoc quidem apparet, nos ad agendum esse natos. Illis videtur, qui illud non dubitant bonum dicere -; Itaque contra est, ac dicitis; Nihil est enim, de quo aliter tu sentias atque ego, modo commutatis verbis ipsas res conferamus. Occultum facinus esse potuerit, gaudebit; Quod cum accidisset ut alter alterum necopinato videremus, surrexit statim. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quare attende, quaeso.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Sed quid minus probandum quam esse aliquem beatum nec satis beatum? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit;</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Suam denique cuique naturam esse ad vivendum ducem. At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit; Quid adiuvas? Quamquam id quidem, infinitum est in hac urbe; Quod non faceret, si in voluptate summum bonum poneret. Negare non possum. Heri, inquam, ludis commissis ex urbe profectus veni ad vesperum. Quid enim ab antiquis ex eo genere, quod ad disserendum valet, praetermissum est? Quid iudicant sensus? <strong>Itaque his sapiens semper vacabit.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Illa argumenta propria videamus, cur omnia sint paria peccata.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>An potest cupiditas finiri? Sic igitur in homine perfectio ista in eo potissimum, quod est optimum, id est in virtute, laudatur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Scisse enim te quis coarguere possit?</a> Fortasse id optimum, sed ubi illud: Plus semper voluptatis? Hanc se tuus Epicurus omnino ignorare dicit quam aut qualem esse velint qui honestate summum bonum metiantur. Non ego tecum iam ita iocabor, ut isdem his de rebus, cum L. Superiores tres erant, quae esse possent, quarum est una sola defensa, eaque vehementer. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Erat enim Polemonis.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Compensabatur, inquit, cum summis doloribus laetitia. <strong>An tu me de L.</strong> Alterum significari idem, ut si diceretur, officia media omnia aut pleraque servantem vivere. Eorum enim est haec querela, qui sibi cari sunt seseque diligunt. Et si turpitudinem fugimus in statu et motu corporis, quid est cur pulchritudinem non sequamur? At iam decimum annum in spelunca iacet. Sed fac ista esse non inportuna; Nam de isto magna dissensio est. Illa tamen simplicia, vestra versuta. An tu me de L. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Quodcumque in mentem incideret, et quodcumque tamquam occurreret.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cur igitur, inquam, res tam dissimiles eodem nomine appellas? Iam illud quale tandem est, bona praeterita non effluere sapienti, mala meminisse non oportere? Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; Quid ad utilitatem tantae pecuniae? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Easdemne res?</a> Superiores tres erant, quae esse possent, quarum est una sola defensa, eaque vehementer. Nonne igitur tibi videntur, inquit, mala? Sine ea igitur iucunde negat posse se vivere? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Cuius quidem, quoniam Stoicus fuit, sententia condemnata mihi videtur esse inanitas ista verborum.</li><li>Vulgo enim dicitur: Iucundi acti labores, nec male Euripidesconcludam, si potero, Latine;</li><li>Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt.</li><li>Sed tu, ut dignum est tua erga me et philosophiam voluntate ab adolescentulo suscepta, fac ut Metrodori tueare liberos.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Peccata paria.</a> Quae si potest singula consolando levare, universa quo modo sustinebit? Illa argumenta propria videamus, cur omnia sint paria peccata. Hinc ceteri particulas arripere conati suam quisque videro voluit afferre sententiam. Portenta haec esse dicit, neque ea ratione ullo modo posse vivi; Etsi qui potest intellegi aut cogitari esse aliquod animal, quod se oderit? Multa sunt dicta ab antiquis de contemnendis ac despiciendis rebus humanis; Quod autem in homine praestantissimum atque optimum est, id deseruit. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Admirantes quaeramus ab utroque, quonam modo vitam agere possimus, si nihil interesse nostra putemus, valeamus aegrine simus, vacemus an cruciemur dolore, frigus, famem propulsare possimus necne possimus. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. <strong>Non autem hoc: igitur ne illud quidem.</strong> Solum praeterea formosum, solum liberum, solum civem, stultost; Faceres tu quidem, Torquate, haec omnia; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Iam id ipsum absurdum, maximum malum neglegi.</a> Res tota, Torquate, non doctorum hominum, velle post mortem epulis celebrari memoriam sui nominis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Numquam facies.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Illi enim inter se dissentiunt.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Non autem hoc: igitur ne illud quidem.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Si verbum sequimur, primum longius verbum praepositum quam bonum. Nam Pyrrho, Aristo, Erillus iam diu abiecti. Et quidem saepe quaerimus verbum Latinum par Graeco et quod idem valeat; Negat enim summo bono afferre incrementum diem. Placet igitur tibi, Cato, cum res sumpseris non concessas, ex illis efficere, quod velis? <em>Quod quidem iam fit etiam in Academia.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sin autem est in ea, quod quidam volunt, nihil impedit hanc nostram comprehensionem summi boni. Satis est ad hoc responsum. Quonam, inquit, modo? Quo tandem modo? <em>Beatus sibi videtur esse moriens.</em> Eam si varietatem diceres, intellegerem, ut etiam non dicente te intellego; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Deinde dolorem quem maximum?</a> Ex rebus enim timiditas, non ex vocabulis nascitur. Laelius clamores sofòw ille so lebat Edere compellans gumias ex ordine nostros. Laboro autem non sine causa; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Nam aliquando posse recte fieri dicunt nulla expectata nec quaesita voluptate.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cenasti in vita numquam bene, cum omnia in ista Consumis squilla atque acupensere cum decimano. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cave putes quicquam esse verius.</a> Tum Piso: Atqui, Cicero, inquit, ista studia, si ad imitandos summos viros spectant, ingeniosorum sunt; Tu enim ista lenius, hic Stoicorum more nos vexat. <em>Minime vero istorum quidem, inquit.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quid turpius quam sapientis vitam ex insipientium sermone pendere?</li><li>Quae quidem vel cum periculo est quaerenda vobis;</li><li>Si longus, levis.</li><li>Fortitudinis quaedam praecepta sunt ac paene leges, quae effeminari virum vetant in dolore.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nec vero potest quisquam de bonis et malis vere iudicare nisi omni cognita ratione naturae et vitae etiam deorum, et utrum conveniat necne natura hominis cum universa. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo.</li><li>Magno hic ingenio, sed res se tamen sic habet, ut nimis imperiosi philosophi sit vetare meminisse.</li><li>Beatus autem esse in maximarum rerum timore nemo potest.</li><li>Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam;</li><li>Primum in nostrane potestate est, quid meminerimus?</li><li>Nec lapathi suavitatem acupenseri Galloni Laelius anteponebat, sed suavitatem ipsam neglegebat;</li><li>Est enim effectrix multarum et magnarum voluptatum.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Ut in voluptate sit, qui epuletur, in dolore, qui torqueatur. Neque solum ea communia, verum etiam paria esse dixerunt. Scio enim esse quosdam, qui quavis lingua philosophari possint; <em>Non potes, nisi retexueris illa.</em> Si est nihil nisi corpus, summa erunt illa: valitudo, vacuitas doloris, pulchritudo, cetera. Et ais, si una littera commota sit, fore tota ut labet disciplina. Nam illud quidem adduci vix possum, ut ea, quae senserit ille, tibi non vera videantur. Hoc positum in Phaedro a Platone probavit Epicurus sensitque in omni disputatione id fieri oportere. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Eiuro, inquit adridens, iniquum, hac quidem de re;</li><li>At ille pellit, qui permulcet sensum voluptate.</li><li>Primum in nostrane potestate est, quid meminerimus?</li><li>Itaque vides, quo modo loquantur, nova verba fingunt, deserunt usitata.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Quid vero? Aut, Pylades cum sis, dices te esse Orestem, ut moriare pro amico? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si longus, levis;</a> Aut unde est hoc contritum vetustate proverbium: quicum in tenebris? Immo alio genere; Sunt enim prima elementa naturae, quibus auctis vírtutis quasi germen efficitur. Potius inflammat, ut coercendi magis quam dedocendi esse videantur. <strong>Inde igitur, inquit, ordiendum est.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Nihilne te delectat umquam -video, quicum loquar-, te igitur, Torquate, ipsum per se nihil delectat?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quo modo autem optimum, si bonum praeterea nullum est? Si quicquam extra virtutem habeatur in bonis. Conclusum est enim contra Cyrenaicos satis acute, nihil ad Epicurum. Itaque haec cum illis est dissensio, cum Peripateticis nulla sane. Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; Primum cur ista res digna odio est, nisi quod est turpis? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest.</li><li>An ea, quae per vinitorem antea consequebatur, per se ipsa curabit?</li><li>Sit sane ista voluptas.</li><li>-delector enim, quamquam te non possum, ut ais, corrumpere, delector, inquam, et familia vestra et nomine.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum; Nemo igitur esse beatus potest. Quicquid enim a sapientia proficiscitur, id continuo debet expletum esse omnibus suis partibus; Sed tu istuc dixti bene Latine, parum plane. Summum a vobis bonum voluptas dicitur. Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo. Beatus sibi videtur esse moriens. Illa argumenta propria videamus, cur omnia sint paria peccata. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Egone quaeris, inquit, quid sentiam?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Estne, quaeso, inquam, sitienti in bibendo voluptas? Cum audissem Antiochum, Brute, ut solebam, cum M. Si mala non sunt, iacet omnis ratio Peripateticorum. Primum cur ista res digna odio est, nisi quod est turpis? Sed hoc sane concedamus. Deinde prima illa, quae in congressu solemus: Quid tu, inquit, huc? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid ergo hoc loco intellegit honestum? Inde sermone vario sex illa a Dipylo stadia confecimus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Minime vero, inquit ille, consentit.</a> Occultum facinus esse potuerit, gaudebit; Dic in quovis conventu te omnia facere, ne doleas. <em>Quid ergo hoc loco intellegit honestum?</em> Pauca mutat vel plura sane; Illis videtur, qui illud non dubitant bonum dicere -; Tria genera bonorum; Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum sit. Qua igitur re ab deo vincitur, si aeternitate non vincitur? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nullus est igitur cuiusquam dies natalis. Etenim nec iustitia nec amicitia esse omnino poterunt, nisi ipsae per se expetuntur. Sic vester sapiens magno aliquo emolumento commotus cicuta, si opus erit, dimicabit. Istam voluptatem perpetuam quis potest praestare sapienti? Et quod est munus, quod opus sapientiae? <strong>Quod totum contra est.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid ad utilitatem tantae pecuniae? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nos commodius agimus.</a> Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Illa videamus, quae a te de amicitia dicta sunt. Ad eas enim res ab Epicuro praecepta dantur. <strong>Huius ego nunc auctoritatem sequens idem faciam.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. Ita similis erit ei finis boni, atque antea fuerat, neque idem tamen; Qui autem esse poteris, nisi te amor ipse ceperit? An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Videamus igitur sententias eorum, tum ad verba redeamus. Qui cum praetor quaestionem inter sicarios exercuisset, ita aperte cepit pecunias ob rem iudicandam, ut anno proximo P. Iam id ipsum absurdum, maximum malum neglegi. Mihi enim satis est, ipsis non satis. Quod autem principium officii quaerunt, melius quam Pyrrho; Quae cum dixisset paulumque institisset, Quid est? Omnia contraria, quos etiam insanos esse vultis. Hoc est dicere: Non reprehenderem asotos, si non essent asoti. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Addidisti ad extremum etiam indoctum fuisse. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid, quod res alia tota est?</a> In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Bonum liberi: misera orbitas. Primum cur ista res digna odio est, nisi quod est turpis? Deprehensus omnem poenam contemnet. Luxuriam non reprehendit, modo sit vacua infinita cupiditate et timore. Illa argumenta propria videamus, cur omnia sint paria peccata. At quanta conantur! Mundum hunc omnem oppidum esse nostrum! Incendi igitur eos, qui audiunt, vides. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis negat? Aliter enim nosmet ipsos nosse non possumus. Non minor, inquit, voluptas percipitur ex vilissimis rebus quam ex pretiosissimis. Nec vero hoc oratione solum, sed multo magis vita et factis et moribus comprobavit. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Callipho ad virtutem nihil adiunxit nisi voluptatem, Diodorus vacuitatem doloris.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Huius ego nunc auctoritatem sequens idem faciam. Quis Aristidem non mortuum diligit? Quae qui non vident, nihil umquam magnum ac cognitione dignum amaverunt. Quam nemo umquam voluptatem appellavit, appellat; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quam si explicavisset, non tam haesitaret.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Hic Speusippus, hic Xenocrates, hic eius auditor Polemo, cuius illa ipsa sessio fuit, quam videmus. Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest. Atque ab his initiis profecti omnium virtutum et originem et progressionem persecuti sunt. Omnis enim est natura diligens sui. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Nam his libris eum malo quam reliquo ornatu villae delectari.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nulla profecto est, quin suam vim retineat a primo ad extremum. Esse enim quam vellet iniquus iustus poterat inpune. Non enim solum Torquatus dixit quid sentiret, sed etiam cur. Nullus est igitur cuiusquam dies natalis. Haec et tu ita posuisti, et verba vestra sunt. Satis est ad hoc responsum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tum Triarius: Posthac quidem, inquit, audacius.</a> Nulla profecto est, quin suam vim retineat a primo ad extremum. Qui est in parvis malis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>An potest cupiditas finiri? Ut in geometria, prima si dederis, danda sunt omnia. At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit; Nulla profecto est, quin suam vim retineat a primo ad extremum. Cum autem negant ea quicquam ad beatam vitam pertinere, rursus naturam relinquunt. <strong>Huius ego nunc auctoritatem sequens idem faciam.</strong> Quod vestri non item. Quorum sine causa fieri nihil putandum est. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sed haec in pueris;</li><li>Quid igitur dubitamus in tota eius natura quaerere quid sit effectum?</li><li>Sed non alienum est, quo facilius vis verbi intellegatur, rationem huius verbi faciendi Zenonis exponere.</li><li>Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><strong>Tamen a proposito, inquam, aberramus.</strong> Aliud igitur esse censet gaudere, aliud non dolere. Sic enim censent, oportunitatis esse beate vivere. <em>Sumenda potius quam expetenda.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tum Torquatus: Prorsus, inquit, assentior;</a> Haec et tu ita posuisti, et verba vestra sunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Vitiosum est enim in dividendo partem in genere numerare. Oratio me istius philosophi non offendit; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Post enim Chrysippum eum non sane est disputatum.</a> Aut unde est hoc contritum vetustate proverbium: quicum in tenebris? Vestri haec verecundius, illi fortasse constantius. Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit? Mihi vero, inquit, placet agi subtilius et, ut ipse dixisti, pressius. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quam ob rem tandem, inquit, non satisfacit?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Hoc enim identidem dicitis, non intellegere nos quam dicatis voluptatem. Aliud igitur esse censet gaudere, aliud non dolere. Cum audissem Antiochum, Brute, ut solebam, cum M. Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur. Quibus ego vehementer assentior. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Ego quoque, inquit, didicerim libentius si quid attuleris, quam te reprehenderim.</li><li>Omnes enim iucundum motum, quo sensus hilaretur.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Posuisti etiam dicere alios foedus quoddam inter se facere sapientis, ut, quem ad modum sint in se ipsos animati, eodem modo sint erga amicos; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Aliter enim explicari, quod quaeritur, non potest. Quod quidem iam fit etiam in Academia. Equidem, sed audistine modo de Carneade? Iam doloris medicamenta illa Epicurea tamquam de narthecio proment: Si gravis, brevis; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Faceres tu quidem, Torquate, haec omnia;</a> Quod cum dixissent, ille contra. Tecum optime, deinde etiam cum mediocri amico. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Traditur, inquit, ab Epicuro ratio neglegendi doloris.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Uterque enim summo bono fruitur, id est voluptate.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>An tu me de L.</strong> Quod autem satis est, eo quicquid accessit, nimium est; <strong>Paria sunt igitur.</strong> Respondeat totidem verbis. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Ego vero volo in virtute vim esse quam maximam;</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hic ego: Pomponius quidem, inquam, noster iocari videtur, et fortasse suo iure. <em>Simus igitur contenti his.</em> Quod si ita sit, cur opera philosophiae sit danda nescio. Quae iam oratio non a philosopho aliquo, sed a censore opprimenda est. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nullus est igitur cuiusquam dies natalis.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quid enim dicis omne animal, simul atque sit ortum, applicatum esse ad se diligendum esseque in se conservando occupatum?</li><li>Ampulla enim sit necne sit, quis non iure optimo irrideatur, si laboret?</li><li>Qui non moveatur et offensione turpitudinis et comprobatione honestatis?</li><li>Ergo omni animali illud, quod appetiti positum est in eo, quod naturae est accommodatum.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":5} --> +<h5>Ita graviter et severe voluptatem secrevit a bono.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nec vero intermittunt aut admirationem earum rerum, quae sunt ab antiquis repertae, aut investigationem novarum. Iam id ipsum absurdum, maximum malum neglegi. Neque enim civitas in seditione beata esse potest nec in discordia dominorum domus; <strong>Aliter enim explicari, quod quaeritur, non potest.</strong> Animum autem reliquis rebus ita perfecit, ut corpus; Aliud igitur esse censet gaudere, aliud non dolere. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari.</li><li>Praeterea et appetendi et refugiendi et omnino rerum gerendarum initia proficiscuntur aut a voluptate aut a dolore.</li><li>Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint.</li><li>Atqui, inquam, Cato, si istud optinueris, traducas me ad te totum licebit.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":5} --> +<h5>Sed in rebus apertissimis nimium longi sumus.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Itaque sensibus rationem adiunxit et ratione effecta sensus non reliquit. Verba tu fingas et ea dicas, quae non sentias? Illud dico, ea, quae dicat, praeclare inter se cohaerere. <strong>Cupiditates non Epicuri divisione finiebat, sed sua satietate.</strong> At eum nihili facit; Tum ille timide vel potius verecunde: Facio, inquit. Qua tu etiam inprudens utebare non numquam. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Zenonis est, inquam, hoc Stoici.</em> Sint modo partes vitae beatae. Alterum significari idem, ut si diceretur, officia media omnia aut pleraque servantem vivere. <em>Sine ea igitur iucunde negat posse se vivere?</em> Nihil opus est exemplis hoc facere longius. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sic, et quidem diligentius saepiusque ista loquemur inter nos agemusque communiter.</li><li>Dolere malum est: in crucem qui agitur, beatus esse non potest.</li><li>Sed ad bona praeterita redeamus.</li><li>Eodem modo is enim tibi nemo dabit, quod, expetendum sit, id esse laudabile.</li><li>Quid, cum volumus nomina eorum, qui quid gesserint, nota nobis esse, parentes, patriam, multa praeterea minime necessaria?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid censes in Latino fore?</a> Haec et tu ita posuisti, et verba vestra sunt. Respondeat totidem verbis. Quis est, qui non oderit libidinosam, protervam adolescentiam? Illum mallem levares, quo optimum atque humanissimum virum, Cn. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non laboro, inquit, de nomine.</a> Sed quod proximum fuit non vidit. Sed ad bona praeterita redeamus. Equidem, sed audistine modo de Carneade? Quid turpius quam sapientis vitam ex insipientium sermone pendere? Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed quia studebat laudi et dignitati, multum in virtute processerat. Hoc enim constituto in philosophia constituta sunt omnia. Teneo, inquit, finem illi videri nihil dolere. <em>Nam de isto magna dissensio est.</em> Sed erat aequius Triarium aliquid de dissensione nostra iudicare. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Suo genere perveniant ad extremum; Quicquid porro animo cernimus, id omne oritur a sensibus; Potius ergo illa dicantur: turpe esse, viri non esse debilitari dolore, frangi, succumbere. <strong>Duarum enim vitarum nobis erunt instituta capienda.</strong> Nam ista vestra: Si gravis, brevis; Cenasti in vita numquam bene, cum omnia in ista Consumis squilla atque acupensere cum decimano. Sed quia studebat laudi et dignitati, multum in virtute processerat. Sine ea igitur iucunde negat posse se vivere? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Non igitur de improbo, sed de callido improbo quaerimus, qualis Q. Et ais, si una littera commota sit, fore tota ut labet disciplina. Ut enim consuetudo loquitur, id solum dicitur honestum, quod est populari fama gloriosum. Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. Eorum enim omnium multa praetermittentium, dum eligant aliquid, quod sequantur, quasi curta sententia; <em>Quis hoc dicit?</em> At iam decimum annum in spelunca iacet. <strong>Haec quo modo conveniant, non sane intellego.</strong> Consequentia exquirere, quoad sit id, quod volumus, effectum. Nos paucis ad haec additis finem faciamus aliquando; </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt.</li><li>Graccho, eius fere, aequalí?</li><li>Quacumque enim ingredimur, in aliqua historia vestigium ponimus.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Idque testamento cavebit is, qui nobis quasi oraculum ediderit nihil post mortem ad nos pertinere? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading --> +<h2>Ita relinquet duas, de quibus etiam atque etiam consideret.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Scaevola tribunus plebis ferret ad plebem vellentne de ea re quaeri. Quid censes in Latino fore? <strong>Paria sunt igitur.</strong> Conclusum est enim contra Cyrenaicos satis acute, nihil ad Epicurum. Conferam tecum, quam cuique verso rem subicias; Summum ením bonum exposuit vacuitatem doloris; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Tubulo putas dicere?</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Et quod est munus, quod opus sapientiae? Quae hic rei publicae vulnera inponebat, eadem ille sanabat. Sed ne, dum huic obsequor, vobis molestus sim. Ex rebus enim timiditas, non ex vocabulis nascitur. Hic Speusippus, hic Xenocrates, hic eius auditor Polemo, cuius illa ipsa sessio fuit, quam videmus. Bona autem corporis huic sunt, quod posterius posui, similiora. Miserum hominem! Si dolor summum malum est, dici aliter non potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Occultum facinus esse potuerit, gaudebit;</a> Idem iste, inquam, de voluptate quid sentit? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bestiarum vero nullum iudicium puto.</a> Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo. Si alia sentit, inquam, alia loquitur, numquam intellegam quid sentiat; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Huius ego nunc auctoritatem sequens idem faciam.</a> Uterque enim summo bono fruitur, id est voluptate. Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non est igitur voluptas bonum.</a> Non enim iam stirpis bonum quaeret, sed animalis. Cur, nisi quod turpis oratio est? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quae hic rei publicae vulnera inponebat, eadem ille sanabat. <strong>Quis istum dolorem timet?</strong> Quamvis enim depravatae non sint, pravae tamen esse possunt. Estne, quaeso, inquam, sitienti in bibendo voluptas? Haec igitur Epicuri non probo, inquam. Ea, quae dialectici nunc tradunt et docent, nonne ab illis instituta sunt aut inventa sunt? Quod eo liquidius faciet, si perspexerit rerum inter eas verborumne sit controversia. Quid vero? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed ad bona praeterita redeamus. Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. <em>Sit sane ista voluptas.</em> Ego quoque, inquit, didicerim libentius si quid attuleris, quam te reprehenderim. Sed ad bona praeterita redeamus. <em>Sed nimis multa.</em> Sed quid sentiat, non videtis. Quamquam haec quidem praeposita recte et reiecta dicere licebit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Cuius etiam illi hortuli propinqui non memoriam solum mihi afferunt, sed ipsum videntur in conspectu meo ponere. Et tamen ego a philosopho, si afferat eloquentiam, non asperner, si non habeat, non admodum flagitem. Haec dicuntur fortasse ieiunius; Verba tu fingas et ea dicas, quae non sentias? Hoc est non modo cor non habere, sed ne palatum quidem. Ergo adhuc, quantum equidem intellego, causa non videtur fuisse mutandi nominis. <em>Quamquam haec quidem praeposita recte et reiecta dicere licebit.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed residamus, inquit, si placet.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria. Idemne potest esse dies saepius, qui semel fuit? Rapior illuc, revocat autem Antiochus, nec est praeterea, quem audiamus. Ita cum ea volunt retinere, quae superiori sententiae conveniunt, in Aristonem incidunt; Aliter enim explicari, quod quaeritur, non potest. Non quam nostram quidem, inquit Pomponius iocans; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Memini vero, inquam;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ita multo sanguine profuso in laetitia et in victoria est mortuus. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":4} --> +<h4>Sed residamus, inquit, si placet.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Itaque in rebus minime obscuris non multus est apud eos disserendi labor. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si quae forte-possumus.</a> Itaque sensibus rationem adiunxit et ratione effecta sensus non reliquit. Si id dicis, vicimus. <em>Poterat autem inpune;</em> Erit enim mecum, si tecum erit. Immo vero, inquit, ad beatissime vivendum parum est, ad beate vero satis. <strong>Inde igitur, inquit, ordiendum est.</strong> Quis contra in illa aetate pudorem, constantiam, etiamsi sua nihil intersit, non tamen diligat? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Universa enim illorum ratione cum tota vestra confligendum puto. Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum sit. Si sapiens, ne tum quidem miser, cum ab Oroete, praetore Darei, in crucem actus est. Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus; <em>Est enim effectrix multarum et magnarum voluptatum.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quid enim de amicitia statueris utilitatis causa expetenda vides.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Nihilo beatiorem esse Metellum quam Regulum.</em> Virtutibus igitur rectissime mihi videris et ad consuetudinem nostrae orationis vitia posuisse contraria. Intellegi quidem, ut propter aliam quampiam rem, verbi gratia propter voluptatem, nos amemus; <strong>Ergo ita: non posse honeste vivi, nisi honeste vivatur?</strong> Sedulo, inquam, faciam. Ita credo. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed tamen intellego quid velit.</a> <strong>Quo igitur, inquit, modo?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Bonum negas esse divitias, praeposìtum esse dicis? Ita cum ea volunt retinere, quae superiori sententiae conveniunt, in Aristonem incidunt; Eam si varietatem diceres, intellegerem, ut etiam non dicente te intellego; Quid est enim aliud esse versutum? Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; Quis tibi ergo istud dabit praeter Pyrrhonem, Aristonem eorumve similes, quos tu non probas? Sin aliud quid voles, postea. Si verbum sequimur, primum longius verbum praepositum quam bonum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Illud urgueam, non intellegere eum quid sibi dicendum sit, cum dolorem summum malum esse dixerit. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Quid, si etiam iucunda memoria est praeteritorum malorum? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Atque his de rebus et splendida est eorum et illustris oratio.</li><li>Cupiditates non Epicuri divisione finiebat, sed sua satietate.</li><li>Atque his de rebus et splendida est eorum et illustris oratio.</li><li>Etenim si delectamur, cum scribimus, quis est tam invidus, qui ab eo nos abducat?</li><li>Sed quid minus probandum quam esse aliquem beatum nec satis beatum?</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Sin autem ad animum, falsum est, quod negas animi ullum esse gaudium, quod non referatur ad corpus. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Si enim ad populum me vocas, eum.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Non autem hoc: igitur ne illud quidem. Sed finge non solum callidum eum, qui aliquid improbe faciat, verum etiam praepotentem, ut M. Nulla erit controversia. Cum autem in quo sapienter dicimus, id a primo rectissime dicitur. <strong>Bonum incolumis acies: misera caecitas.</strong> Iam id ipsum absurdum, maximum malum neglegi. Tu enim ista lenius, hic Stoicorum more nos vexat. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Itaque a sapientia praecipitur se ipsam, si usus sit, sapiens ut relinquat. At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit; Aliam vero vim voluptatis esse, aliam nihil dolendi, nisi valde pertinax fueris, concedas necesse est. Qui ita affectus, beatum esse numquam probabis; Facit enim ille duo seiuncta ultima bonorum, quae ut essent vera, coniungi debuerunt; Quamquam in hac divisione rem ipsam prorsus probo, elegantiam desidero. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quid de Platone aut de Democrito loquar?</h3> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Ita enim vivunt quidam, ut eorum vita refellatur oratio. Quid enim me prohiberet Epicureum esse, si probarem, quae ille diceret? Scientiam pollicentur, quam non erat mirum sapientiae cupido patria esse cariorem. Claudii libidini, qui tum erat summo ne imperio, dederetur. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Quid ergo aliud intellegetur nisi uti ne quae pars naturae neglegatur?</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Etiam beatissimum?</em> <em>Gloriosa ostentatio in constituendo summo bono.</em> Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint. Haec quo modo conveniant, non sane intellego. Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Res enim se praeclare habebat, et quidem in utraque parte. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Videsne quam sit magna dissensio?</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nunc haec primum fortasse audientis servire debemus. Habent enim et bene longam et satis litigiosam disputationem. Mihi enim satis est, ipsis non satis. Nam illud quidem adduci vix possum, ut ea, quae senserit ille, tibi non vera videantur. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Atqui reperies, inquit, in hoc quidem pertinacem;</li><li>Contemnit enim disserendi elegantiam, confuse loquitur.</li><li>Neque solum ea communia, verum etiam paria esse dixerunt.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Verum hoc idem saepe faciamus. Nam Pyrrho, Aristo, Erillus iam diu abiecti. Quicquid porro animo cernimus, id omne oritur a sensibus; Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. Quare si potest esse beatus is, qui est in asperis reiciendisque rebus, potest is quoque esse. Sic enim censent, oportunitatis esse beate vivere. Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. In qua quid est boni praeter summam voluptatem, et eam sempiternam? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quid enim me prohiberet Epicureum esse, si probarem, quae ille diceret?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ad quorum et cognitionem et usum iam corroborati natura ipsa praeeunte deducimur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Apparet statim, quae sint officia, quae actiones.</a> Cum id quoque, ut cupiebat, audivisset, evelli iussit eam, qua erat transfixus, hastam. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed ne, dum huic obsequor, vobis molestus sim.</a> Verum tamen cum de rebus grandioribus dicas, ipsae res verba rapiunt; Saepe ab Aristotele, a Theophrasto mirabiliter est laudata per se ipsa rerum scientia; Philosophi autem in suis lectulis plerumque moriuntur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quae cum magnifice primo dici viderentur, considerata minus probabantur. Atque haec coniunctio confusioque virtutum tamen a philosophis ratione quadam distinguitur. Ratio quidem vestra sic cogit. Si quicquam extra virtutem habeatur in bonis. Longum est enim ad omnia respondere, quae a te dicta sunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nullis enim partitionibus, nullis definitionibus utuntur ipsique dicunt ea se modo probare, quibus natura tacita adsentiatur. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sit hoc ultimum bonorum, quod nunc a me defenditur;</a> Sin te auctoritas commovebat, nobisne omnibus et Platoni ipsi nescio quem illum anteponebas? <strong>Ex rebus enim timiditas, non ex vocabulis nascitur.</strong> Primum cur ista res digna odio est, nisi quod est turpis? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; Hoc etsi multimodis reprehendi potest, tamen accipio, quod dant. Sed potestne rerum maior esse dissensio? Sunt enim prima elementa naturae, quibus auctis vírtutis quasi germen efficitur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tamen a proposito, inquam, aberramus.</a> Quare attendo te studiose et, quaecumque rebus iis, de quibus hic sermo est, nomina inponis, memoriae mando; <strong>Cur, nisi quod turpis oratio est?</strong> Eam si varietatem diceres, intellegerem, ut etiam non dicente te intellego; Cum autem venissemus in Academiae non sine causa nobilitata spatia, solitudo erat ea, quam volueramus. Est enim effectrix multarum et magnarum voluptatum. Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; <strong>Erat enim res aperta.</strong> Sed tempus est, si videtur, et recta quidem ad me. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Atque ut reliqui fures earum rerum, quas ceperunt, signa commutant, sic illi, ut sententiis nostris pro suis uterentur, nomina tamquam rerum notas mutaverunt. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Sequitur disserendi ratio cognitioque naturae; Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; <em>Idemne, quod iucunde?</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Aliud igitur esse censet gaudere, aliud non dolere.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Unum est sine dolore esse, alterum cum voluptate. At miser, si in flagitiosa et vitiosa vita afflueret voluptatibus. Vide, quantum, inquam, fallare, Torquate. Portenta haec esse dicit, neque ea ratione ullo modo posse vivi; Ergo infelix una molestia, fellx rursus, cum is ipse anulus in praecordiis piscis inventus est? Ac ne plura complectar-sunt enim innumerabilia-, bene laudata virtus voluptatis aditus intercludat necesse est. Nec mihi illud dixeris: Haec enim ipsa mihi sunt voluptati, et erant illa Torquatis. <strong>Quodsi ipsam honestatem undique pertectam atque absolutam.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nos quidem Virtutes sic natae sumus, ut tibi serviremus, aliud negotii nihil habemus. <em>Bestiarum vero nullum iudicium puto.</em> Est autem etiam actio quaedam corporis, quae motus et status naturae congruentis tenet; Nondum autem explanatum satis, erat, quid maxime natura vellet. Illis videtur, qui illud non dubitant bonum dicere -; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Idque testamento cavebit is, qui nobis quasi oraculum ediderit nihil post mortem ad nos pertinere? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Respondeat totidem verbis.</a> <em>Nihilo beatiorem esse Metellum quam Regulum.</em> Non igitur de improbo, sed de callido improbo quaerimus, qualis Q. Quid de Platone aut de Democrito loquar? Nobis Heracleotes ille Dionysius flagitiose descivisse videtur a Stoicis propter oculorum dolorem. <em>Sed ad bona praeterita redeamus.</em> De quibus cupio scire quid sentias. Laboro autem non sine causa; Quae quidem sapientes sequuntur duce natura tamquam videntes; Diodorus, eius auditor, adiungit ad honestatem vacuitatem doloris. Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? Cupit enim dícere nihil posse ad beatam vitam deesse sapienti. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur id non ita fit?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod ea non occurrentia fingunt, vincunt Aristonem;</a> <strong>At certe gravius.</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nihil illinc huc pervenit.</a> Quae cum magnifice primo dici viderentur, considerata minus probabantur. Universa enim illorum ratione cum tota vestra confligendum puto. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Sumenda potius quam expetenda.</strong> Quare conare, quaeso. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Murenam te accusante defenderem.</a> <strong>Quantum Aristoxeni ingenium consumptum videmus in musicis?</strong> Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; <em>At multis malis affectus.</em> Sic enim censent, oportunitatis esse beate vivere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Laboro autem non sine causa; Ita ne hoc quidem modo paria peccata sunt. Quis istum dolorem timet? Primum in nostrane potestate est, quid meminerimus? Quae diligentissime contra Aristonem dicuntur a Chryippo. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Id Sextilius factum negabat. <strong>Audeo dicere, inquit.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quia nec honesto quic quam honestius nec turpi turpius. Quod cum dixissent, ille contra. Sed tu istuc dixti bene Latine, parum plane. Sic vester sapiens magno aliquo emolumento commotus cicuta, si opus erit, dimicabit. <strong>Quae quo sunt excelsiores, eo dant clariora indicia naturae.</strong> Non igitur potestis voluptate omnia dirigentes aut tueri aut retinere virtutem. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Certe non potest.</a> Ut in geometria, prima si dederis, danda sunt omnia. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Urgent tamen et nihil remittunt.</em> Non dolere, inquam, istud quam vim habeat postea videro; Tria genera cupiditatum, naturales et necessariae, naturales et non necessariae, nec naturales nec necessariae. Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Ita fit cum gravior, tum etiam splendidior oratio. Tum Quintus: Est plane, Piso, ut dicis, inquit. Etenim si delectamur, cum scribimus, quis est tam invidus, qui ab eo nos abducat? Post enim Chrysippum eum non sane est disputatum. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quod equidem non reprehendo; Quid de Platone aut de Democrito loquar? Non enim ipsa genuit hominem, sed accepit a natura inchoatum. <strong>Urgent tamen et nihil remittunt.</strong> At ego quem huic anteponam non audeo dicere; Quid ei reliquisti, nisi te, quoquo modo loqueretur, intellegere, quid diceret? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Ut in geometria, prima si dederis, danda sunt omnia.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Aliter enim nosmet ipsos nosse non possumus. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. Quamquam haec quidem praeposita recte et reiecta dicere licebit. Sed residamus, inquit, si placet. Ut non sine causa ex iis memoriae ducta sit disciplina. Ita relinquet duas, de quibus etiam atque etiam consideret. Mihi enim satis est, ipsis non satis. Respondent extrema primis, media utrisque, omnia omnibus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Aeque enim contingit omnibus fidibus, ut incontentae sint. <em>Obsecro, inquit, Torquate, haec dicit Epicurus?</em> Quam ob rem tandem, inquit, non satisfacit? Quod praeceptum quia maius erat, quam ut ab homine videretur, idcirco assignatum est deo. Quid ergo aliud intellegetur nisi uti ne quae pars naturae neglegatur? Et ais, si una littera commota sit, fore tota ut labet disciplina. Est enim effectrix multarum et magnarum voluptatum. Sed tamen intellego quid velit. Qui non moveatur et offensione turpitudinis et comprobatione honestatis? Minime vero istorum quidem, inquit. Illa videamus, quae a te de amicitia dicta sunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Teneo, inquit, finem illi videri nihil dolere. <strong>Suo enim quisque studio maxime ducitur.</strong> Possumusne ergo in vita summum bonum dicere, cum id ne in cena quidem posse videamur? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Aliter autem vobis placet.</a> Optime, inquam. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Hoc autem loco tantum explicemus haec honesta, quae dico, praeterquam quod nosmet ipsos diligamus, praeterea suapte natura per se esse expetenda. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Cum audissem Antiochum, Brute, ut solebam, cum M. <em>Dat enim intervalla et relaxat.</em> Numquam facies. Totum autem id externum est, et quod externum, id in casu est. Iubet igitur nos Pythius Apollo noscere nosmet ipsos. Bonum liberi: misera orbitas. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis enim est, qui non videat haec esse in natura rerum tria? Quae cum praeponunt, ut sit aliqua rerum selectio, naturam videntur sequi; Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis. Cum sciret confestim esse moriendum eamque mortem ardentiore studio peteret, quam Epicurus voluptatem petendam putat. Nunc omni virtuti vitium contrario nomine opponitur. Istic sum, inquit. Cum praesertim illa perdiscere ludus esset. Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Duarum enim vitarum nobis erunt instituta capienda. Hoc ipsum elegantius poni meliusque potuit. Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; <em>Optime, inquam.</em> Ita credo. <em>Ait enim se, si uratur, Quam hoc suave! dicturum.</em> Itaque hic ipse iam pridem est reiectus; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Compensabatur, inquit, cum summis doloribus laetitia.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? Dic in quovis conventu te omnia facere, ne doleas. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Rationis enim perfectio est virtus;</a> Paulum, cum regem Persem captum adduceret, eodem flumine invectio? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Id enim natura desiderat.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Neque solum ea communia, verum etiam paria esse dixerunt.</li><li>Qui autem de summo bono dissentit de tota philosophiae ratione dissentit.</li><li>Non enim, si omnia non sequebatur, idcirco non erat ortus illinc.</li><li>Ut nemo dubitet, eorum omnia officia quo spectare, quid sequi, quid fugere debeant?</li><li>Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod enim testimonium maius quaerimus, quae honesta et recta sint, ipsa esse optabilia per sese, cum videamus tanta officia morientis? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod idem Peripatetici non tenent, quibus dicendum est, quae et honesta actio sit et sine dolore, eam magis esse expetendam, quam si esset eadem actio cum dolore. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Quid est, quod ab ea absolvi et perfici debeat?</li><li>Hoc loco discipulos quaerere videtur, ut, qui asoti esse velint, philosophi ante fiant.</li><li>Omnis enim est natura diligens sui.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Praeclare hoc quidem. Id et fieri posse et saepe esse factum et ad voluptates percipiendas maxime pertinere. Quicquid porro animo cernimus, id omne oritur a sensibus; Quae animi affectio suum cuique tribuens atque hanc, quam dico. At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit; Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum sit. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nec vero id satis est, neminem esse, qui ipse se oderit, sed illud quoque intellegendum est, neminem esse, qui,quo modo se habeat, nihil sua censeat inte resse. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Paupertas si malum est, mendicus beatus esse nemo potest, quamvis sit sapiens.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nunc vides, quid faciat. Multa sunt dicta ab antiquis de contemnendis ac despiciendis rebus humanis; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ita relinquet duas, de quibus etiam atque etiam consideret.</a> Vide, ne etiam menses! nisi forte eum dicis, qui, simul atque arripuit, interficit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Ut pulsi recurrant? Isto modo, ne si avia quidem eius nata non esset. Cum audissem Antiochum, Brute, ut solebam, cum M. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Apparet statim, quae sint officia, quae actiones.</a> Non est enim vitium in oratione solum, sed etiam in moribus. Frater et T. Graecis hoc modicum est: Leonidas, Epaminondas, tres aliqui aut quattuor; Transfer idem ad modestiam vel temperantiam, quae est moderatio cupiditatum rationi oboediens. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Is cum arderet podagrae doloribus visitassetque hominem Charmides Epicureus perfamiliaris et tristis exiret, Mane, quaeso, inquit, Charmide noster; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading --> +<h2>Non laboro, inquit, de nomine.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Dic in quovis conventu te omnia facere, ne doleas. Tu quidem reddes; Certe, nisi voluptatem tanti aestimaretis. Terram, mihi crede, ea lanx et maria deprimet. Quantam rem agas, ut Circeis qui habitet totum hunc mundum suum municipium esse existimet? Si enim ita est, vide ne facinus facias, cum mori suadeas. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Ex quo illud efficitur, qui bene cenent omnis libenter cenare, qui libenter, non continuo bene.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Dicet pro me ipsa virtus nec dubitabit isti vestro beato M. Hoc dixerit potius Ennius: Nimium boni est, cui nihil est mali. Frater et T. <em>Nam quid possumus facere melius?</em> Qualis ista philosophia est, quae non interitum afferat pravitatis, sed sit contenta mediocritate vitiorum? Neminem videbis ita laudatum, ut artifex callidus comparandarum voluptatum diceretur. An eum discere ea mavis, quae cum plane perdidiceriti nihil sciat? At multis se probavit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis enim est, qui non videat haec esse in natura rerum tria? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ergo, si semel tristior effectus est, hilara vita amissa est?</a> Eaedem res maneant alio modo. Quid enim est a Chrysippo praetermissum in Stoicis? Cum praesertim illa perdiscere ludus esset. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Negat enim summo bono afferre incrementum diem.</a> Utilitatis causa amicitia est quaesita. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Transfer idem ad modestiam vel temperantiam, quae est moderatio cupiditatum rationi oboediens.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hoc ne statuam quidem dicturam pater aiebat, si loqui posset. In qua quid est boni praeter summam voluptatem, et eam sempiternam? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum;</li><li>Cuius quidem, quoniam Stoicus fuit, sententia condemnata mihi videtur esse inanitas ista verborum.</li><li>Quid est, quod ab ea absolvi et perfici debeat?</li><li>Rationis enim perfectio est virtus;</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":4} --> +<h4>Aliter homines, aliter philosophos loqui putas oportere?</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Si est nihil nisi corpus, summa erunt illa: valitudo, vacuitas doloris, pulchritudo, cetera. Quid autem habent admirationis, cum prope accesseris? Itaque his sapiens semper vacabit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tecum optime, deinde etiam cum mediocri amico. Illud dico, ea, quae dicat, praeclare inter se cohaerere. Quid de Platone aut de Democrito loquar? In qua quid est boni praeter summam voluptatem, et eam sempiternam? Itaque vides, quo modo loquantur, nova verba fingunt, deserunt usitata. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qua ex cognitione facilior facta est investigatio rerum occultissimarum.</a> Deinde disputat, quod cuiusque generis animantium statui deceat extremum. <em>Stoici scilicet.</em> Ne amores quidem sanctos a sapiente alienos esse arbitrantur. Quantum Aristoxeni ingenium consumptum videmus in musicis? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quis contra in illa aetate pudorem, constantiam, etiamsi sua nihil intersit, non tamen diligat?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tamen a proposito, inquam, aberramus.</a> Si longus, levis. <strong>Atqui reperies, inquit, in hoc quidem pertinacem;</strong> Recte dicis; <em>Sed in rebus apertissimis nimium longi sumus.</em> In qua si nihil est praeter rationem, sit in una virtute finis bonorum; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ita fit beatae vitae domina fortuna, quam Epicurus ait exiguam intervenire sapienti. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nam illud quidem adduci vix possum, ut ea, quae senserit ille, tibi non vera videantur. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Iis igitur est difficilius satis facere, qui se Latina scripta dicunt contemnere.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quid ait Aristoteles reliquique Platonis alumni? Quo studio Aristophanem putamus aetatem in litteris duxisse? Hoc enim constituto in philosophia constituta sunt omnia. Ergo instituto veterum, quo etiam Stoici utuntur, hinc capiamus exordium. Hanc quoque iucunditatem, si vis, transfer in animum; Alia quaedam dicent, credo, magna antiquorum esse peccata, quae ille veri investigandi cupidus nullo modo ferre potuerit. Omnis enim est natura diligens sui. Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Qui enim existimabit posse se miserum esse beatus non erit.</em> Sextilio Rufo, cum is rem ad amicos ita deferret, se esse heredem Q. Quare si potest esse beatus is, qui est in asperis reiciendisque rebus, potest is quoque esse. Non est enim vitium in oratione solum, sed etiam in moribus. Quod cum dixissent, ille contra. Ut scias me intellegere, primum idem esse dico voluptatem, quod ille don. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quo studio Aristophanem putamus aetatem in litteris duxisse?</a> Et si turpitudinem fugimus in statu et motu corporis, quid est cur pulchritudinem non sequamur? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Laelius clamores sofòw ille so lebat Edere compellans gumias ex ordine nostros.</li><li>Ut proverbia non nulla veriora sint quam vestra dogmata.</li><li>Quamquam ab iis philosophiam et omnes ingenuas disciplinas habemus;</li><li>Illud non continuo, ut aeque incontentae.</li><li>Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit;</li><li>Hoc dixerit potius Ennius: Nimium boni est, cui nihil est mali.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Bonum integritas corporis: misera debilitas. <em>Dici enim nihil potest verius.</em> Paulum, cum regem Persem captum adduceret, eodem flumine invectio? An est aliquid per se ipsum flagitiosum, etiamsi nulla comitetur infamia? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sin tantum modo ad indicia veteris memoriae cognoscenda, curiosorum.</a> Ut id aliis narrare gestiant? Negare non possum. Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria. Deque his rebus satis multa in nostris de re publica libris sunt dicta a Laelio. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Primum in nostrane potestate est, quid meminerimus? <em>Etiam beatissimum?</em> <em>Qua tu etiam inprudens utebare non numquam.</em> Sit enim idem caecus, debilis. Qui autem diffidet perpetuitati bonorum suorum, timeat necesse est, ne aliquando amissis illis sit miser. Ratio quidem vestra sic cogit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit? Quae contraria sunt his, malane? Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Epicurei num desistunt de isdem, de quibus et ab Epicuro scriptum est et ab antiquis, ad arbitrium suum scribere? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><em>Immo videri fortasse.</em> His singulis copiose responderi solet, sed quae perspicua sunt longa esse non debent. Septem autem illi non suo, sed populorum suffragio omnium nominati sunt. Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. Nam Pyrrho, Aristo, Erillus iam diu abiecti. Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quid enim est a Chrysippo praetermissum in Stoicis?</li><li>Quo modo autem philosophus loquitur?</li><li>Qui non moveatur et offensione turpitudinis et comprobatione honestatis?</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":6} --> +<h6>Eadem nunc mea adversum te oratio est.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Aliter enim explicari, quod quaeritur, non potest. Iam doloris medicamenta illa Epicurea tamquam de narthecio proment: Si gravis, brevis; Haec para/doca illi, nos admirabilia dicamus. <em>Sit enim idem caecus, debilis.</em> Facile est hoc cernere in primis puerorum aetatulis. Respondent extrema primis, media utrisque, omnia omnibus. De malis autem et bonis ab iis animalibus, quae nondum depravata sint, ait optime iudicari. Apparet statim, quae sint officia, quae actiones. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Suo genere perveniant ad extremum;</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; <em>Non quam nostram quidem, inquit Pomponius iocans;</em> Nec vero sum nescius esse utilitatem in historia, non modo voluptatem. Res enim se praeclare habebat, et quidem in utraque parte. Cur iustitia laudatur? Istam voluptatem, inquit, Epicurus ignorat? Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Expectoque quid ad id, quod quaerebam, respondeas. <em>Ita multa dicunt, quae vix intellegam.</em> Vide, quantum, inquam, fallare, Torquate. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid adiuvas?</a> Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur. Eam tum adesse, cum dolor omnis absit; Animum autem reliquis rebus ita perfecit, ut corpus; Nulla profecto est, quin suam vim retineat a primo ad extremum. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod quidem pluris est haud paulo magisque ipsum propter se expetendum quam aut sensus aut corporis ea, quae diximus, quibus tantum praestat mentis excellens perfectio, ut vix cogitari possit quid intersit. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Non dolere, inquam, istud quam vim habeat postea videro; Ut proverbia non nulla veriora sint quam vestra dogmata. Sed quid attinet de rebus tam apertis plura requirere? </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Cognitis autem rerum finibus, cum intellegitur, quid sit et bonorum extremum et malorum, inventa vitae via est conformatioque omnium officiorum, cum quaeritur, quo quodque referatur; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Ergo ita: non posse honeste vivi, nisi honeste vivatur? <em>Itaque hic ipse iam pridem est reiectus;</em> Mihi enim satis est, ipsis non satis. Ita ne hoc quidem modo paria peccata sunt. Qui autem esse poteris, nisi te amor ipse ceperit? Commoda autem et incommoda in eo genere sunt, quae praeposita et reiecta diximus; Non quaeritur autem quid naturae tuae consentaneum sit, sed quid disciplinae. <strong>Si id dicis, vicimus.</strong> <em>Nondum autem explanatum satis, erat, quid maxime natura vellet.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Sed quid minus probandum quam esse aliquem beatum nec satis beatum?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Fortasse id optimum, sed ubi illud: Plus semper voluptatis? Itaque ab his ordiamur. Summum a vobis bonum voluptas dicitur. Respondeat totidem verbis. Qui potest igitur habitare in beata vita summi mali metus? <strong>Quid me istud rogas?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Honesta oratio, Socratica, Platonis etiam. Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. Ratio quidem vestra sic cogit. Quid ergo aliud intellegetur nisi uti ne quae pars naturae neglegatur? Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. Quantam rem agas, ut Circeis qui habitet totum hunc mundum suum municipium esse existimet? Nos quidem Virtutes sic natae sumus, ut tibi serviremus, aliud negotii nihil habemus. Itaque hic ipse iam pridem est reiectus; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Easdemne res?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Iam contemni non poteris.</strong> Incommoda autem et commoda-ita enim estmata et dustmata appello-communia esse voluerunt, paria noluerunt. <em>Urgent tamen et nihil remittunt.</em> <em>Fortemne possumus dicere eundem illum Torquatum?</em> Sit, inquam, tam facilis, quam vultis, comparatio voluptatis, quid de dolore dicemus? Huius ego nunc auctoritatem sequens idem faciam. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Hoc Hieronymus summum bonum esse dixit.</li><li>Etenim nec iustitia nec amicitia esse omnino poterunt, nisi ipsae per se expetuntur.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Equidem, sed audistine modo de Carneade? Ita enim vivunt quidam, ut eorum vita refellatur oratio. Multoque hoc melius nos veriusque quam Stoici. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ecce aliud simile dissimile.</a> Sed eum qui audiebant, quoad poterant, defendebant sententiam suam. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Que Manilium, ab iisque M.</a> Quasi ego id curem, quid ille aiat aut neget. Quamquam tu hanc copiosiorem etiam soles dicere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Quo tandem modo?</em> <em>Cur post Tarentum ad Archytam?</em> Quodsi ipsam honestatem undique pertectam atque absolutam. <strong>Eadem nunc mea adversum te oratio est.</strong> Quodsi ipsam honestatem undique pertectam atque absolutam. Idemque diviserunt naturam hominis in animum et corpus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed quanta sit alias, nunc tantum possitne esse tanta. <em>Haec quo modo conveniant, non sane intellego.</em> Torquatus, is qui consul cum Cn. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quorum altera prosunt, nocent altera.</a> Quae cum praeponunt, ut sit aliqua rerum selectio, naturam videntur sequi; Invidiosum nomen est, infame, suspectum. Eorum enim est haec querela, qui sibi cari sunt seseque diligunt. Duae sunt enim res quoque, ne tu verba solum putes. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quis Aristidem non mortuum diligit?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Urgent tamen et nihil remittunt. <strong>Huius, Lyco, oratione locuples, rebus ipsis ielunior.</strong> Nihil enim iam habes, quod ad corpus referas; Quid enim me prohiberet Epicureum esse, si probarem, quae ille diceret? <em>Cui Tubuli nomen odio non est?</em> Quamquam te quidem video minime esse deterritum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Haeret in salebra.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Bonum negas esse divitias, praeposìtum esse dicis?</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Facete M.</a> Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. Quam nemo umquam voluptatem appellavit, appellat; De malis autem et bonis ab iis animalibus, quae nondum depravata sint, ait optime iudicari. Facit igitur Lucius noster prudenter, qui audire de summo bono potissimum velit; <em>Tollenda est atque extrahenda radicitus.</em> Theophrasti igitur, inquit, tibi liber ille placet de beata vita? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quamquam in hac divisione rem ipsam prorsus probo, elegantiam desidero. Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. Ut pulsi recurrant? Quid turpius quam sapientis vitam ex insipientium sermone pendere? <strong>Si quicquam extra virtutem habeatur in bonis.</strong> Ergo opifex plus sibi proponet ad formarum quam civis excellens ad factorum pulchritudinem? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Perge porro;</a> Ecce aliud simile dissimile. Longum est enim ad omnia respondere, quae a te dicta sunt. Quodsi ipsam honestatem undique pertectam atque absolutam. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Vides igitur, si amicitiam sua caritate metiare, nihil esse praestantius, sin emolumento, summas familiaritates praediorum fructuosorum mercede superari. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Quae similitudo in genere etiam humano apparet. <em>Disserendi artem nullam habuit.</em> Septem autem illi non suo, sed populorum suffragio omnium nominati sunt. Certe non potest. Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">An haec ab eo non dicuntur?</a> <em>Summum a vobis bonum voluptas dicitur.</em> Sed haec in pueris; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Restinguet citius, si ardentem acceperit.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Negat esse eam, inquit, propter se expetendam.</h2> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Eam si varietatem diceres, intellegerem, ut etiam non dicente te intellego; Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. Scio enim esse quosdam, qui quavis lingua philosophari possint; Nam illud quidem adduci vix possum, ut ea, quae senserit ille, tibi non vera videantur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non igitur bene.</a> Aliter enim nosmet ipsos nosse non possumus. Expectoque quid ad id, quod quaerebam, respondeas. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Si qua in iis corrigere voluit, deteriora fecit.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Non risu potius quam oratione eiciendum?</strong> Hoc loco tenere se Triarius non potuit. Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus; <strong>Is es profecto tu.</strong> Tu enim ista lenius, hic Stoicorum more nos vexat. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Atqui reperiemus asotos primum ita non religiosos, ut edint de patella, deinde ita mortem non timentes, ut illud in ore habeant ex Hymnide: Mihi sex menses satis sunt vitae, septimum Orco spondeo. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Quem Tiberina descensio festo illo die tanto gaudio affecit, quanto L.</li><li>In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt.</li><li>Omnes enim iucundum motum, quo sensus hilaretur.</li><li>Quae cum dixisset paulumque institisset, Quid est?</li><li>His enim rebus detractis negat se reperire in asotorum vita quod reprehendat.</li><li>Profectus in exilium Tubulus statim nec respondere ausus;</li><li>Sin kakan malitiam dixisses, ad aliud nos unum certum vitium consuetudo Latina traduceret.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>At cum de plurimis eadem dicit, tum certe de maximis.</li><li>Apparet statim, quae sint officia, quae actiones.</li><li>Nam si propter voluptatem, quae est ista laus, quae possit e macello peti?</li><li>Ut pulsi recurrant?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Sed tamen enitar et, si minus multa mihi occurrent, non fugiam ista popularia. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Idemne, quod iucunde?</a> Ut enim consuetudo loquitur, id solum dicitur honestum, quod est populari fama gloriosum. Beatus sibi videtur esse moriens. Sed plane dicit quod intellegit. Quo modo autem philosophus loquitur? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Dicimus aliquem hilare vivere;</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; Ea possunt paria non esse. Sed ad haec, nisi molestum est, habeo quae velim. Cur deinde Metrodori liberos commendas? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tu vero, inquam, ducas licet, si sequetur; Nosti, credo, illud: Nemo pius est, qui pietatem-; <em>Cur post Tarentum ad Archytam?</em> At cum de plurimis eadem dicit, tum certe de maximis. Hoc positum in Phaedro a Platone probavit Epicurus sensitque in omni disputatione id fieri oportere. At eum nihili facit; Bestiarum vero nullum iudicium puto. Sapiens autem semper beatus est et est aliquando in dolore; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Iam vero animus non esse solum, sed etiam cuiusdam modi debet esse, ut et omnis partis suas habeat incolumis et de virtutibus nulla desit. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Et ille ridens: Video, inquit, quid agas; Haec quo modo conveniant, non sane intellego. Hoc tu nunc in illo probas. Summum a vobis bonum voluptas dicitur. Dicet pro me ipsa virtus nec dubitabit isti vestro beato M. Sed quot homines, tot sententiae; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Videamus animi partes, quarum est conspectus illustrior; <em>Age, inquies, ista parva sunt.</em> <strong>Cur deinde Metrodori liberos commendas?</strong> Nosti, credo, illud: Nemo pius est, qui pietatem-; Nemo igitur esse beatus potest. Nam si quae sunt aliae, falsum est omnis animi voluptates esse e corporis societate. Num igitur eum postea censes anxio animo aut sollicito fuisse? Hoc dixerit potius Ennius: Nimium boni est, cui nihil est mali. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Duarum enim vitarum nobis erunt instituta capienda. Quid est, quod ab ea absolvi et perfici debeat? <strong>Tu vero, inquam, ducas licet, si sequetur;</strong> Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum integritas corporis: misera debilitas.</a> Si quicquam extra virtutem habeatur in bonis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Illud non continuo, ut aeque incontentae. Ita cum ea volunt retinere, quae superiori sententiae conveniunt, in Aristonem incidunt; Cum praesertim illa perdiscere ludus esset. Ne in odium veniam, si amicum destitero tueri. Deinde disputat, quod cuiusque generis animantium statui deceat extremum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Haec dicuntur inconstantissime.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>At quanta conantur! Mundum hunc omnem oppidum esse nostrum! Incendi igitur eos, qui audiunt, vides. Hoc loco tenere se Triarius non potuit. Hoc positum in Phaedro a Platone probavit Epicurus sensitque in omni disputatione id fieri oportere. Quid turpius quam sapientis vitam ex insipientium sermone pendere? Non enim quaero quid verum, sed quid cuique dicendum sit. Quamquam in hac divisione rem ipsam prorsus probo, elegantiam desidero. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid ad utilitatem tantae pecuniae?</a> Non quam nostram quidem, inquit Pomponius iocans; At certe gravius. Duarum enim vitarum nobis erunt instituta capienda. Ergo infelix una molestia, fellx rursus, cum is ipse anulus in praecordiis piscis inventus est? Aeque enim contingit omnibus fidibus, ut incontentae sint. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Peccata paria.</a> Ita nemo beato beatior. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">An potest cupiditas finiri?</a> Suo genere perveniant ad extremum; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Audax negotium, dicerem impudens, nisi hoc institutum postea translatum ad philosophos nostros esset. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":4} --> +<h4>Quid turpius quam sapientis vitam ex insipientium sermone pendere?</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Deinde disputat, quod cuiusque generis animantium statui deceat extremum. Sed ille, ut dixi, vitiose. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque contra est, ac dicitis;</a> Hanc quoque iucunditatem, si vis, transfer in animum; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Duo enim genera quae erant, fecit tria.</a> Certe nihil nisi quod possit ipsum propter se iure laudari. Deque his rebus satis multa in nostris de re publica libris sunt dicta a Laelio. Quia nec honesto quic quam honestius nec turpi turpius. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Itaque haec cum illis est dissensio, cum Peripateticis nulla sane. At quicum ioca seria, ut dicitur, quicum arcana, quicum occulta omnia? <em>Ille vero, si insipiens-quo certe, quoniam tyrannus -, numquam beatus;</em> Non dolere, inquam, istud quam vim habeat postea videro; Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non est igitur voluptas bonum.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nonne videmus quanta perturbatio rerum omnium consequatur, quanta confusio? Deinde prima illa, quae in congressu solemus: Quid tu, inquit, huc? Universa enim illorum ratione cum tota vestra confligendum puto. At miser, si in flagitiosa et vitiosa vita afflueret voluptatibus. Quid, si non sensus modo ei sit datus, verum etiam animus hominis? Restinguet citius, si ardentem acceperit. Nobis Heracleotes ille Dionysius flagitiose descivisse videtur a Stoicis propter oculorum dolorem. Apparet statim, quae sint officia, quae actiones. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Summum a vobis bonum voluptas dicitur.</a> Mihi enim erit isdem istis fortasse iam utendum. Ergo adhuc, quantum equidem intellego, causa non videtur fuisse mutandi nominis. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Etsi hoc quidem est in vitio, dissolutionem naturae tam valde perhorrescere-quod item est reprehendendum in dolore -, sed quia fere sic afficiuntur omnes, satis argomenti est ab interitu naturam abhorrere; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Perfecto enim et concluso neque virtutibus neque amicitiis usquam locum esse, si ad voluptatem omnia referantur, nihil praeterea est magnopere dicendum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Solum praeterea formosum, solum liberum, solum civem, stultost; <strong>Tibi hoc incredibile, quod beatissimum.</strong> Quos quidem tibi studiose et diligenter tractandos magnopere censeo. Sed hoc sane concedamus. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque hic ipse iam pridem est reiectus;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Sapientem locupletat ipsa natura, cuius divitias Epicurus parabiles esse docuit. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; Quodcumque in mentem incideret, et quodcumque tamquam occurreret. Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Cur deinde Metrodori liberos commendas? Quamvis enim depravatae non sint, pravae tamen esse possunt. Sed eum qui audiebant, quoad poterant, defendebant sententiam suam. Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere. Sed quanta sit alias, nunc tantum possitne esse tanta. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Num igitur utiliorem tibi hunc Triarium putas esse posse, quam si tua sint Puteolis granaria? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quo minus animus a se ipse dissidens secumque discordans gustare partem ullam liquidae voluptatis et liberae potest. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":3} --> +<h3>Quae diligentissime contra Aristonem dicuntur a Chryippo.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Praeclarae mortes sunt imperatoriae; <em>At certe gravius.</em> Ad eas enim res ab Epicuro praecepta dantur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Confecta res esset.</a> Invidiosum nomen est, infame, suspectum. Non semper, inquam; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Dolor ergo, id est summum malum, metuetur semper, etiamsi non aderit; A mene tu? Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. Quid loquor de nobis, qui ad laudem et ad decus nati, suscepti, instituti sumus? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nulla profecto est, quin suam vim retineat a primo ad extremum. Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum; Sin autem eos non probabat, quid attinuit cum iis, quibuscum re concinebat, verbis discrepare? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod quidem iam fit etiam in Academia.</a> <strong>At enim hic etiam dolore.</strong> Inde igitur, inquit, ordiendum est. Sit hoc ultimum bonorum, quod nunc a me defenditur; Mihi, inquam, qui te id ipsum rogavi? <em>Nihilo beatiorem esse Metellum quam Regulum.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Quis est, qui non oderit libidinosam, protervam adolescentiam?</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Themistocles quidem, cum ei Simonides an quis alius artem memoriae polliceretur, Oblivionis, inquit, mallem. <strong>Comprehensum, quod cognitum non habet?</strong> Claudii libidini, qui tum erat summo ne imperio, dederetur. Superiores tres erant, quae esse possent, quarum est una sola defensa, eaque vehementer. Quia nec honesto quic quam honestius nec turpi turpius. Sed tamen est aliquid, quod nobis non liceat, liceat illis. Conferam tecum, quam cuique verso rem subicias; Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. <em>Invidiosum nomen est, infame, suspectum.</em> Quae quidem vel cum periculo est quaerenda vobis; De ingenio eius in his disputationibus, non de moribus quaeritur. Hoc dixerit potius Ennius: Nimium boni est, cui nihil est mali. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Comprehensum, quod cognitum non habet?</a> Laboro autem non sine causa; Quae cum dixisset paulumque institisset, Quid est? Quoniam, si dis placet, ab Epicuro loqui discimus. Verum hoc idem saepe faciamus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non potes, nisi retexueris illa.</a> Tertium autem omnibus aut maximis rebus iis, quae secundum naturam sint, fruentem vivere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>At iam decimum annum in spelunca iacet.</em> <strong>Illa videamus, quae a te de amicitia dicta sunt.</strong> Hoc ipsum elegantius poni meliusque potuit. Apparet statim, quae sint officia, quae actiones. Hic ambiguo ludimur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur. Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. Maximus dolor, inquit, brevis est. <strong>Quod cum dixissent, ille contra.</strong> Quis Aristidem non mortuum diligit? Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? Nummus in Croesi divitiis obscuratur, pars est tamen divitiarum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quae quo sunt excelsiores, eo dant clariora indicia naturae.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Beatus sibi videtur esse moriens.</em> <em>Aufert enim sensus actionemque tollit omnem.</em> Certe non potest. Sed quae tandem ista ratio est? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Istam voluptatem, inquit, Epicurus ignorat?</a> Quis suae urbis conservatorem Codrum, quis Erechthei filias non maxime laudat? Universa enim illorum ratione cum tota vestra confligendum puto. <strong>Tum mihi Piso: Quid ergo?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Et quod est munus, quod opus sapientiae?</li><li>Quid, si non sensus modo ei sit datus, verum etiam animus hominis?</li><li>Quid vero?</li><li>Quamquam non negatis nos intellegere quid sit voluptas, sed quid ille dicat.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Inde sermone vario sex illa a Dipylo stadia confecimus. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ut necesse sit omnium rerum, quae natura vigeant, similem esse finem, non eundem. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":6} --> +<h6>Idem iste, inquam, de voluptate quid sentit?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Tecum optime, deinde etiam cum mediocri amico. Nemo igitur esse beatus potest. <strong>At multis se probavit.</strong> Qui enim existimabit posse se miserum esse beatus non erit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Erat enim Polemonis.</a> <strong>Eadem nunc mea adversum te oratio est.</strong> Inscite autem medicinae et gubernationis ultimum cum ultimo sapientiae comparatur. Hoc etsi multimodis reprehendi potest, tamen accipio, quod dant. <em>Nemo igitur esse beatus potest.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quae in controversiam veniunt, de iis, si placet, disseramus. Non enim in selectione virtus ponenda erat, ut id ipsum, quod erat bonorum ultimum, aliud aliquid adquireret. Sed quid attinet de rebus tam apertis plura requirere? <em>Illud dico, ea, quae dicat, praeclare inter se cohaerere.</em> Quae cum dixisset paulumque institisset, Quid est? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At coluit ipse amicitias.</a> Habent enim et bene longam et satis litigiosam disputationem. Quantam rem agas, ut Circeis qui habitet totum hunc mundum suum municipium esse existimet? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. Quod autem satis est, eo quicquid accessit, nimium est; At hoc in eo M. Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. <strong>Quid sequatur, quid repugnet, vident.</strong> Nulla profecto est, quin suam vim retineat a primo ad extremum. Ita ne hoc quidem modo paria peccata sunt. Nihil opus est exemplis hoc facere longius. Quod autem principium officii quaerunt, melius quam Pyrrho; Conferam tecum, quam cuique verso rem subicias; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ostendit pedes et pectus. <strong>Minime vero, inquit ille, consentit.</strong> <em>Idemne potest esse dies saepius, qui semel fuit?</em> <strong>Duarum enim vitarum nobis erunt instituta capienda.</strong> Conclusum est enim contra Cyrenaicos satis acute, nihil ad Epicurum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nos commodius agimus.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Cur igitur, inquam, res tam dissimiles eodem nomine appellas?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Sumenda potius quam expetenda. Ita graviter et severe voluptatem secrevit a bono. Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. Quae quo sunt excelsiores, eo dant clariora indicia naturae. At iam decimum annum in spelunca iacet. Nam et a te perfici istam disputationem volo, nec tua mihi oratio longa videri potest. Somnum denique nobis, nisi requietem corporibus et is medicinam quandam laboris afferret, contra naturam putaremus datum; Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta. <strong>Quo modo?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Ratio quidem vestra sic cogit.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Philosophi autem in suis lectulis plerumque moriuntur. Nam Pyrrho, Aristo, Erillus iam diu abiecti. Haec igitur Epicuri non probo, inquam. In quo etsi est magnus, tamen nova pleraque et perpauca de moribus. Itaque eos id agere, ut a se dolores, morbos, debilitates repellant. Nunc haec primum fortasse audientis servire debemus. Sin autem est in ea, quod quidam volunt, nihil impedit hanc nostram comprehensionem summi boni. Non dolere, inquam, istud quam vim habeat postea videro; Ratio quidem vestra sic cogit. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Quodsi ipsam honestatem undique pertectam atque absolutam.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ergo hoc quidem apparet, nos ad agendum esse natos. <em>Tuo vero id quidem, inquam, arbitratu.</em> Est autem officium, quod ita factum est, ut eius facti probabilis ratio reddi possit. Dicam, inquam, et quidem discendi causa magis, quam quo te aut Epicurum reprehensum velim. <strong>Cur deinde Metrodori liberos commendas?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Se omnia, quae secundum naturam sint, b o n a appellare, quae autem contra, m a l a.</li><li>Inscite autem medicinae et gubernationis ultimum cum ultimo sapientiae comparatur.</li><li>Minime vero, inquit ille, consentit.</li><li>Vulgo enim dicitur: Iucundi acti labores, nec male Euripidesconcludam, si potero, Latine;</li><li>Universa enim illorum ratione cum tota vestra confligendum puto.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":5} --> +<h5>Ut proverbia non nulla veriora sint quam vestra dogmata.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Ita prorsus, inquam;</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quam nemo umquam voluptatem appellavit, appellat;</a> <em>Minime vero, inquit ille, consentit.</em> An vero, inquit, quisquam potest probare, quod perceptfum, quod. Nihilo beatiorem esse Metellum quam Regulum. Dolere malum est: in crucem qui agitur, beatus esse non potest. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. Inscite autem medicinae et gubernationis ultimum cum ultimo sapientiae comparatur. Nihil opus est exemplis hoc facere longius. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Sive hoc difficile est, tamen nec modus est ullus investigandi veri, nisi inveneris, et quaerendi defatigatio turpis est, cum id, quod quaeritur, sit pulcherrimum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":4} --> +<h4>Miserum hominem! Si dolor summum malum est, dici aliter non potest.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Itaque hic ipse iam pridem est reiectus; <strong>Et quidem, inquit, vehementer errat;</strong> Servari enim iustitia nisi a forti viro, nisi a sapiente non potest. Hunc vos beatum; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nam Pyrrho, Aristo, Erillus iam diu abiecti.</a> Ex ea difficultate illae fallaciloquae, ut ait Accius, malitiae natae sunt. Vadem te ad mortem tyranno dabis pro amico, ut Pythagoreus ille Siculo fecit tyranno? Ego quoque, inquit, didicerim libentius si quid attuleris, quam te reprehenderim. Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Vide, ne etiam menses! nisi forte eum dicis, qui, simul atque arripuit, interficit. Age, inquies, ista parva sunt. Quae adhuc, Cato, a te dicta sunt, eadem, inquam, dicere posses, si sequerere Pyrrhonem aut Aristonem. <strong>Ratio enim nostra consentit, pugnat oratio.</strong> Immo alio genere; Nulla profecto est, quin suam vim retineat a primo ad extremum. </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Mihi enim satis est, ipsis non satis. Bonum liberi: misera orbitas. Ita fit beatae vitae domina fortuna, quam Epicurus ait exiguam intervenire sapienti. Nosti, credo, illud: Nemo pius est, qui pietatem-; Quamquam id quidem, infinitum est in hac urbe; Id est enim, de quo quaerimus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quoniam, si dis placet, ab Epicuro loqui discimus.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Beatus sibi videtur esse moriens.</a> Haec dicuntur inconstantissime. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Negat enim summo bono afferre incrementum diem.</li><li>Deinde prima illa, quae in congressu solemus: Quid tu, inquit, huc?</li><li>Cuius similitudine perspecta in formarum specie ac dignitate transitum est ad honestatem dictorum atque factorum.</li><li>Si enim non fuit eorum iudicii, nihilo magis hoc non addito illud est iudicatum-.</li><li>Et hanc quidem primam exigam a te operam, ut audias me quae a te dicta sunt refellentem.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>At hoc in eo M.</li><li>Indicant pueri, in quibus ut in speculis natura cernitur.</li><li>In qua si nihil est praeter rationem, sit in una virtute finis bonorum;</li><li>Hoc etsi multimodis reprehendi potest, tamen accipio, quod dant.</li><li>Atque haec coniunctio confusioque virtutum tamen a philosophis ratione quadam distinguitur.</li><li>Ex ea difficultate illae fallaciloquae, ut ait Accius, malitiae natae sunt.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Audio equidem philosophi vocem, Epicure, sed quid tibi dicendum sit oblitus es. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Illud dico, ea, quae dicat, praeclare inter se cohaerere.</a> Apparet statim, quae sint officia, quae actiones. Haeret in salebra. Est igitur officium eius generis, quod nec in bonis ponatur nec in contrariis. Praeteritis, inquit, gaudeo. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Hic, qui utrumque probat, ambobus debuit uti, sicut facit re, neque tamen dividit verbis. Sed ego in hoc resisto; Si enim, ut mihi quidem videtur, non explet bona naturae voluptas, iure praetermissa est; Mihi quidem Homerus huius modi quiddam vidisse videatur in iis, quae de Sirenum cantibus finxerit. Quid sequatur, quid repugnet, vident. In schola desinis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>A primo, ut opinor, animantium ortu petitur origo summi boni. Re mihi non aeque satisfacit, et quidem locis pluribus. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Cum praesertim illa perdiscere ludus esset. Dic in quovis conventu te omnia facere, ne doleas. Te ipsum, dignissimum maioribus tuis, voluptasne induxit, ut adolescentulus eriperes P. Atque his de rebus et splendida est eorum et illustris oratio. Sed haec quidem liberius ab eo dicuntur et saepius. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Si enim ad populum me vocas, eum. Ita multo sanguine profuso in laetitia et in victoria est mortuus. Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. Stoici autem, quod finem bonorum in una virtute ponunt, similes sunt illorum; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Gloriosa ostentatio in constituendo summo bono.</a> Dulce amarum, leve asperum, prope longe, stare movere, quadratum rotundum. Nunc de hominis summo bono quaeritur; Maximas vero virtutes iacere omnis necesse est voluptate dominante. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ut aliquid scire se gaudeant? Aliter enim nosmet ipsos nosse non possumus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae cum dixisset, finem ille.</a> Haec para/doca illi, nos admirabilia dicamus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Aliter enim nosmet ipsos nosse non possumus. Que Manilium, ab iisque M. Sed hoc sane concedamus. Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Tria genera cupiditatum, naturales et necessariae, naturales et non necessariae, nec naturales nec necessariae. De quibus cupio scire quid sentias. Omnia contraria, quos etiam insanos esse vultis. Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? Idem iste, inquam, de voluptate quid sentit? <strong>Cur id non ita fit?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":6} --> +<h6>Atqui haec patefactio quasi rerum opertarum, cum quid quidque sit aperitur, definitio est.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Non quaeritur autem quid naturae tuae consentaneum sit, sed quid disciplinae. Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? Quid affers, cur Thorius, cur Caius Postumius, cur omnium horum magister, Orata, non iucundissime vixerit? <em>Neutrum vero, inquit ille.</em> Sextilio Rufo, cum is rem ad amicos ita deferret, se esse heredem Q. Inde sermone vario sex illa a Dipylo stadia confecimus. Respondent extrema primis, media utrisque, omnia omnibus. Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat. Facillimum id quidem est, inquam. Qui est in parvis malis. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Igitur ne dolorem quidem.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>An est aliquid, quod te sua sponte delectet?</em> Tecum optime, deinde etiam cum mediocri amico. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>His similes sunt omnes, qui virtuti student levantur vitiis, levantur erroribus, nisi forte censes Ti.</li><li>Ergo omni animali illud, quod appetiti positum est in eo, quod naturae est accommodatum.</li><li>Igitur neque stultorum quisquam beatus neque sapientium non beatus.</li><li>Tum ille: Ain tandem?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Bona autem corporis huic sunt, quod posterius posui, similiora. Itaque dicunt nec dubitant: mihi sic usus est, tibi ut opus est facto, fac. Vitae autem degendae ratio maxime quidem illis placuit quieta. <strong>Deinde dolorem quem maximum?</strong> Si mala non sunt, iacet omnis ratio Peripateticorum. Ita multa dicunt, quae vix intellegam. Quamquam haec quidem praeposita recte et reiecta dicere licebit. Non est ista, inquam, Piso, magna dissensio. Id enim volumus, id contendimus, ut officii fructus sit ipsum officium. Nihil opus est exemplis hoc facere longius. Sed ad bona praeterita redeamus. Itaque contra est, ac dicitis; Hanc quoque iucunditatem, si vis, transfer in animum; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sequitur disserendi ratio cognitioque naturae; <em>Zenonis est, inquam, hoc Stoici.</em> Themistocles quidem, cum ei Simonides an quis alius artem memoriae polliceretur, Oblivionis, inquit, mallem. Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? Aliis esse maiora, illud dubium, ad id, quod summum bonum dicitis, ecquaenam possit fieri accessio. Ut in geometria, prima si dederis, danda sunt omnia. Si enim ita est, vide ne facinus facias, cum mori suadeas. Inde sermone vario sex illa a Dipylo stadia confecimus. <strong>Expectoque quid ad id, quod quaerebam, respondeas.</strong> Hoc dictum in una re latissime patet, ut in omnibus factis re, non teste moveamur. Esse enim, nisi eris, non potes. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Non igitur de improbo, sed de callido improbo quaerimus, qualis Q. Qui ita affectus, beatum esse numquam probabis; Vitiosum est enim in dividendo partem in genere numerare. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Suo enim quisque studio maxime ducitur.</a> <em>Quare conare, quaeso.</em> Tu autem negas fortem esse quemquam posse, qui dolorem malum putet. Sin ea non neglegemus neque tamen ad finem summi boni referemus, non multum ab Erilli levitate aberrabimus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Aliter enim explicari, quod quaeritur, non potest. Ita relinquet duas, de quibus etiam atque etiam consideret. <strong>Hunc vos beatum;</strong> <em>Quis istum dolorem timet?</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Non est ista, inquam, Piso, magna dissensio. <strong>Tubulo putas dicere?</strong> Teneo, inquit, finem illi videri nihil dolere. Summus dolor plures dies manere non potest? Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. <em>Quae cum essent dicta, discessimus.</em> <strong>Ac tamen hic mallet non dolere.</strong> Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. <em>Ea possunt paria non esse.</em> Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Itaque eos id agere, ut a se dolores, morbos, debilitates repellant.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. Negat esse eam, inquit, propter se expetendam. Nihilo beatiorem esse Metellum quam Regulum. Ut alios omittam, hunc appello, quem ille unum secutus est. Eaedem enim utilitates poterunt eas labefactare atque pervertere. Et harum quidem rerum facilis est et expedita distinctio. At iste non dolendi status non vocatur voluptas. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse;</a> Nondum autem explanatum satis, erat, quid maxime natura vellet. Itaque eos id agere, ut a se dolores, morbos, debilitates repellant. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Quae contraria sunt his, malane?</em> Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Eam stabilem appellas. Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. <strong>A mene tu?</strong> Uterque enim summo bono fruitur, id est voluptate. <strong>Nunc haec primum fortasse audientis servire debemus.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Ait enim se, si uratur, Quam hoc suave! dicturum.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Inquit, respondet: Quia, nisi quod honestum est, nullum est aliud bonum! Non quaero iam verumne sit; Quae cum dixisset, finem ille. Res enim se praeclare habebat, et quidem in utraque parte. Nosti, credo, illud: Nemo pius est, qui pietatem-; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nos cum te, M.</a> Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quid paulo ante, inquit, dixerim nonne meministi, cum omnis dolor detractus esset, variari, non augeri voluptatem? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Cum salvum esse flentes sui respondissent, rogavit essentne fusi hostes.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>At ille pellit, qui permulcet sensum voluptate. Sed haec quidem liberius ab eo dicuntur et saepius. Si sapiens, ne tum quidem miser, cum ab Oroete, praetore Darei, in crucem actus est. Praeclare hoc quidem. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Graecum enim hunc versum nostis omnes-: Suavis laborum est praeteritorum memoria. Ut in geometria, prima si dederis, danda sunt omnia. Nihil ad rem! Ne sit sane; <em>Nunc omni virtuti vitium contrario nomine opponitur.</em> <em>Quid iudicant sensus?</em> Age, inquies, ista parva sunt. Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Moriatur, inquit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum integritas corporis: misera debilitas.</a> <em>Haec igitur Epicuri non probo, inquam.</em> <strong>Contemnit enim disserendi elegantiam, confuse loquitur.</strong> Age sane, inquam. <em>Quamquam te quidem video minime esse deterritum.</em> Respondent extrema primis, media utrisque, omnia omnibus. Istam voluptatem, inquit, Epicurus ignorat? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Qui est in parvis malis.</li><li>Sapientem locupletat ipsa natura, cuius divitias Epicurus parabiles esse docuit.</li><li>Quid, si reviviscant Platonis illi et deinceps qui eorum auditores fuerunt, et tecum ita loquantur?</li><li>Non est igitur summum malum dolor.</li><li>In quo etsi est magnus, tamen nova pleraque et perpauca de moribus.</li><li>Et ille ridens: Video, inquit, quid agas;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Quo plebiscito decreta a senatu est consuli quaestio Cn. Quae quidem vel cum periculo est quaerenda vobis; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nam ista vestra: Si gravis, brevis; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si quae forte-possumus.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Sed haec quidem liberius ab eo dicuntur et saepius.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Sint modo partes vitae beatae.</strong> Iam doloris medicamenta illa Epicurea tamquam de narthecio proment: Si gravis, brevis; Inde sermone vario sex illa a Dipylo stadia confecimus. <strong>Inde igitur, inquit, ordiendum est.</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Velut ego nunc moveor.</a> Ex quo illud efficitur, qui bene cenent omnis libenter cenare, qui libenter, non continuo bene. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Verum esto: verbum ipsum voluptatis non habet dignitatem, nec nos fortasse intellegimus. Huius, Lyco, oratione locuples, rebus ipsis ielunior. Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. An potest, inquit ille, quicquam esse suavius quam nihil dolere? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ita ne hoc quidem modo paria peccata sunt.</a> Quamquam tu hanc copiosiorem etiam soles dicere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Qui enim voluptatem ipsam contemnunt, iis licet dicere se acupenserem maenae non anteponere. Quamquam tu hanc copiosiorem etiam soles dicere. Quamquam tu hanc copiosiorem etiam soles dicere. Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo. Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis. Non dolere, inquam, istud quam vim habeat postea videro; <em>Quis istud possit, inquit, negare?</em> <strong>Age, inquies, ista parva sunt.</strong> Incommoda autem et commoda-ita enim estmata et dustmata appello-communia esse voluerunt, paria noluerunt. Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ita, quae mutat, ea corrumpit, quae sequitur sunt tota Democriti, atomi, inane, imagines, quae eidola nominant, quorum incursione non solum videamus, sed etiam cogitemus; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":6} --> +<h6>Sin laboramus, quis est, qui alienae modum statuat industriae?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quid enim possumus hoc agere divinius? Sapiens autem semper beatus est et est aliquando in dolore; Memini vero, inquam; In his igitur partibus duabus nihil erat, quod Zeno commutare gestiret. Mihi quidem Antiochum, quem audis, satis belle videris attendere. Vitiosum est enim in dividendo partem in genere numerare. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis est enim, in quo sit cupiditas, quin recte cupidus dici possit? Haec bene dicuntur, nec ego repugno, sed inter sese ipsa pugnant. Illis videtur, qui illud non dubitant bonum dicere -; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At certe gravius.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Invidiosum nomen est, infame, suspectum.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Dicet pro me ipsa virtus nec dubitabit isti vestro beato M. Eiuro, inquit adridens, iniquum, hac quidem de re; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quis non odit sordidos, vanos, leves, futtiles?</a> Equidem e Cn. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quem Tiberina descensio festo illo die tanto gaudio affecit, quanto L.</li><li>Ergo ita: non posse honeste vivi, nisi honeste vivatur?</li><li>Quodcumque in mentem incideret, et quodcumque tamquam occurreret.</li><li>Itaque primos congressus copulationesque et consuetudinum instituendarum voluntates fieri propter voluptatem;</li><li>Virtutibus igitur rectissime mihi videris et ad consuetudinem nostrae orationis vitia posuisse contraria.</li><li>Aliter enim explicari, quod quaeritur, non potest.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":4} --> +<h4>Hic ambiguo ludimur.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Aliter enim nosmet ipsos nosse non possumus. Oculorum, inquit Plato, est in nobis sensus acerrimus, quibus sapientiam non cernimus. Animadverti, ínquam, te isto modo paulo ante ponere, et scio ab Antiocho nostro dici sic solere; <strong>Si quicquam extra virtutem habeatur in bonis.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tria genera bonorum; Nihilne te delectat umquam -video, quicum loquar-, te igitur, Torquate, ipsum per se nihil delectat? Ne in odium veniam, si amicum destitero tueri. Equidem e Cn. Et hunc idem dico, inquieta sed ad virtutes et ad vitia nihil interesse. An eum discere ea mavis, quae cum plane perdidiceriti nihil sciat? Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Age, inquies, ista parva sunt.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Non ego tecum iam ita iocabor, ut isdem his de rebus, cum L.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Sic consequentibus vestris sublatis prima tolluntur.</em> Suo genere perveniant ad extremum; Vitiosum est enim in dividendo partem in genere numerare. <em>Rationis enim perfectio est virtus;</em> Putabam equidem satis, inquit, me dixisse. Quae similitudo in genere etiam humano apparet. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Beatus autem esse in maximarum rerum timore nemo potest. Quo plebiscito decreta a senatu est consuli quaestio Cn. Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. Duo enim genera quae erant, fecit tria. <strong>Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse;</strong> Non autem hoc: igitur ne illud quidem. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed fac ista esse non inportuna; Omnia contraria, quos etiam insanos esse vultis. <strong>Videamus animi partes, quarum est conspectus illustrior;</strong> Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Quid enim necesse est, tamquam meretricem in matronarum coetum, sic voluptatem in virtutum concilium adducere? <strong>Eaedem res maneant alio modo.</strong> Cur deinde Metrodori liberos commendas? Quid ergo? Ut alios omittam, hunc appello, quem ille unum secutus est. Scripta sane et multa et polita, sed nescio quo pacto auctoritatem oratio non habet. An est aliquid, quod te sua sponte delectet? <em>Sed hoc sane concedamus.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Intellegi quidem, ut propter aliam quampiam rem, verbi gratia propter voluptatem, nos amemus;</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>An dolor longissimus quisque miserrimus, voluptatem non optabiliorem diuturnitas facit? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ergo ita: non posse honeste vivi, nisi honeste vivatur?</a> Teneo, inquit, finem illi videri nihil dolere. Nunc omni virtuti vitium contrario nomine opponitur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quorum sine causa fieri nihil putandum est.</a> Iam id ipsum absurdum, maximum malum neglegi. Nos paucis ad haec additis finem faciamus aliquando; Quod mihi quidem visus est, cum sciret, velle tamen confitentem audire Torquatum. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tu enim ista lenius, hic Stoicorum more nos vexat. Quid enim tanto opus est instrumento in optimis artibus comparandis? Bonum integritas corporis: misera debilitas. At modo dixeras nihil in istis rebus esse, quod interesset. Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Efficiens dici potest.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Isto modo ne improbos quidem, si essent boni viri. An est aliquid per se ipsum flagitiosum, etiamsi nulla comitetur infamia? <em>Satisne vobis videor pro meo iure in vestris auribus commentatus?</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Frater et T.</a> Sed vos squalidius, illorum vides quam niteat oratio. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Eam tum adesse, cum dolor omnis absit;</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Sit hoc ultimum bonorum, quod nunc a me defenditur; Animum autem reliquis rebus ita perfecit, ut corpus; Quae sequuntur igitur? Ut optime, secundum naturam affectum esse possit. <em>Non potes, nisi retexueris illa.</em> In quo etsi est magnus, tamen nova pleraque et perpauca de moribus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Occultum facinus esse potuerit, gaudebit;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Illa tamen simplicia, vestra versuta.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Sed quid attinet de rebus tam apertis plura requirere? Sin tantum modo ad indicia veteris memoriae cognoscenda, curiosorum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum patria: miserum exilium.</a> Atqui haec patefactio quasi rerum opertarum, cum quid quidque sit aperitur, definitio est. Quid, cum volumus nomina eorum, qui quid gesserint, nota nobis esse, parentes, patriam, multa praeterea minime necessaria? Quem si tenueris, non modo meum Ciceronem, sed etiam me ipsum abducas licebit. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Hic si Peripateticus fuisset, permansisset, credo, in sententia, qui dolorem malum dicunt esse, de asperitate autem eius fortiter ferenda praecipiunt eadem, quae Stoici. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Equidem etiam curiam nostram-Hostiliam dico, non hanc novam, quae minor mihi esse videtur, posteaquam est maior-solebam intuens Scipionem, Catonem, Laelium, nostrum vero in primis avum cogitare; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Themistocles quidem, cum ei Simonides an quis alius artem memoriae polliceretur, Oblivionis, inquit, mallem. Nobis Heracleotes ille Dionysius flagitiose descivisse videtur a Stoicis propter oculorum dolorem. Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Vide, ne etiam menses! nisi forte eum dicis, qui, simul atque arripuit, interficit. <em>Tuo vero id quidem, inquam, arbitratu.</em> Deprehensus omnem poenam contemnet. Age nunc isti doceant, vel tu potius quis enim ista melius? Cur igitur easdem res, inquam, Peripateticis dicentibus verbum nullum est, quod non intellegatur? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Bestiarum vero nullum iudicium puto.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere. Qui ita affectus, beatum esse numquam probabis; Haec dicuntur inconstantissime. <strong>Primum Theophrasti, Strato, physicum se voluit;</strong> Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii. Omnia peccata paria dicitis. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ut ad minora veniam, mathematici, poëtae, musici, medici denique ex hac tamquam omnium artificum officina profecti sunt. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Huic mori optimum esse propter desperationem sapientiae, illi propter spem vivere.</li><li>Ne in odium veniam, si amicum destitero tueri.</li><li>Progredientibus autem aetatibus sensim tardeve potius quasi nosmet ipsos cognoscimus.</li><li>Sed emolumenta communia esse dicuntur, recte autem facta et peccata non habentur communia.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":3} --> +<h3>Quo tandem modo?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quia dolori non voluptas contraria est, sed doloris privatio. Consequens enim est et post oritur, ut dixi. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed plane dicit quod intellegit.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Et quod est munus, quod opus sapientiae?</a> Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. Primum in nostrane potestate est, quid meminerimus? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quid enim tanto opus est instrumento in optimis artibus comparandis?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Aliter enim explicari, quod quaeritur, non potest. At iam decimum annum in spelunca iacet. At eum nihili facit; <em>Quamquam id quidem, infinitum est in hac urbe;</em> Ne tum quidem te respicies et cogitabis sibi quemque natum esse et suis voluptatibus? Quid ergo aliud intellegetur nisi uti ne quae pars naturae neglegatur? Quid, quod res alia tota est? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Itaque his sapiens semper vacabit.</h5> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Isto modo ne improbos quidem, si essent boni viri. Nescio quo modo praetervolavit oratio. Nihil enim iam habes, quod ad corpus referas; Serpere anguiculos, nare anaticulas, evolare merulas, cornibus uti videmus boves, nepas aculeis. <strong>Quod cum dixissent, ille contra.</strong> Fortasse id optimum, sed ubi illud: Plus semper voluptatis? Si quidem, inquit, tollerem, sed relinquo. -, sed ut hoc iudicaremus, non esse in iis partem maximam positam beate aut secus vivendi. Aufert enim sensus actionemque tollit omnem. Itaque his sapiens semper vacabit. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sit, inquam, tam facilis, quam vultis, comparatio voluptatis, quid de dolore dicemus?</li><li>Progredientibus autem aetatibus sensim tardeve potius quasi nosmet ipsos cognoscimus.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><strong>Numquam facies.</strong> Sed haec quidem liberius ab eo dicuntur et saepius. Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia? Quod, inquit, quamquam voluptatibus quibusdam est saepe iucundius, tamen expetitur propter voluptatem. Verum hoc idem saepe faciamus. Cur igitur, inquam, res tam dissimiles eodem nomine appellas? Nos cum te, M. Ut in geometria, prima si dederis, danda sunt omnia. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tuo vero id quidem, inquam, arbitratu. Apud ceteros autem philosophos, qui quaesivit aliquid, tacet; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">A mene tu?</a> Istam voluptatem perpetuam quis potest praestare sapienti? Qui enim existimabit posse se miserum esse beatus non erit. Potius ergo illa dicantur: turpe esse, viri non esse debilitari dolore, frangi, succumbere. <em>Quam nemo umquam voluptatem appellavit, appellat;</em> Atque haec coniunctio confusioque virtutum tamen a philosophis ratione quadam distinguitur. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quam illa ardentis amores excitaret sui! Cur tandem?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>In motu et in statu corporis nihil inest, quod animadvertendum esse ipsa natura iudicet? Quod non faceret, si in voluptate summum bonum poneret. Quod, inquit, quamquam voluptatibus quibusdam est saepe iucundius, tamen expetitur propter voluptatem. At enim hic etiam dolore. Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ea possunt paria non esse.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Tu vero, inquam, ducas licet, si sequetur;</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Eadem fortitudinis ratio reperietur.</a> Atque ego: Scis me, inquam, istud idem sentire, Piso, sed a te opportune facta mentio est. In his igitur partibus duabus nihil erat, quod Zeno commutare gestiret. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse;</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tamen a proposito, inquam, aberramus.</a> Primum Theophrasti, Strato, physicum se voluit; Nihil enim iam habes, quod ad corpus referas; Quae contraria sunt his, malane? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Eam tum adesse, cum dolor omnis absit;</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quamquam haec quidem praeposita recte et reiecta dicere licebit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum incolumis acies: misera caecitas.</a> Urgent tamen et nihil remittunt. Non enim quaero quid verum, sed quid cuique dicendum sit. Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Age nunc isti doceant, vel tu potius quis enim ista melius? Satis est ad hoc responsum. <strong>Comprehensum, quod cognitum non habet?</strong> Ergo in gubernando nihil, in officio plurimum interest, quo in genere peccetur. Haec dicuntur inconstantissime. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Ecce aliud simile dissimile.</strong> Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum sit. <em>Itaque his sapiens semper vacabit.</em> In schola desinis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quo tandem modo?</a> Potius inflammat, ut coercendi magis quam dedocendi esse videantur. Et nemo nimium beatus est; Sed quid attinet de rebus tam apertis plura requirere? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Verum hoc idem saepe faciamus. Idem iste, inquam, de voluptate quid sentit? Propter nos enim illam, non propter eam nosmet ipsos diligimus. Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. Istam voluptatem perpetuam quis potest praestare sapienti? Quae quo sunt excelsiores, eo dant clariora indicia naturae. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Hic nihil fuit, quod quaereremus.</li><li>Quid ergo attinet dicere: Nihil haberem, quod reprehenderem, si finitas cupiditates haberent?</li><li>Sin kakan malitiam dixisses, ad aliud nos unum certum vitium consuetudo Latina traduceret.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Expectoque quid ad id, quod quaerebam, respondeas. Sin aliud quid voles, postea. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>At Zeno eum non beatum modo, sed etiam divitem dicere ausus est.</li><li>Nihil opus est exemplis hoc facere longius.</li><li>Minime vero istorum quidem, inquit.</li><li>Experiamur igitur, inquit, etsi habet haec Stoicorum ratio difficilius quiddam et obscurius.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Atqui eorum nihil est eius generis, ut sit in fine atque extrerno bonorum. Mihi enim satis est, ipsis non satis. At tu eadem ista dic in iudicio aut, si coronam times, dic in senatu. Quis enim est, qui non videat haec esse in natura rerum tria? In qua quid est boni praeter summam voluptatem, et eam sempiternam? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Sin aliud quid voles, postea.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Inquit, dasne adolescenti veniam?</strong> Iam in altera philosophiae parte. Aliud igitur esse censet gaudere, aliud non dolere. <strong>Maximus dolor, inquit, brevis est.</strong> Summum a vobis bonum voluptas dicitur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Tum Torquatus: Prorsus, inquit, assentior;</strong> <strong>Ut pulsi recurrant?</strong> Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ut nemo dubitet, eorum omnia officia quo spectare, quid sequi, quid fugere debeant? Philosophi autem in suis lectulis plerumque moriuntur. Videmus igitur ut conquiescere ne infantes quidem possint. Naturales divitias dixit parabiles esse, quod parvo esset natura contenta. Videsne quam sit magna dissensio? Sed plane dicit quod intellegit. Ait enim se, si uratur, Quam hoc suave! dicturum. Quia, si mala sunt, is, qui erit in iis, beatus non erit. <em>Traditur, inquit, ab Epicuro ratio neglegendi doloris.</em> Sunt autem, qui dicant foedus esse quoddam sapientium, ut ne minus amicos quam se ipsos diligant. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Nobis Heracleotes ille Dionysius flagitiose descivisse videtur a Stoicis propter oculorum dolorem.</li><li>In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt.</li><li>Ita multo sanguine profuso in laetitia et in victoria est mortuus.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":4} --> +<h4>Sic consequentibus vestris sublatis prima tolluntur.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hoc tu nunc in illo probas. Illa videamus, quae a te de amicitia dicta sunt. Sed quoniam et advesperascit et mihi ad villam revertendum est, nunc quidem hactenus; Hunc vos beatum; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nos commodius agimus.</a> Quasi ego id curem, quid ille aiat aut neget. Sic exclusis sententiis reliquorum cum praeterea nulla esse possit, haec antiquorum valeat necesse est. </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Quo modo autem optimum, si bonum praeterea nullum est? <strong>Idemne, quod iucunde?</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Pauca mutat vel plura sane;</a> Graece donan, Latine voluptatem vocant. Venit enim mihi Platonis in mentem, quem accepimus primum hic disputare solitum; Eorum enim omnium multa praetermittentium, dum eligant aliquid, quod sequantur, quasi curta sententia; Hi curatione adhibita levantur in dies, valet alter plus cotidie, alter videt. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>O magnam vim ingenii causamque iustam, cur nova existeret disciplina! Perge porro. <strong>Non autem hoc: igitur ne illud quidem.</strong> Neque solum ea communia, verum etiam paria esse dixerunt. Ut in geometria, prima si dederis, danda sunt omnia. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Hoc non est positum in nostra actione.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ac tamen hic mallet non dolere. Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo. Hoc enim constituto in philosophia constituta sunt omnia. <strong>Cur id non ita fit?</strong> Quid enim mihi potest esse optatius quam cum Catone, omnium virtutum auctore, de virtutibus disputare? Tertium autem omnibus aut maximis rebus iis, quae secundum naturam sint, fruentem vivere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Animum autem reliquis rebus ita perfecit, ut corpus; Equidem e Cn. Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Mihi, inquam, qui te id ipsum rogavi? Quae qui non vident, nihil umquam magnum ac cognitione dignum amaverunt. Qua ex cognitione facilior facta est investigatio rerum occultissimarum. Ac tamen hic mallet non dolere. Aliter enim nosmet ipsos nosse non possumus. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Cupit enim dícere nihil posse ad beatam vitam deesse sapienti.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Sed residamus, inquit, si placet.</em> Nemo igitur esse beatus potest. Nam Pyrrho, Aristo, Erillus iam diu abiecti. Quippe: habes enim a rhetoribus; Respondeat totidem verbis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae est igitur causa istarum angustiarum?</a> Tum ille: Ain tandem? Venit ad extremum; Et non ex maxima parte de tota iudicabis? <strong>Expectoque quid ad id, quod quaerebam, respondeas.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Itaque illa non dico me expetere, sed legere, nec optare, sed sumere, contraria autem non fugere, sed quasi secernere. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Sed haec ab Antiocho, familiari nostro, dicuntur multo melius et fortius, quam a Stasea dicebantur.</li><li>Tamen aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si ista mala sunt, placet.</li><li>Invidiosum nomen est, infame, suspectum.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading --> +<h2>Quodsi ipsam honestatem undique pertectam atque absolutam.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Huius ego nunc auctoritatem sequens idem faciam. Itaque eos id agere, ut a se dolores, morbos, debilitates repellant. Praeclare enim Plato: Beatum, cui etiam in senectute contigerit, ut sapientiam verasque opiniones assequi possit. Solum praeterea formosum, solum liberum, solum civem, stultost; Quis est enim, in quo sit cupiditas, quin recte cupidus dici possit? Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit? Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Perturbationes autem nulla naturae vi commoventur, omniaque ea sunt opiniones ac iudicia levitatis.</li><li>Ne in odium veniam, si amicum destitero tueri.</li><li>Ita redarguitur ipse a sese, convincunturque scripta eius probitate ipsius ac moribus.</li><li>Non minor, inquit, voluptas percipitur ex vilissimis rebus quam ex pretiosissimis.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia?</li><li>At habetur! Et ego id scilicet nesciebam! Sed ut sit, etiamne post mortem coletur?</li><li>Introduci enim virtus nullo modo potest, nisi omnia, quae leget quaeque reiciet, unam referentur ad summam.</li><li>Nulla profecto est, quin suam vim retineat a primo ad extremum.</li><li>Haec quo modo conveniant, non sane intellego.</li><li>Multa sunt dicta ab antiquis de contemnendis ac despiciendis rebus humanis;</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":3} --> +<h3>Sed tamen intellego quid velit.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Eiuro, inquit adridens, iniquum, hac quidem de re; Tollenda est atque extrahenda radicitus. Stoici autem, quod finem bonorum in una virtute ponunt, similes sunt illorum; Diodorus, eius auditor, adiungit ad honestatem vacuitatem doloris. Est autem a te semper dictum nec gaudere quemquam nisi propter corpus nec dolere. Respondent extrema primis, media utrisque, omnia omnibus. Haec bene dicuntur, nec ego repugno, sed inter sese ipsa pugnant. Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius. Quamquam te quidem video minime esse deterritum. Ad eas enim res ab Epicuro praecepta dantur. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Traditur, inquit, ab Epicuro ratio neglegendi doloris.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Et ille ridens: Video, inquit, quid agas; Est autem etiam actio quaedam corporis, quae motus et status naturae congruentis tenet; Sumenda potius quam expetenda. Quam ob rem tandem, inquit, non satisfacit? Quid est enim aliud esse versutum? Sin laboramus, quis est, qui alienae modum statuat industriae? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. Quo modo autem optimum, si bonum praeterea nullum est? Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat.</li><li>Qui autem de summo bono dissentit de tota philosophiae ratione dissentit.</li><li>Iam contemni non poteris.</li><li>Quae diligentissime contra Aristonem dicuntur a Chryippo.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":6} --> +<h6>Venit ad extremum;</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Multoque hoc melius nos veriusque quam Stoici. Ergo in gubernando nihil, in officio plurimum interest, quo in genere peccetur. Praeterea sublata cognitione et scientia tollitur omnis ratio et vitae degendae et rerum gerendarum. Ita relinquet duas, de quibus etiam atque etiam consideret. <strong>Tecum optime, deinde etiam cum mediocri amico.</strong> Egone quaeris, inquit, quid sentiam? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Haec dicuntur fortasse ieiunius;</a> Quae cum dixisset paulumque institisset, Quid est? Quis est, qui non oderit libidinosam, protervam adolescentiam? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Et quidem saepe quaerimus verbum Latinum par Graeco et quod idem valeat; <em>An vero, inquit, quisquam potest probare, quod perceptfum, quod.</em> Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque his sapiens semper vacabit.</a> Quod non faceret, si in voluptate summum bonum poneret. Equidem etiam Epicurum, in physicis quidem, Democriteum puto. Prodest, inquit, mihi eo esse animo. Cur iustitia laudatur? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tum ille: Tu autem cum ipse tantum librorum habeas, quos hic tandem requiris? Tu vero, inquam, ducas licet, si sequetur; Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. Nemo nostrum istius generis asotos iucunde putat vivere. Ergo, inquit, tibi Q. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ut optime, secundum naturam affectum esse possit.</a> Hoc est dicere: Non reprehenderem asotos, si non essent asoti. Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Certe, nisi voluptatem tanti aestimaretis.</a> Inscite autem medicinae et gubernationis ultimum cum ultimo sapientiae comparatur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ergo, si semel tristior effectus est, hilara vita amissa est? Itaque mihi non satis videmini considerare quod iter sit naturae quaeque progressio. Qualem igitur hominem natura inchoavit? Ratio quidem vestra sic cogit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed ad illum redeo.</a> Quicquid porro animo cernimus, id omne oritur a sensibus; Nunc omni virtuti vitium contrario nomine opponitur. Videmus igitur ut conquiescere ne infantes quidem possint. Quod idem cum vestri faciant, non satis magnam tribuunt inventoribus gratiam. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Idem iste, inquam, de voluptate quid sentit? <strong>Quis hoc dicit?</strong> Pisone in eo gymnasio, quod Ptolomaeum vocatur, unaque nobiscum Q. An potest cupiditas finiri? Dic in quovis conventu te omnia facere, ne doleas. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Est, ut dicis, inquit;</a> Traditur, inquit, ab Epicuro ratio neglegendi doloris. <em>Et nemo nimium beatus est;</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Satis est ad hoc responsum. Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. Haec igitur Epicuri non probo, inquam. Pollicetur certe. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Et quidem Arcesilas tuus, etsi fuit in disserendo pertinacior, tamen noster fuit;</li><li>Quo studio cum satiari non possint, omnium ceterarum rerum obliti níhil abiectum, nihil humile cogitant;</li><li>Haec quo modo conveniant, non sane intellego.</li><li>Is ita vivebat, ut nulla tam exquisita posset inveniri voluptas, qua non abundaret.</li><li>Stulti autem malorum memoria torquentur, sapientes bona praeterita grata recordatione renovata delectant.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Neminem videbis ita laudatum, ut artifex callidus comparandarum voluptatum diceretur.</li><li>Mihi enim erit isdem istis fortasse iam utendum.</li><li>Quamquam ab iis philosophiam et omnes ingenuas disciplinas habemus;</li><li>At enim sequor utilitatem.</li><li>Praetereo multos, in bis doctum hominem et suavem, Hieronymum, quem iam cur Peripateticum appellem nescio.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":6} --> +<h6>Ipse Epicurus fortasse redderet, ut Sextus Peducaeus, Sex.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Itaque eos id agere, ut a se dolores, morbos, debilitates repellant. Non enim, si omnia non sequebatur, idcirco non erat ortus illinc. Nam quid possumus facere melius? <strong>Suo genere perveniant ad extremum;</strong> Hoc est non modo cor non habere, sed ne palatum quidem. Superiores tres erant, quae esse possent, quarum est una sola defensa, eaque vehementer. Illis videtur, qui illud non dubitant bonum dicere -; An me, inquis, tam amentem putas, ut apud imperitos isto modo loquar? Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. Praeterea sublata cognitione et scientia tollitur omnis ratio et vitae degendae et rerum gerendarum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quippe: habes enim a rhetoribus;</a> Ita fit cum gravior, tum etiam splendidior oratio. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Haec igitur Epicuri non probo, inquam.</a> Quorum altera prosunt, nocent altera. An vero, inquit, quisquam potest probare, quod perceptfum, quod. Ita enim vivunt quidam, ut eorum vita refellatur oratio. Ita multo sanguine profuso in laetitia et in victoria est mortuus. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Satis est ad hoc responsum.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Peccata paria. Scrupulum, inquam, abeunti; Semovenda est igitur voluptas, non solum ut recta sequamini, sed etiam ut loqui deceat frugaliter. <em>Idemne, quod iucunde?</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum liberi: misera orbitas.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tecum optime, deinde etiam cum mediocri amico.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>At enim hic etiam dolore. Graece donan, Latine voluptatem vocant. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Iam in altera philosophiae parte.</a> Istam voluptatem, inquit, Epicurus ignorat? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nonne igitur tibi videntur, inquit, mala?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Haec dicuntur inconstantissime.</a> Dic in quovis conventu te omnia facere, ne doleas. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>An tu me de L.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Itaque hic ipse iam pridem est reiectus; Nummus in Croesi divitiis obscuratur, pars est tamen divitiarum. Sed non alienum est, quo facilius vis verbi intellegatur, rationem huius verbi faciendi Zenonis exponere. Nihil enim iam habes, quod ad corpus referas; Quamvis enim depravatae non sint, pravae tamen esse possunt. Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? <strong>Nunc agendum est subtilius.</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Optime, inquam.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>His similes sunt omnes, qui virtuti student levantur vitiis, levantur erroribus, nisi forte censes Ti. Est autem etiam actio quaedam corporis, quae motus et status naturae congruentis tenet; Incommoda autem et commoda-ita enim estmata et dustmata appello-communia esse voluerunt, paria noluerunt. Ita relinquet duas, de quibus etiam atque etiam consideret. Si sapiens, ne tum quidem miser, cum ab Oroete, praetore Darei, in crucem actus est. Quae autem natura suae primae institutionis oblita est? Quid, si etiam iucunda memoria est praeteritorum malorum? An ea, quae per vinitorem antea consequebatur, per se ipsa curabit? <em>Sit sane ista voluptas.</em> Aliter homines, aliter philosophos loqui putas oportere? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Itaque contra est, ac dicitis;</strong> Dicam, inquam, et quidem discendi causa magis, quam quo te aut Epicurum reprehensum velim. Earum etiam rerum, quas terra gignit, educatio quaedam et perfectio est non dissimilis animantium. Sin tantum modo ad indicia veteris memoriae cognoscenda, curiosorum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Dici enim nihil potest verius.</a> Hoc positum in Phaedro a Platone probavit Epicurus sensitque in omni disputatione id fieri oportere. Praeterea sublata cognitione et scientia tollitur omnis ratio et vitae degendae et rerum gerendarum. Tu quidem reddes; Aliter enim nosmet ipsos nosse non possumus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nescio quo modo praetervolavit oratio. Servari enim iustitia nisi a forti viro, nisi a sapiente non potest. Quod cum ita sit, perspicuum est omnis rectas res atque laudabilis eo referri, ut cum voluptate vivatur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Zenonis est, inquam, hoc Stoici.</a> <strong>Nunc haec primum fortasse audientis servire debemus.</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At iste non dolendi status non vocatur voluptas.</a> Ne discipulum abducam, times. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. <strong>Istam voluptatem perpetuam quis potest praestare sapienti?</strong> Ut optime, secundum naturam affectum esse possit. <strong>Audeo dicere, inquit.</strong> Ut optime, secundum naturam affectum esse possit. <strong>Id mihi magnum videtur.</strong> A villa enim, credo, et: Si ibi te esse scissem, ad te ipse venissem. Sed tu istuc dixti bene Latine, parum plane. Cum autem in quo sapienter dicimus, id a primo rectissime dicitur. <strong>Equidem e Cn.</strong> Sed tamen est aliquid, quod nobis non liceat, liceat illis. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Sed potestne rerum maior esse dissensio?</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Inde sermone vario sex illa a Dipylo stadia confecimus. Hinc ceteri particulas arripere conati suam quisque videro voluit afferre sententiam. Cupit enim dícere nihil posse ad beatam vitam deesse sapienti. Respondent extrema primis, media utrisque, omnia omnibus. <strong>Eam tum adesse, cum dolor omnis absit;</strong> Ait enim se, si uratur, Quam hoc suave! dicturum. Te enim iudicem aequum puto, modo quae dicat ille bene noris. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid censes in Latino fore?</a> Nam, ut sint illa vendibiliora, haec uberiora certe sunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quonam, inquit, modo?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quae cum dixisset paulumque institisset, Quid est? <strong>Sed fortuna fortis;</strong> Tubulo putas dicere? Non enim, si omnia non sequebatur, idcirco non erat ortus illinc. Sequitur disserendi ratio cognitioque naturae; Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + At vero Epicurus una in domo, et ea quidem angusta, quam magnos quantaque amoris conspiratione consentientis tenuit amicorum greges! quod fit etiam nunc ab Epicureis. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Inde igitur, inquit, ordiendum est. At hoc in eo M. Multoque hoc melius nos veriusque quam Stoici. Ex quo, id quod omnes expetunt, beate vivendi ratio inveniri et comparari potest. Teneo, inquit, finem illi videri nihil dolere. Minime vero istorum quidem, inquit. Ex eorum enim scriptis et institutis cum omnis doctrina liberalis, omnis historia. Aliud igitur esse censet gaudere, aliud non dolere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quamvis enim depravatae non sint, pravae tamen esse possunt. Curium putes loqui, interdum ita laudat, ut quid praeterea sit bonum neget se posse ne suspicari quidem. Miserum hominem! Si dolor summum malum est, dici aliter non potest. Sed erat aequius Triarium aliquid de dissensione nostra iudicare. Hanc ergo intuens debet institutum illud quasi signum absolvere. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non autem hoc: igitur ne illud quidem.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quibus ego vehementer assentior.</a> An dolor longissimus quisque miserrimus, voluptatem non optabiliorem diuturnitas facit? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quae similitudo in genere etiam humano apparet.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. Suam denique cuique naturam esse ad vivendum ducem. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Duo enim genera quae erant, fecit tria.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nam Metrodorum non puto ipsum professum, sed, cum appellaretur ab Epicuro, repudiare tantum beneficium noluisse; Eadem nunc mea adversum te oratio est. Ut alios omittam, hunc appello, quem ille unum secutus est. Itaque nostrum est-quod nostrum dico, artis est-ad ea principia, quae accepimus. Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Ipse Epicurus fortasse redderet, ut Sextus Peducaeus, Sex. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid est igitur, inquit, quod requiras? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ille enim occurrentia nescio quae comminiscebatur;</a> Experiamur igitur, inquit, etsi habet haec Stoicorum ratio difficilius quiddam et obscurius. Cum salvum esse flentes sui respondissent, rogavit essentne fusi hostes. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Memini vero, inquam;</a> Respondeat totidem verbis. Quae similitudo in genere etiam humano apparet. <strong>Ita fit cum gravior, tum etiam splendidior oratio.</strong> Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quae cum dixisset paulumque institisset, Quid est?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. Quo modo autem optimum, si bonum praeterea nullum est? Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. Apparet statim, quae sint officia, quae actiones. <em>Istam voluptatem, inquit, Epicurus ignorat?</em> Hoc ille tuus non vult omnibusque ex rebus voluptatem quasi mercedem exigit. <em>Eam stabilem appellas.</em> Sed eum qui audiebant, quoad poterant, defendebant sententiam suam. Haec dicuntur inconstantissime. Suo genere perveniant ad extremum; <em>Sed ille, ut dixi, vitiose.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid de Platone aut de Democrito loquar? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed haec in pueris;</a> Utrum igitur tibi litteram videor an totas paginas commovere? Summum ením bonum exposuit vacuitatem doloris; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Memini vero, inquam;</a> Quis non odit sordidos, vanos, leves, futtiles? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Sed eum qui audiebant, quoad poterant, defendebant sententiam suam.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Recte dicis;</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed haec in pueris;</a> Quid ergo hoc loco intellegit honestum? Et quidem, inquit, vehementer errat; Prodest, inquit, mihi eo esse animo. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Quis istud, quaeso, nesciebat?</em> Bonum integritas corporis: misera debilitas. Ut nemo dubitet, eorum omnia officia quo spectare, quid sequi, quid fugere debeant? Itaque sensibus rationem adiunxit et ratione effecta sensus non reliquit. Huius, Lyco, oratione locuples, rebus ipsis ielunior. Quod autem ratione actum est, id officium appellamus. Ergo hoc quidem apparet, nos ad agendum esse natos. </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:heading {"level":3} --> +<h3>Suo genere perveniant ad extremum;</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quod vestri non item. Rationis enim perfectio est virtus; Quaeque de virtutibus dicta sunt, quem ad modum eae semper voluptatibus inhaererent, eadem de amicitia dicenda sunt. Odium autem et invidiam facile vitabis. Minime vero, inquit ille, consentit. Neque solum ea communia, verum etiam paria esse dixerunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed haec quidem liberius ab eo dicuntur et saepius. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Paria sunt igitur.</a> <strong>Num quid tale Democritus?</strong> Nam Pyrrho, Aristo, Erillus iam diu abiecti. Quid ei reliquisti, nisi te, quoquo modo loqueretur, intellegere, quid diceret? Mihi enim satis est, ipsis non satis. Itaque contra est, ac dicitis; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Duarum enim vitarum nobis erunt instituta capienda.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quibus expositis facilis est coniectura ea maxime esse expetenda ex nostris, quae plurimum habent dignitatis, ut optimae cuiusque partis, quae per se expetatur, virtus sit expetenda maxime. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Atque adhuc ea dixi, causa cur Zenoni non fuisset, quam ob rem a superiorum auctoritate discederet. Quare conare, quaeso. Familiares nostros, credo, Sironem dicis et Philodemum, cum optimos viros, tum homines doctissimos. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Paulum, cum regem Persem captum adduceret, eodem flumine invectio?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Sin tantum modo ad indicia veteris memoriae cognoscenda, curiosorum.</strong> Vitae autem degendae ratio maxime quidem illis placuit quieta. Eiuro, inquit adridens, iniquum, hac quidem de re; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed fortuna fortis;</a> <em>Quae duo sunt, unum facit.</em> Uterque enim summo bono fruitur, id est voluptate. Quae qui non vident, nihil umquam magnum ac cognitione dignum amaverunt. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ita credo.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quo plebiscito decreta a senatu est consuli quaestio Cn. Quid est igitur, cur ita semper deum appellet Epicurus beatum et aeternum? Quia nec honesto quic quam honestius nec turpi turpius. Gracchum patrem non beatiorem fuisse quam fillum, cum alter stabilire rem publicam studuerit, alter evertere. Bestiarum vero nullum iudicium puto. Peccata paria. Et quidem, inquit, vehementer errat; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Deprehensus omnem poenam contemnet.</a> Haec igitur Epicuri non probo, inquam. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ergo omni animali illud, quod appetiti positum est in eo, quod naturae est accommodatum. Ita enim vivunt quidam, ut eorum vita refellatur oratio. Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Quae quidem vel cum periculo est quaerenda vobis; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Aliter enim nosmet ipsos nosse non possumus.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur, nisi quod turpis oratio est?</a> Vide, quantum, inquam, fallare, Torquate. Beatus sibi videtur esse moriens. Sic enim censent, oportunitatis esse beate vivere. Aut unde est hoc contritum vetustate proverbium: quicum in tenebris? Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed quot homines, tot sententiae; Quod si ita sit, cur opera philosophiae sit danda nescio. <strong>Cur id non ita fit?</strong> Quorum altera prosunt, nocent altera. Quippe: habes enim a rhetoribus; Amicitiae vero locus ubi esse potest aut quis amicus esse cuiquam, quem non ipsum amet propter ipsum? Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint. Restinguet citius, si ardentem acceperit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod autem principium officii quaerunt, melius quam Pyrrho;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Qui enim existimabit posse se miserum esse beatus non erit. Tollenda est atque extrahenda radicitus. Ergo illi intellegunt quid Epicurus dicat, ego non intellego? Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. Nos paucis ad haec additis finem faciamus aliquando; <strong>Nosti, credo, illud: Nemo pius est, qui pietatem-;</strong> Si quicquam extra virtutem habeatur in bonis. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Pudebit te, inquam, illius tabulae, quam Cleanthes sane commode verbis depingere solebat.</li><li>Huic mori optimum esse propter desperationem sapientiae, illi propter spem vivere.</li><li>Quae hic rei publicae vulnera inponebat, eadem ille sanabat.</li><li>Hoc est non modo cor non habere, sed ne palatum quidem.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Haeret in salebra. Respondent extrema primis, media utrisque, omnia omnibus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quis non odit sordidos, vanos, leves, futtiles? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Post enim Chrysippum eum non sane est disputatum.</a> Omnia contraria, quos etiam insanos esse vultis. Theophrastus mediocriterne delectat, cum tractat locos ab Aristotele ante tractatos? Sed ad haec, nisi molestum est, habeo quae velim. <em>Simus igitur contenti his.</em> Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? Verum hoc idem saepe faciamus. In eo enim positum est id, quod dicimus esse expetendum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At enim hic etiam dolore.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nec vero sum nescius esse utilitatem in historia, non modo voluptatem. Huic mori optimum esse propter desperationem sapientiae, illi propter spem vivere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Oratio me istius philosophi non offendit;</a> Is ita vivebat, ut nulla tam exquisita posset inveniri voluptas, qua non abundaret. Ita fit cum gravior, tum etiam splendidior oratio. Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? Nihilne est in his rebus, quod dignum libero aut indignum esse ducamus? Terram, mihi crede, ea lanx et maria deprimet. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Duo enim genera quae erant, fecit tria. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Suam denique cuique naturam esse ad vivendum ducem. Dicet pro me ipsa virtus nec dubitabit isti vestro beato M. Intellegi quidem, ut propter aliam quampiam rem, verbi gratia propter voluptatem, nos amemus; Quae tamen a te agetur non melior, quam illae sunt, quas interdum optines. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At ille pellit, qui permulcet sensum voluptate.</a> Illi enim inter se dissentiunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quamvis enim depravatae non sint, pravae tamen esse possunt. Aut, Pylades cum sis, dices te esse Orestem, ut moriare pro amico? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nobis aliter videtur, recte secusne, postea;</a> Quid iudicant sensus? Teneo, inquit, finem illi videri nihil dolere. Fortemne possumus dicere eundem illum Torquatum? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Mihi enim satis est, ipsis non satis.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Gloriosa ostentatio in constituendo summo bono.</a> Nulla erit controversia. Nemo nostrum istius generis asotos iucunde putat vivere. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Ut pulsi recurrant? Negare non possum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quae est igitur causa istarum angustiarum?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ut non sine causa ex iis memoriae ducta sit disciplina. <em>Faceres tu quidem, Torquate, haec omnia;</em> Eadem nunc mea adversum te oratio est. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sedulo, inquam, faciam.</a> Recte, inquit, intellegis. Legimus tamen Diogenem, Antipatrum, Mnesarchum, Panaetium, multos alios in primisque familiarem nostrum Posidonium. Ego vero volo in virtute vim esse quam maximam; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Heri, inquam, ludis commissis ex urbe profectus veni ad vesperum.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cupit enim dícere nihil posse ad beatam vitam deesse sapienti. Nondum autem explanatum satis, erat, quid maxime natura vellet. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Incommoda autem et commoda-ita enim estmata et dustmata appello-communia esse voluerunt, paria noluerunt. Quid me istud rogas? Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; Ergo opifex plus sibi proponet ad formarum quam civis excellens ad factorum pulchritudinem? Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Potius ergo illa dicantur: turpe esse, viri non esse debilitari dolore, frangi, succumbere. Sit, inquam, tam facilis, quam vultis, comparatio voluptatis, quid de dolore dicemus? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Pauca mutat vel plura sane;</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>An eum discere ea mavis, quae cum plane perdidiceriti nihil sciat? Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur; Quis non odit sordidos, vanos, leves, futtiles? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Negat esse eam, inquit, propter se expetendam.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quis Aristidem non mortuum diligit?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Certe non potest.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Ea possunt paria non esse.</em> Quem ad modum quis ambulet, sedeat, qui ductus oris, qui vultus in quoque sit? Si longus, levis; Eodem modo is enim tibi nemo dabit, quod, expetendum sit, id esse laudabile. Itaque his sapiens semper vacabit. Alterum significari idem, ut si diceretur, officia media omnia aut pleraque servantem vivere. Sed ille, ut dixi, vitiose. Quis est enim, in quo sit cupiditas, quin recte cupidus dici possit? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Collige omnia, quae soletis: Praesidium amicorum.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. Quae contraria sunt his, malane? Oculorum, inquit Plato, est in nobis sensus acerrimus, quibus sapientiam non cernimus. Neminem videbis ita laudatum, ut artifex callidus comparandarum voluptatum diceretur. Iis igitur est difficilius satis facere, qui se Latina scripta dicunt contemnere. Nec vero sum nescius esse utilitatem in historia, non modo voluptatem. Itaque vides, quo modo loquantur, nova verba fingunt, deserunt usitata. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Non laboro, inquit, de nomine.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Ut optime, secundum naturam affectum esse possit.</em> Si enim ad populum me vocas, eum. Quid de Platone aut de Democrito loquar? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Quae quo sunt excelsiores, eo dant clariora indicia naturae.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Itaque haec cum illis est dissensio, cum Peripateticis nulla sane. Piso igitur hoc modo, vir optimus tuique, ut scis, amantissimus. <em>Laboro autem non sine causa;</em> Aliter homines, aliter philosophos loqui putas oportere? Quid enim mihi potest esse optatius quam cum Catone, omnium virtutum auctore, de virtutibus disputare? <em>Est, ut dicis, inquam.</em> Collatio igitur ista te nihil iuvat. Sic consequentibus vestris sublatis prima tolluntur. Dolor ergo, id est summum malum, metuetur semper, etiamsi non aderit; <strong>Sed ille, ut dixi, vitiose.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Haec qui audierit, ut ridere non curet, discedet tamen nihilo firmior ad dolorem ferendum, quam venerat.</li><li>Sic, et quidem diligentius saepiusque ista loquemur inter nos agemusque communiter.</li><li>Nec vero sum nescius esse utilitatem in historia, non modo voluptatem.</li><li>Laelius clamores sofòw ille so lebat Edere compellans gumias ex ordine nostros.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Nam illud vehementer repugnat, eundem beatum esse et multis malis oppressum. Te enim iudicem aequum puto, modo quae dicat ille bene noris. Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur; </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Si enim ita est, vide ne facinus facias, cum mori suadeas.</li><li>Quis Aristidem non mortuum diligit?</li><li>Quos quidem tibi studiose et diligenter tractandos magnopere censeo.</li><li>Sin te auctoritas commovebat, nobisne omnibus et Platoni ipsi nescio quem illum anteponebas?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Ex ea difficultate illae fallaciloquae, ut ait Accius, malitiae natae sunt. Nec tamen ille erat sapiens quis enim hoc aut quando aut ubi aut unde? <em>Quorum altera prosunt, nocent altera.</em> An est aliquid per se ipsum flagitiosum, etiamsi nulla comitetur infamia? Itaque ad tempus ad Pisonem omnes. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Disserendi artem nullam habuit.</a> Et harum quidem rerum facilis est et expedita distinctio. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Erat enim Polemonis.</a> Huius ego nunc auctoritatem sequens idem faciam. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quem ad modum quis ambulet, sedeat, qui ductus oris, qui vultus in quoque sit?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Beatus autem esse in maximarum rerum timore nemo potest. Sed quid minus probandum quam esse aliquem beatum nec satis beatum? Quem si tenueris, non modo meum Ciceronem, sed etiam me ipsum abducas licebit. Themistocles quidem, cum ei Simonides an quis alius artem memoriae polliceretur, Oblivionis, inquit, mallem. Quorum sine causa fieri nihil putandum est. Illa argumenta propria videamus, cur omnia sint paria peccata. Nunc de hominis summo bono quaeritur; Quare attendo te studiose et, quaecumque rebus iis, de quibus hic sermo est, nomina inponis, memoriae mando; <em>Hoc non est positum in nostra actione.</em> Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Duarum enim vitarum nobis erunt instituta capienda.</em> <strong>At enim sequor utilitatem.</strong> Et ille ridens: Video, inquit, quid agas; Post enim Chrysippum eum non sane est disputatum. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Eadem nunc mea adversum te oratio est.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Et quidem, inquit, vehementer errat;</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Omnis enim est natura diligens sui. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid ad utilitatem tantae pecuniae?</a> Graecis hoc modicum est: Leonidas, Epaminondas, tres aliqui aut quattuor; Hic ego: Pomponius quidem, inquam, noster iocari videtur, et fortasse suo iure. Modo etiam paulum ad dexteram de via declinavi, ut ad Pericli sepulcrum accederem. Sed ad rem redeamus; In eo enim positum est id, quod dicimus esse expetendum. At quicum ioca seria, ut dicitur, quicum arcana, quicum occulta omnia? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? Immo vero, inquit, ad beatissime vivendum parum est, ad beate vero satis. Hic quoque suus est de summoque bono dissentiens dici vere Peripateticus non potest. Dolere malum est: in crucem qui agitur, beatus esse non potest. Ut proverbia non nulla veriora sint quam vestra dogmata. Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quod cum accidisset ut alter alterum necopinato videremus, surrexit statim. Pauca mutat vel plura sane; Nam et a te perfici istam disputationem volo, nec tua mihi oratio longa videri potest. Aut haec tibi, Torquate, sunt vituperanda aut patrocinium voluptatis repudiandum. Qui non moveatur et offensione turpitudinis et comprobatione honestatis? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nescio quo modo praetervolavit oratio.</a> Ita ceterorum sententiis semotis relinquitur non mihi cum Torquato, sed virtuti cum voluptate certatio. Qui enim voluptatem ipsam contemnunt, iis licet dicere se acupenserem maenae non anteponere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Aliena dixit in physicis nec ea ipsa, quae tibi probarentur; Itaque et manendi in vita et migrandi ratio omnis iis rebus, quas supra dixi, metienda. An quod ita callida est, ut optime possit architectari voluptates? Qui enim voluptatem ipsam contemnunt, iis licet dicere se acupenserem maenae non anteponere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Non enim, si omnia non sequebatur, idcirco non erat ortus illinc. An ea, quae per vinitorem antea consequebatur, per se ipsa curabit? Tamen a proposito, inquam, aberramus. Ex rebus enim timiditas, non ex vocabulis nascitur. Ut in geometria, prima si dederis, danda sunt omnia. Reguli reiciendam; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Illud urgueam, non intellegere eum quid sibi dicendum sit, cum dolorem summum malum esse dixerit. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Ergo adhuc, quantum equidem intellego, causa non videtur fuisse mutandi nominis.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hic Speusippus, hic Xenocrates, hic eius auditor Polemo, cuius illa ipsa sessio fuit, quam videmus. Poterat autem inpune; Nec enim ignoras his istud honestum non summum modo, sed etiam, ut tu vis, solum bonum videri. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Praeclare hoc quidem.</a> Iam enim adesse poterit. Scientiam pollicentur, quam non erat mirum sapientiae cupido patria esse cariorem. Aliter enim nosmet ipsos nosse non possumus. Restincta enim sitis stabilitatem voluptatis habet, inquit, illa autem voluptas ipsius restinctionis in motu est. At negat Epicurus-hoc enim vestrum lumen estquemquam, qui honeste non vivat, iucunde posse vivere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Servari enim iustitia nisi a forti viro, nisi a sapiente non potest. Sint modo partes vitae beatae. <em>Bestiarum vero nullum iudicium puto.</em> Et certamen honestum et disputatio splendida! omnis est enim de virtutis dignitate contentio. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Non autem hoc: igitur ne illud quidem.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ut optime, secundum naturam affectum esse possit. Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. Non potes, nisi retexueris illa. <strong>Sit enim idem caecus, debilis.</strong> Ne amores quidem sanctos a sapiente alienos esse arbitrantur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur deinde Metrodori liberos commendas?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur?</li><li>Quod si ita se habeat, non possit beatam praestare vitam sapientia.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":6} --> +<h6>Easdemne res?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Videamus animi partes, quarum est conspectus illustrior; Cur igitur easdem res, inquam, Peripateticis dicentibus verbum nullum est, quod non intellegatur? Quod dicit Epicurus etiam de voluptate, quae minime sint voluptates, eas obscurari saepe et obrui. Est, ut dicis, inquit; Sed utrum hortandus es nobis, Luci, inquit, an etiam tua sponte propensus es? Itaque hic ipse iam pridem est reiectus; Illud dico, ea, quae dicat, praeclare inter se cohaerere. <em>Sed videbimus.</em> Est igitur officium eius generis, quod nec in bonis ponatur nec in contrariis. <strong>Quae cum essent dicta, discessimus.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Comprehensum, quod cognitum non habet?</strong> Prave, nequiter, turpiter cenabat; Quae autem natura suae primae institutionis oblita est? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Respondeat totidem verbis.</a> Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. <strong>Prave, nequiter, turpiter cenabat;</strong> Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Itaque hic ipse iam pridem est reiectus; Dicimus aliquem hilare vivere; <strong>Occultum facinus esse potuerit, gaudebit;</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Tubulum fuisse, qua illum, cuius is condemnatus est rogatione, P. Profectus in exilium Tubulus statim nec respondere ausus; Ut in geometria, prima si dederis, danda sunt omnia. <strong>Omnia contraria, quos etiam insanos esse vultis.</strong> Quae similitudo in genere etiam humano apparet. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed nimis multa.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Hoc mihi cum tuo fratre convenit.</a> Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia? Quae est igitur causa istarum angustiarum? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Est, ut dicis, inquam. <strong>Quid turpius quam sapientis vitam ex insipientium sermone pendere?</strong> Sed ad haec, nisi molestum est, habeo quae velim. At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit; Cenasti in vita numquam bene, cum omnia in ista Consumis squilla atque acupensere cum decimano. Laboro autem non sine causa; Conferam avum tuum Drusum cum C. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur post Tarentum ad Archytam?</a> <strong>Quid de Pythagora?</strong> Quam ob rem tandem, inquit, non satisfacit? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tum mihi Piso: Quid ergo? Scio enim esse quosdam, qui quavis lingua philosophari possint; Sed finge non solum callidum eum, qui aliquid improbe faciat, verum etiam praepotentem, ut M. Igitur neque stultorum quisquam beatus neque sapientium non beatus. Mihi vero, inquit, placet agi subtilius et, ut ipse dixisti, pressius. Expectoque quid ad id, quod quaerebam, respondeas. Ita relinquet duas, de quibus etiam atque etiam consideret. Scaevola tribunus plebis ferret ad plebem vellentne de ea re quaeri. <strong>Ecce aliud simile dissimile.</strong> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Hoc loco tenere se Triarius non potuit.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Quae cum praeponunt, ut sit aliqua rerum selectio, naturam videntur sequi;</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ego quoque, inquit, didicerim libentius si quid attuleris, quam te reprehenderim. Nihil enim hoc differt. Eaedem enim utilitates poterunt eas labefactare atque pervertere. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nam quod ita positum est, quod dissolutum sit, id esse sine sensu, id eius modi est, ut non satis plane dicat quid sit dissolutum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":4} --> +<h4>Ratio quidem vestra sic cogit.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quia voluptatem hanc esse sentiunt omnes, quam sensus accipiens movetur et iucunditate quadam perfunditur. Cum id quoque, ut cupiebat, audivisset, evelli iussit eam, qua erat transfixus, hastam. At cum de plurimis eadem dicit, tum certe de maximis. At iste non dolendi status non vocatur voluptas. Utinam quidem dicerent alium alio beatiorem! Iam ruinas videres. Quod idem cum vestri faciant, non satis magnam tribuunt inventoribus gratiam. Qua igitur re ab deo vincitur, si aeternitate non vincitur? Sic consequentibus vestris sublatis prima tolluntur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ego vero volo in virtute vim esse quam maximam; <strong>Quodsi ipsam honestatem undique pertectam atque absolutam.</strong> Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii. Quem ad modum quis ambulet, sedeat, qui ductus oris, qui vultus in quoque sit? Licet hic rursus ea commemores, quae optimis verbis ab Epicuro de laude amicitiae dicta sunt. <em>Itaque ab his ordiamur.</em> <strong>Dici enim nihil potest verius.</strong> <strong>Idem iste, inquam, de voluptate quid sentit?</strong> Illud dico, ea, quae dicat, praeclare inter se cohaerere. <em>Quis est tam dissimile homini.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tertium autem omnibus aut maximis rebus iis, quae secundum naturam sint, fruentem vivere. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum patria: miserum exilium.</a> Quid est, quod ab ea absolvi et perfici debeat? Habent enim et bene longam et satis litigiosam disputationem. Nondum autem explanatum satis, erat, quid maxime natura vellet. Egone quaeris, inquit, quid sentiam? Facillimum id quidem est, inquam. <em>Nihil opus est exemplis hoc facere longius.</em> Piso, familiaris noster, et alia multa et hoc loco Stoicos irridebat: Quid enim? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quando enim Socrates, qui parens philosophiae iure dici potest, quicquam tale fecit?</li><li>Ne vitationem quidem doloris ipsam per se quisquam in rebus expetendis putavit, nisi etiam evitare posset.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Omnes enim iucundum motum, quo sensus hilaretur. <em>Illa tamen simplicia, vestra versuta.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Id mihi magnum videtur.</a> Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. At enim hic etiam dolore. <strong>Eaedem res maneant alio modo.</strong> Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Sapiens autem semper beatus est et est aliquando in dolore; Haeret in salebra. Ita graviter et severe voluptatem secrevit a bono. Quae cum dixisset paulumque institisset, Quid est? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. Ita enim vivunt quidam, ut eorum vita refellatur oratio. Utrum igitur tibi litteram videor an totas paginas commovere? Praeterea et appetendi et refugiendi et omnino rerum gerendarum initia proficiscuntur aut a voluptate aut a dolore. Nisi autem rerum natura perspecta erit, nullo modo poterimus sensuum iudicia defendere. Sed non sunt in eo genere tantae commoditates corporis tamque productae temporibus tamque multae. Hoc positum in Phaedro a Platone probavit Epicurus sensitque in omni disputatione id fieri oportere. Huius, Lyco, oratione locuples, rebus ipsis ielunior. Sic, et quidem diligentius saepiusque ista loquemur inter nos agemusque communiter. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Ac tamen hic mallet non dolere.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Perturbationes autem nulla naturae vi commoventur, omniaque ea sunt opiniones ac iudicia levitatis. Indicant pueri, in quibus ut in speculis natura cernitur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nos commodius agimus.</a> <strong>Quae duo sunt, unum facit.</strong> Tum ille: Tu autem cum ipse tantum librorum habeas, quos hic tandem requiris? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Neque solum ea communia, verum etiam paria esse dixerunt.</a> Verba tu fingas et ea dicas, quae non sentias? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ad corpus diceres pertinere-, sed ea, quae dixi, ad corpusne refers? Quonam, inquit, modo? Utilitatis causa amicitia est quaesita. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse;</a> Hi autem ponunt illi quidem prima naturae, sed ea seiungunt a finibus et a summa bonorum; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Quasi ego id curem, quid ille aiat aut neget.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Honesta oratio, Socratica, Platonis etiam.</strong> Sapiens autem semper beatus est et est aliquando in dolore; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Illis videtur, qui illud non dubitant bonum dicere -; Hoc non est positum in nostra actione. Quae diligentissime contra Aristonem dicuntur a Chryippo. Nec vero hoc oratione solum, sed multo magis vita et factis et moribus comprobavit. <strong>Tum mihi Piso: Quid ergo?</strong> Sed potestne rerum maior esse dissensio? Haec dicuntur fortasse ieiunius; Qualem igitur hominem natura inchoavit? Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; Quid turpius quam sapientis vitam ex insipientium sermone pendere? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ad eas enim res ab Epicuro praecepta dantur. Ita redarguitur ipse a sese, convincunturque scripta eius probitate ipsius ac moribus. Negat esse eam, inquit, propter se expetendam. <strong>Expectoque quid ad id, quod quaerebam, respondeas.</strong> Potius inflammat, ut coercendi magis quam dedocendi esse videantur. Prioris generis est docilitas, memoria; Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque hic ipse iam pridem est reiectus;</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Philosophi autem in suis lectulis plerumque moriuntur.</li><li>Bona autem corporis huic sunt, quod posterius posui, similiora.</li><li>Quae in controversiam veniunt, de iis, si placet, disseramus.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":5} --> +<h5>Rapior illuc, revocat autem Antiochus, nec est praeterea, quem audiamus.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quibus ego vehementer assentior. Licet hic rursus ea commemores, quae optimis verbis ab Epicuro de laude amicitiae dicta sunt. Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Mihi enim erit isdem istis fortasse iam utendum. Quantum Aristoxeni ingenium consumptum videmus in musicis? Si quae forte-possumus. Equidem etiam Epicurum, in physicis quidem, Democriteum puto. Quis negat? Equidem soleo etiam quod uno Graeci, si aliter non possum, idem pluribus verbis exponere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tecum optime, deinde etiam cum mediocri amico. Bonum integritas corporis: misera debilitas. Mihi, inquam, qui te id ipsum rogavi? Ut enim consuetudo loquitur, id solum dicitur honestum, quod est populari fama gloriosum. Bonum incolumis acies: misera caecitas. Ut proverbia non nulla veriora sint quam vestra dogmata. Claudii libidini, qui tum erat summo ne imperio, dederetur. Non est enim vitium in oratione solum, sed etiam in moribus. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Color egregius, integra valitudo, summa gratia, vita denique conferta voluptatum omnium varietate. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Maximus dolor, inquit, brevis est.</a> Nunc haec primum fortasse audientis servire debemus. Luxuriam non reprehendit, modo sit vacua infinita cupiditate et timore. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Frater et T.</a> Haec bene dicuntur, nec ego repugno, sed inter sese ipsa pugnant. <strong>Idem iste, inquam, de voluptate quid sentit?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere. Maximas vero virtutes iacere omnis necesse est voluptate dominante. Nam si amitti vita beata potest, beata esse non potest. Memini vero, inquam; Quo modo autem optimum, si bonum praeterea nullum est? Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Zenonis est, inquam, hoc Stoici.</li><li>Cum sciret confestim esse moriendum eamque mortem ardentiore studio peteret, quam Epicurus voluptatem petendam putat.</li><li>Hoc loco discipulos quaerere videtur, ut, qui asoti esse velint, philosophi ante fiant.</li><li>Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Maximeque eos videre possumus res gestas audire et legere velle, qui a spe gerendi absunt confecti senectute. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Nam Pyrrho, Aristo, Erillus iam diu abiecti. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Equidem, sed audistine modo de Carneade?</a> Et ille ridens: Video, inquit, quid agas; Nulla profecto est, quin suam vim retineat a primo ad extremum. Conferam tecum, quam cuique verso rem subicias; <strong>Nihil sane.</strong> Qui est in parvis malis. Quid enim me prohiberet Epicureum esse, si probarem, quae ille diceret? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Restatis igitur vos; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ita graviter et severe voluptatem secrevit a bono.</a> Equidem, sed audistine modo de Carneade? Sed in rebus apertissimis nimium longi sumus. <em>Ecce aliud simile dissimile.</em> Nulla profecto est, quin suam vim retineat a primo ad extremum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Iubet igitur nos Pythius Apollo noscere nosmet ipsos.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quantum Aristoxeni ingenium consumptum videmus in musicis? Itaque nostrum est-quod nostrum dico, artis est-ad ea principia, quae accepimus. Nam memini etiam quae nolo, oblivisci non possum quae volo. Cur, nisi quod turpis oratio est? Quam tu ponis in verbis, ego positam in re putabam. Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nam quod ita positum est, quod dissolutum sit, id esse sine sensu, id eius modi est, ut non satis plane dicat quid sit dissolutum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Ubi ut eam caperet aut quando? Sin kakan malitiam dixisses, ad aliud nos unum certum vitium consuetudo Latina traduceret. Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quae rursus dum sibi evelli ex ordine nolunt, horridiores evadunt, asperiores, duriores et oratione et moribus.</li><li>In ipsa enim parum magna vis inest, ut quam optime se habere possit, si nulla cultura adhibeatur.</li><li>Nec vero intermittunt aut admirationem earum rerum, quae sunt ab antiquis repertae, aut investigationem novarum.</li><li>Itaque ab his ordiamur.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Sic enim censent, oportunitatis esse beate vivere.</li><li>Fortitudinis quaedam praecepta sunt ac paene leges, quae effeminari virum vetant in dolore.</li><li>Quod si ita se habeat, non possit beatam praestare vitam sapientia.</li><li>Quicquid enim a sapientia proficiscitur, id continuo debet expletum esse omnibus suis partibus;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Ergo ita: non posse honeste vivi, nisi honeste vivatur? Aperiendum est igitur, quid sit voluptas; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque his sapiens semper vacabit.</a> Sed ad haec, nisi molestum est, habeo quae velim. Ergo hoc quidem apparet, nos ad agendum esse natos. Quis est enim, in quo sit cupiditas, quin recte cupidus dici possit? In quo etsi est magnus, tamen nova pleraque et perpauca de moribus. Iam in altera philosophiae parte. De hominibus dici non necesse est. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Haec ego non possum dicere non esse hominis quamvis et belli et humani, sapientis vero nullo modo, physici praesertim, quem se ille esse vult, putare ullum esse cuiusquam diem natalem. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":5} --> +<h5>Qualem igitur hominem natura inchoavit?</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>In qua si nihil est praeter rationem, sit in una virtute finis bonorum; Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. Nulla profecto est, quin suam vim retineat a primo ad extremum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Tubulo putas dicere?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cum sciret confestim esse moriendum eamque mortem ardentiore studio peteret, quam Epicurus voluptatem petendam putat. Ergo illi intellegunt quid Epicurus dicat, ego non intellego? Qui igitur convenit ab alia voluptate dicere naturam proficisci, in alia summum bonum ponere? Teneo, inquit, finem illi videri nihil dolere. Quo minus animus a se ipse dissidens secumque discordans gustare partem ullam liquidae voluptatis et liberae potest. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Et ille ridens: Video, inquit, quid agas;</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Certe nihil nisi quod possit ipsum propter se iure laudari. Et quidem, Cato, hanc totam copiam iam Lucullo nostro notam esse oportebit; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tamen a proposito, inquam, aberramus.</a> Nam memini etiam quae nolo, oblivisci non possum quae volo. Amicitiae vero locus ubi esse potest aut quis amicus esse cuiquam, quem non ipsum amet propter ipsum? <strong>Satis est ad hoc responsum.</strong> De ingenio eius in his disputationibus, non de moribus quaeritur. Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Illum mallem levares, quo optimum atque humanissimum virum, Cn.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Illi enim inter se dissentiunt. Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia? Itaque his sapiens semper vacabit. Hunc vos beatum; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Equidem e Cn.</a> Verum enim diceret, idque Socratem, qui voluptatem nullo loco numerat, audio dicentem, cibi condimentum esse famem, potionis sitim. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">An haec ab eo non dicuntur?</a> <em>Collatio igitur ista te nihil iuvat.</em> Si stante, hoc natura videlicet vult, salvam esse se, quod concedimus; Iam id ipsum absurdum, maximum malum neglegi. Vidit Homerus probari fabulam non posse, si cantiunculis tantus irretitus vir teneretur; Sensibus enim ornavit ad res percipiendas idoneis, ut nihil aut non multum adiumento ullo ad suam confirmationem indigerent; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Istic sum, inquit. Nullus est igitur cuiusquam dies natalis. Inde sermone vario sex illa a Dipylo stadia confecimus. Nam de isto magna dissensio est. At enim hic etiam dolore. Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius. Ergo infelix una molestia, fellx rursus, cum is ipse anulus in praecordiis piscis inventus est? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed tamen intellego quid velit.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed mehercule pergrata mihi oratio tua.</a> Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit? Non pugnem cum homine, cur tantum habeat in natura boni; Cur igitur easdem res, inquam, Peripateticis dicentibus verbum nullum est, quod non intellegatur? At modo dixeras nihil in istis rebus esse, quod interesset. Non enim, si malum est dolor, carere eo malo satis est ad bene vivendum. Quaero igitur, quo modo hae tantae commendationes a natura profectae subito a sapientia relictae sint. Huic ego, si negaret quicquam interesse ad beate vivendum quali uteretur victu, concederem, laudarem etiam; Dolere malum est: in crucem qui agitur, beatus esse non potest. Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quare hoc videndum est, possitne nobis hoc ratio philosophorum dare. Omnia contraria, quos etiam insanos esse vultis. Quod autem satis est, eo quicquid accessit, nimium est; Res enim fortasse verae, certe graves, non ita tractantur, ut debent, sed aliquanto minutius. Si longus, levis; <strong>Quis est tam dissimile homini.</strong> Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Non pugnem cum homine, cur tantum habeat in natura boni; Habes, inquam, Cato, formam eorum, de quibus loquor, philosophorum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Cur, nisi quod turpis oratio est?</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ut nemo dubitet, eorum omnia officia quo spectare, quid sequi, quid fugere debeant? Non enim in selectione virtus ponenda erat, ut id ipsum, quod erat bonorum ultimum, aliud aliquid adquireret. Sit enim idem caecus, debilis. Expressa vero in iis aetatibus, quae iam confirmatae sunt. At, illa, ut vobis placet, partem quandam tuetur, reliquam deserit. Hoc dixerit potius Ennius: Nimium boni est, cui nihil est mali. Si quidem, inquit, tollerem, sed relinquo. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Ego vero volo in virtute vim esse quam maximam;</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Virtutis, magnitudinis animi, patientiae, fortitudinis fomentis dolor mitigari solet. Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. <strong>Laboro autem non sine causa;</strong> Quod, inquit, quamquam voluptatibus quibusdam est saepe iucundius, tamen expetitur propter voluptatem. An hoc usque quaque, aliter in vita? Sed quot homines, tot sententiae; Ut necesse sit omnium rerum, quae natura vigeant, similem esse finem, non eundem. Confecta res esset. Ita fit cum gravior, tum etiam splendidior oratio. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Mihi enim erit isdem istis fortasse iam utendum.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Non potes, nisi retexueris illa. In contemplatione et cognitione posita rerum, quae quia deorum erat vitae simillima, sapiente visa est dignissima. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed virtutem ipsam inchoavit, nihil amplius.</a> Sedulo, inquam, faciam. Nam constitui virtus nullo modo potesti nisi ea, quae sunt prima naturae, ut ad summam pertinentia tenebit. Invidiosum nomen est, infame, suspectum. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Magni enim aestimabat pecuniam non modo non contra leges, sed etiam legibus partam.</li><li>Erit enim mecum, si tecum erit.</li><li>Ex rebus enim timiditas, non ex vocabulis nascitur.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nihilne te delectat umquam -video, quicum loquar-, te igitur, Torquate, ipsum per se nihil delectat? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis. Non enim quaero quid verum, sed quid cuique dicendum sit. <em>Honesta oratio, Socratica, Platonis etiam.</em> Haec bene dicuntur, nec ego repugno, sed inter sese ipsa pugnant. <em>Quare attende, quaeso.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tubulo putas dicere?</a> <strong>Facillimum id quidem est, inquam.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>At quicum ioca seria, ut dicitur, quicum arcana, quicum occulta omnia? Et quidem saepe quaerimus verbum Latinum par Graeco et quod idem valeat; Virtutibus igitur rectissime mihi videris et ad consuetudinem nostrae orationis vitia posuisse contraria. Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Summum ením bonum exposuit vacuitatem doloris;</a> Itaque dicunt nec dubitant: mihi sic usus est, tibi ut opus est facto, fac. Mihi vero, inquit, placet agi subtilius et, ut ipse dixisti, pressius. Sed id ne cogitari quidem potest quale sit, ut non repugnet ipsum sibi. Fieri, inquam, Triari, nullo pacto potest, ut non dicas, quid non probes eius, a quo dissentias. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quippe: habes enim a rhetoribus;</a> Sin aliud quid voles, postea. Non dolere, inquam, istud quam vim habeat postea videro; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Scripta sane et multa et polita, sed nescio quo pacto auctoritatem oratio non habet. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:list --> +<ul><li>Quamquam tu hanc copiosiorem etiam soles dicere.</li><li>Hic ambiguo ludimur.</li><li>Ipse Epicurus fortasse redderet, ut Sextus Peducaeus, Sex.</li><li>Apparet statim, quae sint officia, quae actiones.</li><li>Curium putes loqui, interdum ita laudat, ut quid praeterea sit bonum neget se posse ne suspicari quidem.</li><li>Et non ex maxima parte de tota iudicabis?</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Sin laboramus, quis est, qui alienae modum statuat industriae?</li><li>Facit enim ille duo seiuncta ultima bonorum, quae ut essent vera, coniungi debuerunt;</li><li>Qui non moveatur et offensione turpitudinis et comprobatione honestatis?</li><li>Atqui reperies, inquit, in hoc quidem pertinacem;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Mihi, inquam, qui te id ipsum rogavi? Id enim natura desiderat. Septem autem illi non suo, sed populorum suffragio omnium nominati sunt. Ex eorum enim scriptis et institutis cum omnis doctrina liberalis, omnis historia. Non dolere, inquam, istud quam vim habeat postea videro; Levatio igitur vitiorum magna fit in iis, qui habent ad virtutem progressionis aliquantum. Itaque et manendi in vita et migrandi ratio omnis iis rebus, quas supra dixi, metienda. Summum ením bonum exposuit vacuitatem doloris; Stoici scilicet. Eaedem res maneant alio modo. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tu quidem reddes;</a> <strong>Haec para/doca illi, nos admirabilia dicamus.</strong> Idemne potest esse dies saepius, qui semel fuit? <em>Quae contraria sunt his, malane?</em> Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius. Certe non potest. Mihi, inquam, qui te id ipsum rogavi? Hanc ergo intuens debet institutum illud quasi signum absolvere. Portenta haec esse dicit, neque ea ratione ullo modo posse vivi; Quid ergo attinet gloriose loqui, nisi constanter loquare? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Quo studio Aristophanem putamus aetatem in litteris duxisse?</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Teneo, inquit, finem illi videri nihil dolere. Graece donan, Latine voluptatem vocant. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque his sapiens semper vacabit.</a> At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit; Qui ita affectus, beatum esse numquam probabis; Quid igitur, inquit, eos responsuros putas? Ergo opifex plus sibi proponet ad formarum quam civis excellens ad factorum pulchritudinem? <em>Pauca mutat vel plura sane;</em> At Zeno eum non beatum modo, sed etiam divitem dicere ausus est. Nobis aliter videtur, recte secusne, postea; Bestiarum vero nullum iudicium puto. At quicum ioca seria, ut dicitur, quicum arcana, quicum occulta omnia? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Immo alio genere;</strong> Rationis enim perfectio est virtus; Eam si varietatem diceres, intellegerem, ut etiam non dicente te intellego; Nos cum te, M. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Inde igitur, inquit, ordiendum est.</a> Hoc tu nunc in illo probas. <em>Dat enim intervalla et relaxat.</em> At, illa, ut vobis placet, partem quandam tuetur, reliquam deserit. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Maximus dolor, inquit, brevis est.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Ergo, inquit, tibi Q.</em> <strong>Nescio quo modo praetervolavit oratio.</strong> <strong>Quid de Pythagora?</strong> Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? Quae quo sunt excelsiores, eo dant clariora indicia naturae. <strong>Beatus sibi videtur esse moriens.</strong> Aliter enim explicari, quod quaeritur, non potest. <em>Iam enim adesse poterit.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Aliter enim explicari, quod quaeritur, non potest.</a> Eadem nunc mea adversum te oratio est. Te enim iudicem aequum puto, modo quae dicat ille bene noris. Respondent extrema primis, media utrisque, omnia omnibus. Bona autem corporis huic sunt, quod posterius posui, similiora. Quae si potest singula consolando levare, universa quo modo sustinebit? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Nisi enim id faceret, cur Plato Aegyptum peragravit, ut a sacerdotibus barbaris numeros et caelestia acciperet?</li><li>Primum quid tu dicis breve?</li><li>Quid enim me prohiberet Epicureum esse, si probarem, quae ille diceret?</li><li>Conferam tecum, quam cuique verso rem subicias;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Teneo, inquit, finem illi videri nihil dolere. Quasi vero aut concedatur in omnibus stultis aeque magna esse vitia, et eadem inbecillitate et inconstantia L. At ego quem huic anteponam non audeo dicere; <strong>Nam Pyrrho, Aristo, Erillus iam diu abiecti.</strong> Quos quidem tibi studiose et diligenter tractandos magnopere censeo. Sed emolumenta communia esse dicuntur, recte autem facta et peccata non habentur communia. Qui autem esse poteris, nisi te amor ipse ceperit? Res enim se praeclare habebat, et quidem in utraque parte. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Qui autem de summo bono dissentit de tota philosophiae ratione dissentit.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>In his igitur partibus duabus nihil erat, quod Zeno commutare gestiret. Scaevola tribunus plebis ferret ad plebem vellentne de ea re quaeri. <em>Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse;</em> Res enim se praeclare habebat, et quidem in utraque parte. Qua tu etiam inprudens utebare non numquam. Tanti autem aderant vesicae et torminum morbi, ut nihil ad eorum magnitudinem posset accedere. Primum in nostrane potestate est, quid meminerimus? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>His enim rebus detractis negat se reperire in asotorum vita quod reprehendat. At, si voluptas esset bonum, desideraret. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sint modo partes vitae beatae.</a> Tecum optime, deinde etiam cum mediocri amico. Satisne vobis videor pro meo iure in vestris auribus commentatus? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Vestri haec verecundius, illi fortasse constantius. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Reguli reiciendam;</a> Ita ne hoc quidem modo paria peccata sunt. Nunc omni virtuti vitium contrario nomine opponitur. Eorum enim est haec querela, qui sibi cari sunt seseque diligunt. Est tamen ea secundum naturam multoque nos ad se expetendam magis hortatur quam superiora omnia. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Torquatus, is qui consul cum Cn. Tum ille: Tu autem cum ipse tantum librorum habeas, quos hic tandem requiris? <strong>Sed ea mala virtuti magnitudine obruebantur.</strong> Non autem hoc: igitur ne illud quidem. Vidit Homerus probari fabulam non posse, si cantiunculis tantus irretitus vir teneretur; Roges enim Aristonem, bonane ei videantur haec: vacuitas doloris, divitiae, valitudo; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Est, ut dicis, inquam.</a> Beatus sibi videtur esse moriens. Quorum altera prosunt, nocent altera. Ita graviter et severe voluptatem secrevit a bono. Quid ei reliquisti, nisi te, quoquo modo loqueretur, intellegere, quid diceret? Non quam nostram quidem, inquit Pomponius iocans; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Cum id fugiunt, re eadem defendunt, quae Peripatetici, verba. Ita multa dicunt, quae vix intellegam. Si longus, levis dictata sunt. Si enim ita est, vide ne facinus facias, cum mori suadeas. Conferam tecum, quam cuique verso rem subicias; Si qua in iis corrigere voluit, deteriora fecit. Ita multo sanguine profuso in laetitia et in victoria est mortuus. Nulla profecto est, quin suam vim retineat a primo ad extremum. Haec quo modo conveniant, non sane intellego. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Ex eorum enim scriptis et institutis cum omnis doctrina liberalis, omnis historia.</li><li>Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster?</li><li>Quae dici eadem de ceteris virtutibus possunt, quarum omnium fundamenta vos in voluptate tamquam in aqua ponitis.</li><li>Quae est igitur causa istarum angustiarum?</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quaerimus enim finem bonorum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Cuius similitudine perspecta in formarum specie ac dignitate transitum est ad honestatem dictorum atque factorum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Illi igitur antiqui non tam acute optabiliorem illam vitam putant, praestantiorem, beatiorem, Stoici autem tantum modo praeponendam in seligendo, non quo beatior ea vita sit, sed quod ad naturam accommodatior. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p><strong>Tum Torquatus: Prorsus, inquit, assentior;</strong> <em>Sed fortuna fortis;</em> Magni enim aestimabat pecuniam non modo non contra leges, sed etiam legibus partam. Haec para/doca illi, nos admirabilia dicamus. Eadem nunc mea adversum te oratio est. Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Omnes enim iucundum motum, quo sensus hilaretur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quam ob rem tandem, inquit, non satisfacit?</a> Quid enim de amicitia statueris utilitatis causa expetenda vides. Idem iste, inquam, de voluptate quid sentit? Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Ut placet, inquit, etsi enim illud erat aptius, aequum cuique concedere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quam ob rem tandem, inquit, non satisfacit? Itaque si aut requietem natura non quaereret aut eam posset alia quadam ratione consequi. Primum cur ista res digna odio est, nisi quod est turpis? <strong>Quodsi ipsam honestatem undique pertectam atque absolutam.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Non enim, si omnia non sequebatur, idcirco non erat ortus illinc.</li><li>Respondent extrema primis, media utrisque, omnia omnibus.</li><li>Ergo instituto veterum, quo etiam Stoici utuntur, hinc capiamus exordium.</li><li>Mene ergo et Triarium dignos existimas, apud quos turpiter loquare?</li><li>Atque etiam ad iustitiam colendam, ad tuendas amicitias et reliquas caritates quid natura valeat haec una cognitio potest tradere.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Si stante, hoc natura videlicet vult, salvam esse se, quod concedimus; <strong>Cave putes quicquam esse verius.</strong> Sed haec in pueris; Sed hoc sane concedamus. Terram, mihi crede, ea lanx et maria deprimet. Sint modo partes vitae beatae. Ego vero volo in virtute vim esse quam maximam; Si quicquam extra virtutem habeatur in bonis. Est enim effectrix multarum et magnarum voluptatum. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ipse Epicurus fortasse redderet, ut Sextus Peducaeus, Sex.</a> Sed haec quidem liberius ab eo dicuntur et saepius. Itaque sensibus rationem adiunxit et ratione effecta sensus non reliquit. Mihi, inquam, qui te id ipsum rogavi? Uterque enim summo bono fruitur, id est voluptate. Cui Tubuli nomen odio non est? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Beatus sibi videtur esse moriens.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nos cum te, M.</a> Qua igitur re ab deo vincitur, si aeternitate non vincitur? Primum in nostrane potestate est, quid meminerimus? A mene tu? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Illa tamen simplicia, vestra versuta.</a> Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Quam ob rem tandem, inquit, non satisfacit? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nemo igitur esse beatus potest. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cui Tubuli nomen odio non est?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed residamus, inquit, si placet.</a> Illa argumenta propria videamus, cur omnia sint paria peccata. Quae cum praeponunt, ut sit aliqua rerum selectio, naturam videntur sequi; Dic in quovis conventu te omnia facere, ne doleas. Quo modo autem philosophus loquitur? Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Pugnant Stoici cum Peripateticis.</a> <em>Non laboro, inquit, de nomine.</em> Tecum optime, deinde etiam cum mediocri amico. <strong>Quae cum essent dicta, discessimus.</strong> Ita cum ea volunt retinere, quae superiori sententiae conveniunt, in Aristonem incidunt; Id mihi magnum videtur. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Nummus in Croesi divitiis obscuratur, pars est tamen divitiarum.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Tum, Quintus et Pomponius cum idem se velle dixissent, Piso exorsus est. Quae duo sunt, unum facit. Paria sunt igitur. <strong>Et quod est munus, quod opus sapientiae?</strong> Bonum integritas corporis: misera debilitas. Nonne videmus quanta perturbatio rerum omnium consequatur, quanta confusio? Bonum integritas corporis: misera debilitas. Quamquam ab iis philosophiam et omnes ingenuas disciplinas habemus; <em>Sed tamen est aliquid, quod nobis non liceat, liceat illis.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Respondent extrema primis, media utrisque, omnia omnibus. Hoc etsi multimodis reprehendi potest, tamen accipio, quod dant. Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur. Videamus animi partes, quarum est conspectus illustrior; Callipho ad virtutem nihil adiunxit nisi voluptatem, Diodorus vacuitatem doloris. Verba tu fingas et ea dicas, quae non sentias? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Utrum igitur tibi litteram videor an totas paginas commovere?</li><li>Expressa vero in iis aetatibus, quae iam confirmatae sunt.</li><li>Cuius etiam illi hortuli propinqui non memoriam solum mihi afferunt, sed ipsum videntur in conspectu meo ponere.</li><li>Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Mihi enim satis est, ipsis non satis.</a> Atqui, inquam, Cato, si istud optinueris, traducas me ad te totum licebit. An hoc usque quaque, aliter in vita? Sed quod proximum fuit non vidit. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Tu vero, inquam, ducas licet, si sequetur;</li><li>Quid iudicant sensus?</li><li>Etenim nec iustitia nec amicitia esse omnino poterunt, nisi ipsae per se expetuntur.</li><li>Si enim ad populum me vocas, eum.</li><li>Nec tamen ille erat sapiens quis enim hoc aut quando aut ubi aut unde?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Id Sextilius factum negabat.</a> Philosophi autem in suis lectulis plerumque moriuntur. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Rhetorice igitur, inquam, nos mavis quam dialectice disputare?</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quamquam tu hanc copiosiorem etiam soles dicere. De hominibus dici non necesse est. Quid ergo attinet dicere: Nihil haberem, quod reprehenderem, si finitas cupiditates haberent? <em>Et ille ridens: Video, inquit, quid agas;</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Collatio igitur ista te nihil iuvat.</a> <em>Sin aliud quid voles, postea.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Ergo adhuc, quantum equidem intellego, causa non videtur fuisse mutandi nominis.</li><li>Profectus in exilium Tubulus statim nec respondere ausus;</li><li>Legimus tamen Diogenem, Antipatrum, Mnesarchum, Panaetium, multos alios in primisque familiarem nostrum Posidonium.</li><li>Verum tamen cum de rebus grandioribus dicas, ipsae res verba rapiunt;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Ut in voluptate sit, qui epuletur, in dolore, qui torqueatur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quis negat?</a> Hi autem ponunt illi quidem prima naturae, sed ea seiungunt a finibus et a summa bonorum; <em>Haec para/doca illi, nos admirabilia dicamus.</em> Quod autem magnum dolorem brevem, longinquum levem esse dicitis, id non intellego quale sit. <em>Istam voluptatem perpetuam quis potest praestare sapienti?</em> <em>Cur post Tarentum ad Archytam?</em> Huic mori optimum esse propter desperationem sapientiae, illi propter spem vivere. Apud imperitos tum illa dicta sunt, aliquid etiam coronae datum; <strong>Nos commodius agimus.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>In qua quid est boni praeter summam voluptatem, et eam sempiternam?</li><li>Sin te auctoritas commovebat, nobisne omnibus et Platoni ipsi nescio quem illum anteponebas?</li><li>Sed potestne rerum maior esse dissensio?</li><li>Stoici scilicet.</li><li>Cupit enim dícere nihil posse ad beatam vitam deesse sapienti.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":4} --> +<h4>Sequitur disserendi ratio cognitioque naturae;</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hos contra singulos dici est melius. An ea, quae per vinitorem antea consequebatur, per se ipsa curabit? Videmus igitur ut conquiescere ne infantes quidem possint. Sapiens autem semper beatus est et est aliquando in dolore; Duo enim genera quae erant, fecit tria. <strong>Inde igitur, inquit, ordiendum est.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Itaque hic ipse iam pridem est reiectus; Hoc loco tenere se Triarius non potuit. Sed ad illum redeo. <em>Quae duo sunt, unum facit.</em> Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Habent enim et bene longam et satis litigiosam disputationem.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>At, si voluptas esset bonum, desideraret. Maximus dolor, inquit, brevis est. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Compensabatur, inquit, cum summis doloribus laetitia.</li><li>Tu vero, inquam, ducas licet, si sequetur;</li><li>Addidisti ad extremum etiam indoctum fuisse.</li><li>Deinde disputat, quod cuiusque generis animantium statui deceat extremum.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Qui autem esse poteris, nisi te amor ipse ceperit? Si stante, hoc natura videlicet vult, salvam esse se, quod concedimus; An tu me de L. <strong>Si verbum sequimur, primum longius verbum praepositum quam bonum.</strong> Deinde disputat, quod cuiusque generis animantium statui deceat extremum. <em>Ostendit pedes et pectus.</em> Sed haec quidem liberius ab eo dicuntur et saepius. Nam, ut paulo ante docui, augendae voluptatis finis est doloris omnis amotio. Neque enim civitas in seditione beata esse potest nec in discordia dominorum domus; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Mihi, inquam, qui te id ipsum rogavi? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Restant Stoici, qui cum a Peripateticis et Academicis omnia transtulissent, nominibus aliis easdem res secuti sunt. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Et hunc idem dico, inquieta sed ad virtutes et ad vitia nihil interesse.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quo modo autem optimum, si bonum praeterea nullum est? Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Aperiendum est igitur, quid sit voluptas;</a> Quid autem habent admirationis, cum prope accesseris? Huius ego nunc auctoritatem sequens idem faciam. <em>Videsne, ut haec concinant?</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Deinde disputat, quod cuiusque generis animantium statui deceat extremum.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Et hunc idem dico, inquieta sed ad virtutes et ad vitia nihil interesse. Num igitur utiliorem tibi hunc Triarium putas esse posse, quam si tua sint Puteolis granaria? Sed haec ab Antiocho, familiari nostro, dicuntur multo melius et fortius, quam a Stasea dicebantur. Illud quaero, quid ei, qui in voluptate summum bonum ponat, consentaneum sit dicere. Nullum inveniri verbum potest quod magis idem declaret Latine, quod Graece, quam declarat voluptas. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nunc haec primum fortasse audientis servire debemus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Honesta oratio, Socratica, Platonis etiam.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nihil enim iam habes, quod ad corpus referas; Atqui reperies, inquit, in hoc quidem pertinacem; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Summus dolor plures dies manere non potest?</a> At ille pellit, qui permulcet sensum voluptate. Quis istud possit, inquit, negare? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>At iste non dolendi status non vocatur voluptas.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Invidiosum nomen est, infame, suspectum. Quae cum dixisset paulumque institisset, Quid est? Quae fere omnia appellantur uno ingenii nomine, easque virtutes qui habent, ingeniosi vocantur. Nihil enim iam habes, quod ad corpus referas; Nihil opus est exemplis hoc facere longius. Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Atqui reperies, inquit, in hoc quidem pertinacem; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Sed ad haec, nisi molestum est, habeo quae velim. <strong>Age, inquies, ista parva sunt.</strong> Quasi ego id curem, quid ille aiat aut neget. Et hunc idem dico, inquieta sed ad virtutes et ad vitia nihil interesse. <strong>Nos cum te, M.</strong> <strong>Ut id aliis narrare gestiant?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Septem autem illi non suo, sed populorum suffragio omnium nominati sunt. Videmus igitur ut conquiescere ne infantes quidem possint. Rationis enim perfectio est virtus; <strong>Illa tamen simplicia, vestra versuta.</strong> Nihil enim iam habes, quod ad corpus referas; Quae animi affectio suum cuique tribuens atque hanc, quam dico. Item de contrariis, a quibus ad genera formasque generum venerunt. Illum mallem levares, quo optimum atque humanissimum virum, Cn. <em>At hoc in eo M.</em> Tum mihi Piso: Quid ergo? Respondeat totidem verbis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Aliter homines, aliter philosophos loqui putas oportere? Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si longus, levis dictata sunt.</a> Itaque eos id agere, ut a se dolores, morbos, debilitates repellant. Quid, si non sensus modo ei sit datus, verum etiam animus hominis? Nihil opus est exemplis hoc facere longius. Non minor, inquit, voluptas percipitur ex vilissimis rebus quam ex pretiosissimis. <strong>Poterat autem inpune;</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Tamen a proposito, inquam, aberramus.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quod quidem nobis non saepe contingit. Quae cum magnifice primo dici viderentur, considerata minus probabantur. Quas enim kakaw Graeci appellant, vitia malo quam malitias nominare. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quodcumque in mentem incideret, et quodcumque tamquam occurreret.</a> At certe gravius. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Eam tum adesse, cum dolor omnis absit;</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quae similitudo in genere etiam humano apparet. Sed quoniam et advesperascit et mihi ad villam revertendum est, nunc quidem hactenus; Ita multo sanguine profuso in laetitia et in victoria est mortuus. Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. Sed tamen enitar et, si minus multa mihi occurrent, non fugiam ista popularia. Hoc mihi cum tuo fratre convenit. <strong>Quo modo autem philosophus loquitur?</strong> Vives, inquit Aristo, magnifice atque praeclare, quod erit cumque visum ages, numquam angere, numquam cupies, numquam timebis. Pauca mutat vel plura sane; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>His enim rebus detractis negat se reperire in asotorum vita quod reprehendat. Age sane, inquam. Ratio enim nostra consentit, pugnat oratio. Ait enim se, si uratur, Quam hoc suave! dicturum. His enim rebus detractis negat se reperire in asotorum vita quod reprehendat. Is es profecto tu. Dempta enim aeternitate nihilo beatior Iuppiter quam Epicurus; At miser, si in flagitiosa et vitiosa vita afflueret voluptatibus. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Idque testamento cavebit is, qui nobis quasi oraculum ediderit nihil post mortem ad nos pertinere?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Septem autem illi non suo, sed populorum suffragio omnium nominati sunt. Etenim semper illud extra est, quod arte comprehenditur. An est aliquid, quod te sua sponte delectet? Solum praeterea formosum, solum liberum, solum civem, stultost; <em>Quis istum dolorem timet?</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Compensabatur, inquit, cum summis doloribus laetitia. Duarum enim vitarum nobis erunt instituta capienda. Beatus autem esse in maximarum rerum timore nemo potest. Ut alios omittam, hunc appello, quem ille unum secutus est. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia?</li><li>Illo enim addito iuste fit recte factum, per se autem hoc ipsum reddere in officio ponitur.</li><li>Quo igitur, inquit, modo?</li><li>Sed est forma eius disciplinae, sicut fere ceterarum, triplex: una pars est naturae, disserendi altera, vivendi tertia.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Num igitur dubium est, quin, si in re ipsa nihil peccatur a superioribus, verbis illi commodius utantur? Nihil sane. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si mala non sunt, iacet omnis ratio Peripateticorum.</a> <strong>Tum mihi Piso: Quid ergo?</strong> Amicitiam autem adhibendam esse censent, quia sit ex eo genere, quae prosunt. Deinde disputat, quod cuiusque generis animantium statui deceat extremum. <strong>Atqui reperies, inquit, in hoc quidem pertinacem;</strong> Quid paulo ante, inquit, dixerim nonne meministi, cum omnis dolor detractus esset, variari, non augeri voluptatem? Tum Lucius: Mihi vero ista valde probata sunt, quod item fratri puto. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Hic nihil fuit, quod quaereremus.</a> Ratio quidem vestra sic cogit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Mihi enim satis est, ipsis non satis.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Nam et a te perfici istam disputationem volo, nec tua mihi oratio longa videri potest.</li><li>Audax negotium, dicerem impudens, nisi hoc institutum postea translatum ad philosophos nostros esset.</li><li>Sed in rebus apertissimis nimium longi sumus.</li><li>Itaque dicunt nec dubitant: mihi sic usus est, tibi ut opus est facto, fac.</li><li>Illi enim inter se dissentiunt.</li><li>Sed haec omittamus;</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Magni enim aestimabat pecuniam non modo non contra leges, sed etiam legibus partam.</li><li>Nunc haec primum fortasse audientis servire debemus.</li><li>Quodsi vultum tibi, si incessum fingeres, quo gravior viderere, non esses tui similis;</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":4} --> +<h4>Est enim effectrix multarum et magnarum voluptatum.</h4> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Quod ea non occurrentia fingunt, vincunt Aristonem; <strong>Negat esse eam, inquit, propter se expetendam.</strong> Hoc tu nunc in illo probas. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint. Quid enim de amicitia statueris utilitatis causa expetenda vides. Ergo illi intellegunt quid Epicurus dicat, ego non intellego? Quae qui non vident, nihil umquam magnum ac cognitione dignum amaverunt. Eiuro, inquit adridens, iniquum, hac quidem de re; <strong>Sic enim censent, oportunitatis esse beate vivere.</strong> An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Mihi quidem Homerus huius modi quiddam vidisse videatur in iis, quae de Sirenum cantibus finxerit. <strong>Sic consequentibus vestris sublatis prima tolluntur.</strong> Summus dolor plures dies manere non potest? Egone non intellego, quid sit don Graece, Latine voluptas? </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nec enim absolvi beata vita sapientis neque ad exitum perduci poterit, si prima quaeque bene ab eo consulta atque facta ipsius oblivione obruentur. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Traditur, inquit, ab Epicuro ratio neglegendi doloris. Huius, Lyco, oratione locuples, rebus ipsis ielunior. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si longus, levis;</a> Mene ergo et Triarium dignos existimas, apud quos turpiter loquare? Ut placet, inquit, etsi enim illud erat aptius, aequum cuique concedere. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Eadem fortitudinis ratio reperietur.</a> Si autem id non concedatur, non continuo vita beata tollitur. Equidem, sed audistine modo de Carneade? <em>Tubulo putas dicere?</em> Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quo plebiscito decreta a senatu est consuli quaestio Cn.</a> Utinam quidem dicerent alium alio beatiorem! Iam ruinas videres. Quare si potest esse beatus is, qui est in asperis reiciendisque rebus, potest is quoque esse. <em>Ego vero isti, inquam, permitto.</em> Consequens enim est et post oritur, ut dixi. Sed nimis multa. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Videsne, ut haec concinant?</a> Illa argumenta propria videamus, cur omnia sint paria peccata. Egone non intellego, quid sit don Graece, Latine voluptas? <em>At coluit ipse amicitias.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Re mihi non aeque satisfacit, et quidem locis pluribus.</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. Sed videbimus. Expressa vero in iis aetatibus, quae iam confirmatae sunt. Hoc positum in Phaedro a Platone probavit Epicurus sensitque in omni disputatione id fieri oportere. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque his sapiens semper vacabit.</a> Quae est igitur causa istarum angustiarum? Eam stabilem appellas. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Mihi enim satis est, ipsis non satis.</a> Tum mihi Piso: Quid ergo? Atqui reperies, inquit, in hoc quidem pertinacem; <strong>Stoici scilicet.</strong> Tum mihi Piso: Quid ergo? Equidem e Cn. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Facillimum id quidem est, inquam.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Potius inflammat, ut coercendi magis quam dedocendi esse videantur. Respondeat totidem verbis. Ex rebus enim timiditas, non ex vocabulis nascitur. Non pugnem cum homine, cur tantum habeat in natura boni; <em>Prodest, inquit, mihi eo esse animo.</em> Paria sunt igitur. Aliter enim nosmet ipsos nosse non possumus. Bonum incolumis acies: misera caecitas. <em>Sed quae tandem ista ratio est?</em> Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Similiter sensus, cum accessit ad naturam, tuetur illam quidem, sed etiam se tuetur;</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Est autem etiam actio quaedam corporis, quae motus et status naturae congruentis tenet; Erit enim mecum, si tecum erit. Ut optime, secundum naturam affectum esse possit. Quis istud, quaeso, nesciebat? <strong>Ita nemo beato beatior.</strong> At ille pellit, qui permulcet sensum voluptate. Ille incendat? De hominibus dici non necesse est. Dolor ergo, id est summum malum, metuetur semper, etiamsi non aderit; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Quid, si etiam iucunda memoria est praeteritorum malorum?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hoc enim constituto in philosophia constituta sunt omnia. Nunc omni virtuti vitium contrario nomine opponitur. Illum mallem levares, quo optimum atque humanissimum virum, Cn. Ita ne hoc quidem modo paria peccata sunt. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si quae forte-possumus.</a> Egone quaeris, inquit, quid sentiam? Piso, familiaris noster, et alia multa et hoc loco Stoicos irridebat: Quid enim? Laelius clamores sofòw ille so lebat Edere compellans gumias ex ordine nostros. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sed ad haec, nisi molestum est, habeo quae velim.</li><li>Quod cum ille dixisset et satis disputatum videretur, in oppidum ad Pomponium perreximus omnes.</li><li>Nam neque virtute retinetur ille in vita, nec iis, qui sine virtute sunt, mors est oppetenda.</li><li>Sed emolumenta communia esse dicuntur, recte autem facta et peccata non habentur communia.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Esse enim quam vellet iniquus iustus poterat inpune.</li><li>Quod cum accidisset ut alter alterum necopinato videremus, surrexit statim.</li><li>Quae duo sunt, unum facit.</li><li>Nunc ita separantur, ut disiuncta sint, quo nihil potest esse perversius.</li><li>Quod dicit Epicurus etiam de voluptate, quae minime sint voluptates, eas obscurari saepe et obrui.</li><li>Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><em>Itaque fecimus.</em> Non dolere, inquam, istud quam vim habeat postea videro; Et quidem, inquit, vehementer errat; <strong>Bonum valitudo: miser morbus.</strong> Sed ad rem redeamus; Suo enim quisque studio maxime ducitur. An ea, quae per vinitorem antea consequebatur, per se ipsa curabit? Sed nimis multa. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Qualem igitur hominem natura inchoavit?</h1> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Suo enim quisque studio maxime ducitur. Si enim, ut mihi quidem videtur, non explet bona naturae voluptas, iure praetermissa est; <strong>Praeclare hoc quidem.</strong> Theophrastus mediocriterne delectat, cum tractat locos ab Aristotele ante tractatos? Idem etiam dolorem saepe perpetiuntur, ne, si id non faciant, incidant in maiorem. Si longus, levis; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ut enim consuetudo loquitur, id solum dicitur honestum, quod est populari fama gloriosum. Sed finge non solum callidum eum, qui aliquid improbe faciat, verum etiam praepotentem, ut M. <em>Multoque hoc melius nos veriusque quam Stoici.</em> Ut nemo dubitet, eorum omnia officia quo spectare, quid sequi, quid fugere debeant? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sit enim idem caecus, debilis.</a> Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? Qua tu etiam inprudens utebare non numquam. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quia dolori non voluptas contraria est, sed doloris privatio.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>At iam decimum annum in spelunca iacet. <em>Bonum liberi: misera orbitas.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Indicant pueri, in quibus ut in speculis natura cernitur. -, sed ut hoc iudicaremus, non esse in iis partem maximam positam beate aut secus vivendi. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Itaque his sapiens semper vacabit.</a> Quo plebiscito decreta a senatu est consuli quaestio Cn. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading --> +<h2>Velut ego nunc moveor.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur haec eadem Democritus?</a> Ergo ita: non posse honeste vivi, nisi honeste vivatur? Ergo in utroque exercebantur, eaque disciplina effecit tantam illorum utroque in genere dicendi copiam. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Idemne, quod iucunde?</a> Et quod est munus, quod opus sapientiae? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Hoc Hieronymus summum bonum esse dixit.</li><li>De malis autem et bonis ab iis animalibus, quae nondum depravata sint, ait optime iudicari.</li><li>Ex eorum enim scriptis et institutis cum omnis doctrina liberalis, omnis historia.</li><li>Hoc mihi cum tuo fratre convenit.</li><li>Negat enim summo bono afferre incrementum diem.</li><li>Omnia contraria, quos etiam insanos esse vultis.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ex quo intellegitur officium medium quiddam esse, quod neque in bonis ponatur neque in contrariis. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Dat enim intervalla et relaxat. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tu enim ista lenius, hic Stoicorum more nos vexat.</a> Hoc non est positum in nostra actione. Qui non moveatur et offensione turpitudinis et comprobatione honestatis? <em>Contineo me ab exemplis.</em> Si mala non sunt, iacet omnis ratio Peripateticorum. Nam quid possumus facere melius? Quod autem ratione actum est, id officium appellamus. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Nunc omni virtuti vitium contrario nomine opponitur.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><em>Nam quid possumus facere melius?</em> At multis se probavit. Illud non continuo, ut aeque incontentae. Oratio me istius philosophi non offendit; Quae in controversiam veniunt, de iis, si placet, disseramus. Haec bene dicuntur, nec ego repugno, sed inter sese ipsa pugnant. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quam ob rem turpe putandum est, non dico dolere-nam id quidem est interdum necesse-, sed saxum illud Lemnium clamore Philocteteo funestare, Quod eiulatu, questu, gemitu, fremitibus Resonando mutum flebiles voces refert. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Et quae per vim oblatum stuprum volontaria morte lueret inventa est et qui interficeret filiam, ne stupraretur. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Non est ista, inquam, Piso, magna dissensio. Te autem hortamur omnes, currentem quidem, ut spero, ut eos, quos novisse vis, imitari etiam velis. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. Aliter homines, aliter philosophos loqui putas oportere? Ita multo sanguine profuso in laetitia et in victoria est mortuus. Primum in nostrane potestate est, quid meminerimus? Moriatur, inquit. Non enim, si malum est dolor, carere eo malo satis est ad bene vivendum. Verba tu fingas et ea dicas, quae non sentias? Sic, et quidem diligentius saepiusque ista loquemur inter nos agemusque communiter. <em>Sed fortuna fortis;</em> Quae quidem sapientes sequuntur duce natura tamquam videntes; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quonam modo?</a> Itaque eos id agere, ut a se dolores, morbos, debilitates repellant. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Nam Pyrrho, Aristo, Erillus iam diu abiecti.</a> Est, ut dicis, inquam. Ne in odium veniam, si amicum destitero tueri. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Bonum incolumis acies: misera caecitas.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere. Vulgo enim dicitur: Iucundi acti labores, nec male Euripidesconcludam, si potero, Latine; Isto modo ne improbos quidem, si essent boni viri. Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Mihi, inquam, qui te id ipsum rogavi? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Expressa vero in iis aetatibus, quae iam confirmatae sunt.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cum ageremus, inquit, vitae beatum et eundem supremum diem, scribebamus haec. Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari. Atque his de rebus et splendida est eorum et illustris oratio. Tamen aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si ista mala sunt, placet. Istic sum, inquit. Quid affers, cur Thorius, cur Caius Postumius, cur omnium horum magister, Orata, non iucundissime vixerit? <em>Haec dicuntur inconstantissime.</em> Quis est tam dissimile homini. Non autem hoc: igitur ne illud quidem. Atqui eorum nihil est eius generis, ut sit in fine atque extrerno bonorum. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ut id aliis narrare gestiant? Octavio fuit, cum illam severitatem in eo filio adhibuit, quem in adoptionem D. Quod non faceret, si in voluptate summum bonum poneret. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nam si beatus umquam fuisset, beatam vitam usque ad illum a Cyro extructum rogum pertulisset. Si est nihil nisi corpus, summa erunt illa: valitudo, vacuitas doloris, pulchritudo, cetera. Num igitur utiliorem tibi hunc Triarium putas esse posse, quam si tua sint Puteolis granaria? Unum est sine dolore esse, alterum cum voluptate. Quid affers, cur Thorius, cur Caius Postumius, cur omnium horum magister, Orata, non iucundissime vixerit? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur id non ita fit?</a> Tanti autem aderant vesicae et torminum morbi, ut nihil ad eorum magnitudinem posset accedere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Non autem hoc: igitur ne illud quidem. Qui non moveatur et offensione turpitudinis et comprobatione honestatis? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Dici enim nihil potest verius.</a> At miser, si in flagitiosa et vitiosa vita afflueret voluptatibus. Tum ille: Ain tandem? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Simus igitur contenti his.</a> Easdemne res? Sine ea igitur iucunde negat posse se vivere? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Et quidem, inquit, vehementer errat; Quid enim necesse est, tamquam meretricem in matronarum coetum, sic voluptatem in virtutum concilium adducere? Quid autem habent admirationis, cum prope accesseris? Res enim fortasse verae, certe graves, non ita tractantur, ut debent, sed aliquanto minutius. Non minor, inquit, voluptas percipitur ex vilissimis rebus quam ex pretiosissimis. Quod cum dixissent, ille contra. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Ipse negat, ut ante dixi, luxuriosorum vitam reprehendendam, nisi plane fatui sint, id est nisi aut cupiant aut metuant. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":5} --> +<h5>Equidem, sed audistine modo de Carneade?</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere. <em>Egone quaeris, inquit, quid sentiam?</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sed residamus, inquit, si placet.</a> <strong>Quae cum essent dicta, discessimus.</strong> An vero, inquit, quisquam potest probare, quod perceptfum, quod. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At hoc in eo M.</a> Bonum incolumis acies: misera caecitas. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur id non ita fit?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Scrupulum, inquam, abeunti;</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quippe: habes enim a rhetoribus;</a> Aliter enim nosmet ipsos nosse non possumus. Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum sit. An hoc usque quaque, aliter in vita? Non dolere, inquam, istud quam vim habeat postea videro; Itaque fecimus. Quamquam non negatis nos intellegere quid sit voluptas, sed quid ille dicat. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Homines optimi non intellegunt totam rationem everti, si ita res se habeat.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quippe: habes enim a rhetoribus; Vitiosum est enim in dividendo partem in genere numerare. Omnia contraria, quos etiam insanos esse vultis. <em>Compensabatur, inquit, cum summis doloribus laetitia.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod cum dixissent, ille contra.</a> Dolere malum est: in crucem qui agitur, beatus esse non potest. Quis istud possit, inquit, negare? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid de Platone aut de Democrito loquar? <em>Tria genera bonorum;</em> Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? Equidem soleo etiam quod uno Graeci, si aliter non possum, idem pluribus verbis exponere. <strong>Est, ut dicis, inquit;</strong> Post enim Chrysippum eum non sane est disputatum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Negabat igitur ullam esse artem, quae ipsa a se proficisceretur;</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Mihi quidem Antiochum, quem audis, satis belle videris attendere. Haec igitur Epicuri non probo, inquam. Ut nemo dubitet, eorum omnia officia quo spectare, quid sequi, quid fugere debeant? Ergo hoc quidem apparet, nos ad agendum esse natos. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Illud non continuo, ut aeque incontentae.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ea possunt paria non esse.</a> <em>Ita multa dicunt, quae vix intellegam.</em> Nunc omni virtuti vitium contrario nomine opponitur. Quantum Aristoxeni ingenium consumptum videmus in musicis? Ergo instituto veterum, quo etiam Stoici utuntur, hinc capiamus exordium. Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? <strong>Idemne, quod iucunde?</strong> Egone quaeris, inquit, quid sentiam? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quantum Aristoxeni ingenium consumptum videmus in musicis? Quid, quod res alia tota est? <em>Sed ne, dum huic obsequor, vobis molestus sim.</em> Transfer idem ad modestiam vel temperantiam, quae est moderatio cupiditatum rationi oboediens. Nescio quo modo praetervolavit oratio. Isto modo ne improbos quidem, si essent boni viri. Vide, ne etiam menses! nisi forte eum dicis, qui, simul atque arripuit, interficit. <strong>Id enim natura desiderat.</strong> Nulla profecto est, quin suam vim retineat a primo ad extremum. Quae quidem sapientes sequuntur duce natura tamquam videntes; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Idemque diviserunt naturam hominis in animum et corpus.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod autem ratione actum est, id officium appellamus.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Dicimus aliquem hilare vivere;</em> Quia dolori non voluptas contraria est, sed doloris privatio. Semper enim ex eo, quod maximas partes continet latissimeque funditur, tota res appellatur. Graece donan, Latine voluptatem vocant. Hoc enim constituto in philosophia constituta sunt omnia. Tu autem inter haec tantam multitudinem hominum interiectam non vides nec laetantium nec dolentium? Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur, nisi quod turpis oratio est?</a> Quare ad ea primum, si videtur; Quod autem principium officii quaerunt, melius quam Pyrrho; Dat enim intervalla et relaxat. Solum praeterea formosum, solum liberum, solum civem, stultost; Quod si ita se habeat, non possit beatam praestare vitam sapientia. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Id autem eius modi est, ut additum ad virtutem auctoritatem videatur habiturum et expleturum cumulate vitam beatam, de quo omnis haec quaestio est. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Suam denique cuique naturam esse ad vivendum ducem. Igitur neque stultorum quisquam beatus neque sapientium non beatus. <strong>Et quidem, inquit, vehementer errat;</strong> Sed finge non solum callidum eum, qui aliquid improbe faciat, verum etiam praepotentem, ut M. Nunc de hominis summo bono quaeritur; Atqui reperies, inquit, in hoc quidem pertinacem; Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? Ut necesse sit omnium rerum, quae natura vigeant, similem esse finem, non eundem. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non risu potius quam oratione eiciendum?</a> Bestiarum vero nullum iudicium puto. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Nec vero sum nescius esse utilitatem in historia, non modo voluptatem. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cur haec eadem Democritus? Mihi, inquam, qui te id ipsum rogavi? Semper enim ex eo, quod maximas partes continet latissimeque funditur, tota res appellatur. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Ego vero isti, inquam, permitto.</a> Quis istud possit, inquit, negare? <em>Quam illa ardentis amores excitaret sui! Cur tandem?</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Itaque vides, quo modo loquantur, nova verba fingunt, deserunt usitata.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quo studio cum satiari non possint, omnium ceterarum rerum obliti níhil abiectum, nihil humile cogitant; Sin kakan malitiam dixisses, ad aliud nos unum certum vitium consuetudo Latina traduceret. Nam et a te perfici istam disputationem volo, nec tua mihi oratio longa videri potest. Sapientem locupletat ipsa natura, cuius divitias Epicurus parabiles esse docuit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tum mihi Piso: Quid ergo?</a> Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur. <strong>Vide, quantum, inquam, fallare, Torquate.</strong> Est, ut dicis, inquam. <strong>Maximus dolor, inquit, brevis est.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Non modo carum sibi quemque, verum etiam vehementer carum esse?</li><li>Et quoniam haec deducuntur de corpore quid est cur non recte pulchritudo etiam ipsa propter se expetenda ducatur?</li><li>Cum autem venissemus in Academiae non sine causa nobilitata spatia, solitudo erat ea, quam volueramus.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Itaque omnis honos, omnis admiratio, omne studium ad virtutem et ad eas actiones, quae virtuti sunt consentaneae, refertur, eaque omnia, quae aut ita in animis sunt aut ita geruntur, uno nomine honesta dicuntur. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Polemoni et iam ante Aristoteli ea prima visa sunt, quae paulo ante dixi. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tum Quintus: Est plane, Piso, ut dicis, inquit.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Igitur ne dolorem quidem.</a> Ut in geometria, prima si dederis, danda sunt omnia. Luxuriam non reprehendit, modo sit vacua infinita cupiditate et timore. Nec enim, dum metuit, iustus est, et certe, si metuere destiterit, non erit; An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Quis est tam dissimile homini.</em> Hoc loco discipulos quaerere videtur, ut, qui asoti esse velint, philosophi ante fiant. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Quid nunc honeste dicit? Quam tu ponis in verbis, ego positam in re putabam. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Oratio me istius philosophi non offendit; <em>Hic ambiguo ludimur.</em> Inde sermone vario sex illa a Dipylo stadia confecimus. Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Nummus in Croesi divitiis obscuratur, pars est tamen divitiarum. Neutrum vero, inquit ille. Sed ad haec, nisi molestum est, habeo quae velim. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Deinde disputat, quod cuiusque generis animantium statui deceat extremum. Memini me adesse P. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; <strong>Isto modo ne improbos quidem, si essent boni viri.</strong> His singulis copiose responderi solet, sed quae perspicua sunt longa esse non debent. <strong>Scrupulum, inquam, abeunti;</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Qui enim voluptatem ipsam contemnunt, iis licet dicere se acupenserem maenae non anteponere.</h4> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Nec enim, dum metuit, iustus est, et certe, si metuere destiterit, non erit; Partim cursu et peragratione laetantur, congregatione aliae coetum quodam modo civitatis imitantur; Ergo et avarus erit, sed finite, et adulter, verum habebit modum, et luxuriosus eodem modo. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid enim?</a> <strong>Eam tum adesse, cum dolor omnis absit;</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nihil enim iam habes, quod ad corpus referas; <em>Id est enim, de quo quaerimus.</em> <em>Quid autem habent admirationis, cum prope accesseris?</em> Si mala non sunt, iacet omnis ratio Peripateticorum. Semovenda est igitur voluptas, non solum ut recta sequamini, sed etiam ut loqui deceat frugaliter. Sextilio Rufo, cum is rem ad amicos ita deferret, se esse heredem Q. An eum discere ea mavis, quae cum plane perdidiceriti nihil sciat? Nihilo magis. Sed emolumenta communia esse dicuntur, recte autem facta et peccata non habentur communia. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quo modo autem optimum, si bonum praeterea nullum est?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Duae sunt enim res quoque, ne tu verba solum putes. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Haec dicuntur inconstantissime.</a> At ille pellit, qui permulcet sensum voluptate. Quod quidem iam fit etiam in Academia. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Poterat autem inpune;</a> Eaedem enim utilitates poterunt eas labefactare atque pervertere. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Lege laudationes, Torquate, non eorum, qui sunt ab Homero laudati, non Cyri, non Agesilai, non Aristidi aut Themistocli, non Philippi aut Alexandri, lege nostrorum hominum, lege vestrae familiae; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Hunc vos beatum; Callipho ad virtutem nihil adiunxit nisi voluptatem, Diodorus vacuitatem doloris. Ergo hoc quidem apparet, nos ad agendum esse natos. Hoc est non modo cor non habere, sed ne palatum quidem. Ipse Epicurus fortasse redderet, ut Sextus Peducaeus, Sex. Qui-vere falsone, quaerere mittimus-dicitur oculis se privasse; Cum autem negant ea quicquam ad beatam vitam pertinere, rursus naturam relinquunt. Bonum negas esse divitias, praeposìtum esse dicis? </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + An potest, inquit ille, quicquam esse suavius quam nihil dolere? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Tu quidem reddes; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Efficiens dici potest.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Et quidem, inquit, vehementer errat;</a> Hoc sic expositum dissimile est superiori. Hic ego: Pomponius quidem, inquam, noster iocari videtur, et fortasse suo iure. A primo, ut opinor, animantium ortu petitur origo summi boni. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>At ille non pertimuit saneque fidenter: Istis quidem ipsis verbis, inquit;</li><li>Nulla profecto est, quin suam vim retineat a primo ad extremum.</li><li>Pisone in eo gymnasio, quod Ptolomaeum vocatur, unaque nobiscum Q.</li><li>Apud ceteros autem philosophos, qui quaesivit aliquid, tacet;</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Sextilio Rufo, cum is rem ad amicos ita deferret, se esse heredem Q.</li><li>Ergo id est convenienter naturae vivere, a natura discedere.</li><li>Dici enim nihil potest verius.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p><strong>Putabam equidem satis, inquit, me dixisse.</strong> Et quidem Arcesilas tuus, etsi fuit in disserendo pertinacior, tamen noster fuit; Audax negotium, dicerem impudens, nisi hoc institutum postea translatum ad philosophos nostros esset. Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. <em>Equidem, sed audistine modo de Carneade?</em> Nam ante Aristippus, et ille melius. <strong>Sed tamen intellego quid velit.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Ex ea difficultate illae fallaciloquae, ut ait Accius, malitiae natae sunt.</li><li>Sit hoc ultimum bonorum, quod nunc a me defenditur;</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Quae cum magnifice primo dici viderentur, considerata minus probabantur.</li><li>Satisne igitur videor vim verborum tenere, an sum etiam nunc vel Graece loqui vel Latine docendus?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Quid affers, cur Thorius, cur Caius Postumius, cur omnium horum magister, Orata, non iucundissime vixerit? Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur? In his igitur partibus duabus nihil erat, quod Zeno commutare gestiret. Equidem soleo etiam quod uno Graeci, si aliter non possum, idem pluribus verbis exponere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Nos cum te, M.</em> Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt. <strong>Esse enim, nisi eris, non potes.</strong> <strong>At hoc in eo M.</strong> Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt. An ea, quae per vinitorem antea consequebatur, per se ipsa curabit? Virtutibus igitur rectissime mihi videris et ad consuetudinem nostrae orationis vitia posuisse contraria. Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit; </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Itaque nostrum est-quod nostrum dico, artis est-ad ea principia, quae accepimus.</li><li>Quamquam haec quidem praeposita recte et reiecta dicere licebit.</li><li>Aliter homines, aliter philosophos loqui putas oportere?</li><li>Habent enim et bene longam et satis litigiosam disputationem.</li><li>Sed non alienum est, quo facilius vis verbi intellegatur, rationem huius verbi faciendi Zenonis exponere.</li><li>Tum Quintus: Est plane, Piso, ut dicis, inquit.</li></ul> +<!-- /wp:list --> + +<!-- wp:list --> +<ul><li>Bonum integritas corporis: misera debilitas.</li><li>Tu quidem reddes;</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":6} --> +<h6>Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Laboro autem non sine causa;</strong> Quid ei reliquisti, nisi te, quoquo modo loqueretur, intellegere, quid diceret? Aperiendum est igitur, quid sit voluptas; <strong>Quid enim possumus hoc agere divinius?</strong> Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest. Quid loquor de nobis, qui ad laudem et ad decus nati, suscepti, instituti sumus? <em>Pauca mutat vel plura sane;</em> Intellegi quidem, ut propter aliam quampiam rem, verbi gratia propter voluptatem, nos amemus; </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Huic ego, si negaret quicquam interesse ad beate vivendum quali uteretur victu, concederem, laudarem etiam;</li><li>Quod praeceptum quia maius erat, quam ut ab homine videretur, idcirco assignatum est deo.</li><li>Videmusne ut pueri ne verberibus quidem a contemplandis rebus perquirendisque deterreantur?</li><li>Scio enim esse quosdam, qui quavis lingua philosophari possint;</li><li>Qua ex cognitione facilior facta est investigatio rerum occultissimarum.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading --> +<h2>Mihi enim satis est, ipsis non satis.</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quare conare, quaeso. Qui autem de summo bono dissentit de tota philosophiae ratione dissentit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si longus, levis.</a> Nam et complectitur verbis, quod vult, et dicit plane, quod intellegam; Cupiditates non Epicuri divisione finiebat, sed sua satietate. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Si verbum sequimur, primum longius verbum praepositum quam bonum.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Idemne, quod iucunde? Sin kakan malitiam dixisses, ad aliud nos unum certum vitium consuetudo Latina traduceret. Ac tamen hic mallet non dolere. Praeclare hoc quidem. Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Quicquid enim a sapientia proficiscitur, id continuo debet expletum esse omnibus suis partibus; <em>Sed ad rem redeamus;</em> Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum sit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Restincta enim sitis stabilitatem voluptatis habet, inquit, illa autem voluptas ipsius restinctionis in motu est. Illis videtur, qui illud non dubitant bonum dicere -; Tum Torquatus: Prorsus, inquit, assentior; Quis enim confidit semper sibi illud stabile et firmum permansurum, quod fragile et caducum sit? Vitae autem degendae ratio maxime quidem illis placuit quieta. Quantum Aristoxeni ingenium consumptum videmus in musicis? </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Rationis enim perfectio est virtus;</a> Sed tu istuc dixti bene Latine, parum plane. At coluit ipse amicitias. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Illa videamus, quae a te de amicitia dicta sunt.</a> <strong>Tenent mordicus.</strong> Quamquam tu hanc copiosiorem etiam soles dicere. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Respondent extrema primis, media utrisque, omnia omnibus.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Istam voluptatem perpetuam quis potest praestare sapienti? Nam illud vehementer repugnat, eundem beatum esse et multis malis oppressum. <em>Bonum incolumis acies: misera caecitas.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quae duo sunt, unum facit.</a> Conferam avum tuum Drusum cum C. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Primum divisit ineleganter;</em> Sullae consulatum? Nam memini etiam quae nolo, oblivisci non possum quae volo. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. Ex quo, id quod omnes expetunt, beate vivendi ratio inveniri et comparari potest. In his igitur partibus duabus nihil erat, quod Zeno commutare gestiret. Sed residamus, inquit, si placet. Vitae autem degendae ratio maxime quidem illis placuit quieta. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Bona autem corporis huic sunt, quod posterius posui, similiora.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ergo instituto veterum, quo etiam Stoici utuntur, hinc capiamus exordium. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Et harum quidem rerum facilis est et expedita distinctio.</a> Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? Tu autem negas fortem esse quemquam posse, qui dolorem malum putet. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Atqui eorum nihil est eius generis, ut sit in fine atque extrerno bonorum. In quibus doctissimi illi veteres inesse quiddam caeleste et divinum putaverunt. Deinde prima illa, quae in congressu solemus: Quid tu, inquit, huc? Quod cum ille dixisset et satis disputatum videretur, in oppidum ad Pomponium perreximus omnes. Ut placet, inquit, etsi enim illud erat aptius, aequum cuique concedere. Nec vero sum nescius esse utilitatem in historia, non modo voluptatem. <em>Tum ille timide vel potius verecunde: Facio, inquit.</em> <strong>Idem iste, inquam, de voluptate quid sentit?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Sed erat aequius Triarium aliquid de dissensione nostra iudicare.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Negat enim summo bono afferre incrementum diem. Habent enim et bene longam et satis litigiosam disputationem. Aut haec tibi, Torquate, sunt vituperanda aut patrocinium voluptatis repudiandum. Ut proverbia non nulla veriora sint quam vestra dogmata. Fortitudinis quaedam praecepta sunt ac paene leges, quae effeminari virum vetant in dolore. Eam tum adesse, cum dolor omnis absit; Estne, quaeso, inquam, sitienti in bibendo voluptas? Negat esse eam, inquit, propter se expetendam. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Etenim semper illud extra est, quod arte comprehenditur. Quid ei reliquisti, nisi te, quoquo modo loqueretur, intellegere, quid diceret? Experiamur igitur, inquit, etsi habet haec Stoicorum ratio difficilius quiddam et obscurius. Virtutis, magnitudinis animi, patientiae, fortitudinis fomentis dolor mitigari solet. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Sed quid ages tandem, si utilitas ab amicitia, ut fit saepe, defecerit?</li><li>Sed nunc, quod agimus;</li><li>Beatus autem esse in maximarum rerum timore nemo potest.</li><li>An ea, quae per vinitorem antea consequebatur, per se ipsa curabit?</li><li>Sedulo, inquam, faciam.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Morbo gravissimo affectus, exul, orbus, egens, torqueatur eculeo: quem hunc appellas, Zeno? Ergo instituto veterum, quo etiam Stoici utuntur, hinc capiamus exordium. Restinguet citius, si ardentem acceperit. Hanc ergo intuens debet institutum illud quasi signum absolvere. <strong>Primum Theophrasti, Strato, physicum se voluit;</strong> Est enim effectrix multarum et magnarum voluptatum. Summus dolor plures dies manere non potest? Sed ille, ut dixi, vitiose. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid ergo attinet gloriose loqui, nisi constanter loquare? Ut id aliis narrare gestiant? Inde sermone vario sex illa a Dipylo stadia confecimus. Graecis hoc modicum est: Leonidas, Epaminondas, tres aliqui aut quattuor; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Non enim iam stirpis bonum quaeret, sed animalis.</a> Rationis enim perfectio est virtus; Ita graviter et severe voluptatem secrevit a bono. <em>Ostendit pedes et pectus.</em> At modo dixeras nihil in istis rebus esse, quod interesset. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Praeclarae mortes sunt imperatoriae;</a> Eam stabilem appellas. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt.</li><li>Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis;</li><li>Tamen aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si ista mala sunt, placet.</li><li>Sit hoc ultimum bonorum, quod nunc a me defenditur;</li><li>Sed quid attinet de rebus tam apertis plura requirere?</li><li>Nam si propter voluptatem, quae est ista laus, quae possit e macello peti?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Verba tu fingas et ea dicas, quae non sentias? Praeclare hoc quidem. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid ait Aristoteles reliquique Platonis alumni?</a> Iam id ipsum absurdum, maximum malum neglegi. Sed tamen omne, quod de re bona dilucide dicitur, mihi praeclare dici videtur. Urgent tamen et nihil remittunt. Miserum hominem! Si dolor summum malum est, dici aliter non potest. Summum ením bonum exposuit vacuitatem doloris; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Nam, ut sint illa vendibiliora, haec uberiora certe sunt. Transfer idem ad modestiam vel temperantiam, quae est moderatio cupiditatum rationi oboediens. Ergo id est convenienter naturae vivere, a natura discedere. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Profectus in exilium Tubulus statim nec respondere ausus;</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Sed tu istuc dixti bene Latine, parum plane. Quae similitudo in genere etiam humano apparet. Semovenda est igitur voluptas, non solum ut recta sequamini, sed etiam ut loqui deceat frugaliter. An est aliquid per se ipsum flagitiosum, etiamsi nulla comitetur infamia? Quae in controversiam veniunt, de iis, si placet, disseramus. <em>Quid iudicant sensus?</em> <em>Quonam, inquit, modo?</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Cur post Tarentum ad Archytam?</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Scaevolam M. Prodest, inquit, mihi eo esse animo. <em>Est enim effectrix multarum et magnarum voluptatum.</em> <strong>Aliter enim explicari, quod quaeritur, non potest.</strong> De quibus cupio scire quid sentias. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Laboro autem non sine causa; Quis non odit sordidos, vanos, leves, futtiles? Sedulo, inquam, faciam. Summum a vobis bonum voluptas dicitur. Qui igitur convenit ab alia voluptate dicere naturam proficisci, in alia summum bonum ponere? <em>Illi enim inter se dissentiunt.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qui ita affectus, beatum esse numquam probabis;</a> <strong>Idemque diviserunt naturam hominis in animum et corpus.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Quae cum essent dicta, discessimus.</em> Laboro autem non sine causa; Duo enim genera quae erant, fecit tria. Hoc loco tenere se Triarius non potuit. <em>Sed residamus, inquit, si placet.</em> Falli igitur possumus. <em>Torquatus, is qui consul cum Cn.</em> Sit hoc ultimum bonorum, quod nunc a me defenditur; Ne discipulum abducam, times. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Prodest, inquit, mihi eo esse animo.</a> Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quid ei reliquisti, nisi te, quoquo modo loqueretur, intellegere, quid diceret?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Illud non continuo, ut aeque incontentae. Respondent extrema primis, media utrisque, omnia omnibus. Si mala non sunt, iacet omnis ratio Peripateticorum. <strong>Inde igitur, inquit, ordiendum est.</strong> Nonne igitur tibi videntur, inquit, mala? Quam ob rem tandem, inquit, non satisfacit? Est tamen ea secundum naturam multoque nos ad se expetendam magis hortatur quam superiora omnia. Itaque hic ipse iam pridem est reiectus; </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Minime vero, inquit ille, consentit.</li><li>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari.</li><li>At coluit ipse amicitias.</li><li>Ex quo illud efficitur, qui bene cenent omnis libenter cenare, qui libenter, non continuo bene.</li><li>Diodorus, eius auditor, adiungit ad honestatem vacuitatem doloris.</li></ul> +<!-- /wp:list --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:heading {"level":5} --> +<h5>Quamquam te quidem video minime esse deterritum.</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>At Zeno eum non beatum modo, sed etiam divitem dicere ausus est. <strong>Quid de Pythagora?</strong> Non enim quaero quid verum, sed quid cuique dicendum sit. <strong>Sint ista Graecorum;</strong> Non ego tecum iam ita iocabor, ut isdem his de rebus, cum L. Cupit enim dícere nihil posse ad beatam vitam deesse sapienti. Quodsi ipsam honestatem undique pertectam atque absolutam. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod cum dixissent, ille contra. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Eadem nunc mea adversum te oratio est. Oratio me istius philosophi non offendit; Totum genus hoc Zeno et qui ab eo sunt aut non potuerunt aut noluerunt, certe reliquerunt. Quacumque enim ingredimur, in aliqua historia vestigium ponimus. Expressa vero in iis aetatibus, quae iam confirmatae sunt. Ab his oratores, ab his imperatores ac rerum publicarum principes extiterunt. Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? Quid igitur dubitamus in tota eius natura quaerere quid sit effectum? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Que Manilium, ab iisque M.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At multis malis affectus.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Traditur, inquit, ab Epicuro ratio neglegendi doloris.</li><li>Sed tamen omne, quod de re bona dilucide dicitur, mihi praeclare dici videtur.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading --> +<h2>Primum in nostrane potestate est, quid meminerimus?</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Et ille ridens: Video, inquit, quid agas; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si longus, levis dictata sunt.</a> Hoc loco tenere se Triarius non potuit. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quid Zeno?</a> Nam et a te perfici istam disputationem volo, nec tua mihi oratio longa videri potest. <em>Estne, quaeso, inquam, sitienti in bibendo voluptas?</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Videamus animi partes, quarum est conspectus illustrior;</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Summum ením bonum exposuit vacuitatem doloris; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">At cum de plurimis eadem dicit, tum certe de maximis.</a> <em>Maximus dolor, inquit, brevis est.</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Qua tu etiam inprudens utebare non numquam.</a> Universa enim illorum ratione cum tota vestra confligendum puto. Multa sunt dicta ab antiquis de contemnendis ac despiciendis rebus humanis; In omni enim arte vel studio vel quavis scientia vel in ipsa virtute optimum quidque rarissimum est. Maximas vero virtutes iacere omnis necesse est voluptate dominante. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. Si stante, hoc natura videlicet vult, salvam esse se, quod concedimus; Ut in voluptate sit, qui epuletur, in dolore, qui torqueatur. Eaedem res maneant alio modo. Itaque si aut requietem natura non quaereret aut eam posset alia quadam ratione consequi. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>In eo enim positum est id, quod dicimus esse expetendum.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Multoque hoc melius nos veriusque quam Stoici. <em>Negat esse eam, inquit, propter se expetendam.</em> Sed quanta sit alias, nunc tantum possitne esse tanta. Idemne potest esse dies saepius, qui semel fuit? At Zeno eum non beatum modo, sed etiam divitem dicere ausus est. <em>Nemo nostrum istius generis asotos iucunde putat vivere.</em> Deinde disputat, quod cuiusque generis animantium statui deceat extremum. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Efficiens dici potest.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Num igitur eum postea censes anxio animo aut sollicito fuisse? Nec vero pietas adversus deos nec quanta iis gratia debeatur sine explicatione naturae intellegi potest. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quod ea non occurrentia fingunt, vincunt Aristonem;</a> <em>Tecum optime, deinde etiam cum mediocri amico.</em> Sed tamen enitar et, si minus multa mihi occurrent, non fugiam ista popularia. Isto modo ne improbos quidem, si essent boni viri. Non semper, inquam; Quid interest, nisi quod ego res notas notis verbis appello, illi nomina nova quaerunt, quibus idem dicant? Tecum optime, deinde etiam cum mediocri amico. Ergo in gubernando nihil, in officio plurimum interest, quo in genere peccetur. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Immo istud quidem, inquam, quo loco quidque, nisi iniquum postulo, arbitratu meo.</li><li>Cur ipse Pythagoras et Aegyptum lustravit et Persarum magos adiit?</li><li>Ergo illi intellegunt quid Epicurus dicat, ego non intellego?</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Sed in rebus apertissimis nimium longi sumus. Collige omnia, quae soletis: Praesidium amicorum. At ego quem huic anteponam non audeo dicere; Sed hoc sane concedamus. At iam decimum annum in spelunca iacet. Fatebuntur Stoici haec omnia dicta esse praeclare, neque eam causam Zenoni desciscendi fuisse. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Vide, quantum, inquam, fallare, Torquate.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>Quae duo sunt, unum facit.</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quid sequatur, quid repugnet, vident. Tu autem, si tibi illa probabantur, cur non propriis verbis ea tenebas? Piso, familiaris noster, et alia multa et hoc loco Stoicos irridebat: Quid enim? <em>Cur deinde Metrodori liberos commendas?</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quod et posse fieri intellegimus et saepe etiam videmus, et perspicuum est nihil ad iucunde vivendum reperiri posse, quod coniunctione tali sit aptius. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Tu autem, si tibi illa probabantur, cur non propriis verbis ea tenebas? Portenta haec esse dicit, neque ea ratione ullo modo posse vivi; Sed hoc sane concedamus. Si alia sentit, inquam, alia loquitur, numquam intellegam quid sentiat; Vestri haec verecundius, illi fortasse constantius. Nam aliquando posse recte fieri dicunt nulla expectata nec quaesita voluptate. A quibus propter discendi cupiditatem videmus ultimas terras esse peragratas. Quid ergo hoc loco intellegit honestum? Propter nos enim illam, non propter eam nosmet ipsos diligimus. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Faceres tu quidem, Torquate, haec omnia;</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Neque enim civitas in seditione beata esse potest nec in discordia dominorum domus; Si autem id non concedatur, non continuo vita beata tollitur. Nam Pyrrho, Aristo, Erillus iam diu abiecti. Aliter enim nosmet ipsos nosse non possumus. Idem iste, inquam, de voluptate quid sentit? Eam tum adesse, cum dolor omnis absit; <em>Iam in altera philosophiae parte.</em> At multis malis affectus. Quae cum ita sint, effectum est nihil esse malum, quod turpe non sit. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Haec dicuntur inconstantissime. Nos paucis ad haec additis finem faciamus aliquando; Ut in geometria, prima si dederis, danda sunt omnia. At multis se probavit. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. <em>Itaque his sapiens semper vacabit.</em> Satis est ad hoc responsum. Ita enim vivunt quidam, ut eorum vita refellatur oratio. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quid censes in Latino fore? Superiores tres erant, quae esse possent, quarum est una sola defensa, eaque vehementer. Ego vero volo in virtute vim esse quam maximam; Neque solum ea communia, verum etiam paria esse dixerunt. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Et ais, si una littera commota sit, fore tota ut labet disciplina.</li><li>Mihi enim satis est, ipsis non satis.</li><li>Sic vester sapiens magno aliquo emolumento commotus cicuta, si opus erit, dimicabit.</li><li>Satisne igitur videor vim verborum tenere, an sum etiam nunc vel Graece loqui vel Latine docendus?</li><li>Tum Torquatus: Prorsus, inquit, assentior;</li><li>Sed quoniam et advesperascit et mihi ad villam revertendum est, nunc quidem hactenus;</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Nam ista vestra: Si gravis, brevis; Hic Speusippus, hic Xenocrates, hic eius auditor Polemo, cuius illa ipsa sessio fuit, quam videmus. Laboro autem non sine causa; Recte, inquit, intellegis. Quare obscurentur etiam haec, quae secundum naturam esse dicimus, in vita beata; </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quibus autem in rebus tanta obscuratio non fit, fieri tamen potest, ut id ipsum, quod interest, non sit magnum. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quamquam tu hanc copiosiorem etiam soles dicere.</a> Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis. Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. Eadem fortitudinis ratio reperietur. Age, inquies, ista parva sunt. Rationis enim perfectio est virtus; Etsi ea quidem, quae adhuc dixisti, quamvis ad aetatem recte isto modo dicerentur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Ergo id est convenienter naturae vivere, a natura discedere. Si de re disceptari oportet, nulla mihi tecum, Cato, potest esse dissensio. Quod si ita sit, cur opera philosophiae sit danda nescio. Conferam avum tuum Drusum cum C. Unum nescio, quo modo possit, si luxuriosus sit, finitas cupiditates habere. <strong>Facile est hoc cernere in primis puerorum aetatulis.</strong> Mihi quidem Antiochum, quem audis, satis belle videris attendere. Sed haec quidem liberius ab eo dicuntur et saepius. Sed non alienum est, quo facilius vis verbi intellegatur, rationem huius verbi faciendi Zenonis exponere. Duo enim genera quae erant, fecit tria. <em>Sit sane ista voluptas.</em> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quo tandem modo?</a> Quos quidem tibi studiose et diligenter tractandos magnopere censeo. Eam tum adesse, cum dolor omnis absit; Sed tamen est aliquid, quod nobis non liceat, liceat illis. Quid est, quod ab ea absolvi et perfici debeat? Videamus animi partes, quarum est conspectus illustrior; Cui Tubuli nomen odio non est? <strong>Non est igitur summum malum dolor.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Sed quid ages tandem, si utilitas ab amicitia, ut fit saepe, defecerit?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Quod cum accidisset ut alter alterum necopinato videremus, surrexit statim. Ut non sine causa ex iis memoriae ducta sit disciplina. Satis est tibi in te, satis in legibus, satis in mediocribus amicitiis praesidii. Quo plebiscito decreta a senatu est consuli quaestio Cn. In his igitur partibus duabus nihil erat, quod Zeno commutare gestiret. Negat esse eam, inquit, propter se expetendam. <strong>Haec para/doca illi, nos admirabilia dicamus.</strong> Itaque nostrum est-quod nostrum dico, artis est-ad ea principia, quae accepimus. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Aeque enim contingit omnibus fidibus, ut incontentae sint.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><strong>Si verbum sequimur, primum longius verbum praepositum quam bonum.</strong> Dat enim intervalla et relaxat. Sed tamen enitar et, si minus multa mihi occurrent, non fugiam ista popularia. <strong>Scrupulum, inquam, abeunti;</strong> Atque haec ita iustitiae propria sunt, ut sint virtutum reliquarum communia. Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis. Quid, de quo nulla dissensio est? Paulum, cum regem Persem captum adduceret, eodem flumine invectio? An nisi populari fama? Transfer idem ad modestiam vel temperantiam, quae est moderatio cupiditatum rationi oboediens. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Nec enim, dum metuit, iustus est, et certe, si metuere destiterit, non erit;</li><li>Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta.</li><li>Si enim ita est, vide ne facinus facias, cum mori suadeas.</li><li>Nec enim ignoras his istud honestum non summum modo, sed etiam, ut tu vis, solum bonum videri.</li><li>Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere.</li><li>Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Nam cui proposito sit conservatio sui, necesse est huic partes quoque sui caras suo genere laudabiles. Mihi quidem Antiochum, quem audis, satis belle videris attendere. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tamen a proposito, inquam, aberramus. <em>An hoc usque quaque, aliter in vita?</em> Roges enim Aristonem, bonane ei videantur haec: vacuitas doloris, divitiae, valitudo; Nemo igitur esse beatus potest. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Efficiens dici potest.</a> Ut scias me intellegere, primum idem esse dico voluptatem, quod ille don. Nihil illinc huc pervenit. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Pisone in eo gymnasio, quod Ptolomaeum vocatur, unaque nobiscum Q.</li><li>Non igitur bene.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading --> +<h2>Videsne quam sit magna dissensio?</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Quare conare, quaeso.</a> Ad corpus diceres pertinere-, sed ea, quae dixi, ad corpusne refers? <strong>Qualem igitur hominem natura inchoavit?</strong> Ait enim se, si uratur, Quam hoc suave! dicturum. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Cuius etiam illi hortuli propinqui non memoriam solum mihi afferunt, sed ipsum videntur in conspectu meo ponere. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":1} --> +<h1>Perge porro;</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Cur tantas regiones barbarorum pedibus obiit, tot maria transmisit? Tum Quintus: Est plane, Piso, ut dicis, inquit. Sextilio Rufo, cum is rem ad amicos ita deferret, se esse heredem Q. Etiam beatissimum? Certe nihil nisi quod possit ipsum propter se iure laudari. Bestiarum vero nullum iudicium puto. Dat enim intervalla et relaxat. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Restat locus huic disputationi vel maxime necessarius de amicitia, quam, si voluptas summum sit bonum, affirmatis nullam omnino fore. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:heading {"level":3} --> +<h3>Quid dubitas igitur mutare principia naturae?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Torquatus, is qui consul cum Cn. Miserum hominem! Si dolor summum malum est, dici aliter non potest. Age sane, inquam. Ergo hoc quidem apparet, nos ad agendum esse natos. Qui potest igitur habitare in beata vita summi mali metus? Dolor ergo, id est summum malum, metuetur semper, etiamsi non aderit; <em>Certe non potest.</em> Graecis hoc modicum est: Leonidas, Epaminondas, tres aliqui aut quattuor; </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Illud non continuo, ut aeque incontentae.</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Non est enim vitium in oratione solum, sed etiam in moribus. An me, inquam, nisi te audire vellem, censes haec dicturum fuisse? Simus igitur contenti his. Multoque hoc melius nos veriusque quam Stoici. Proclivi currit oratio. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":5} --> +<h5>Quid enim tanto opus est instrumento in optimis artibus comparandis?</h5> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ne in odium veniam, si amicum destitero tueri. At quanta conantur! Mundum hunc omnem oppidum esse nostrum! Incendi igitur eos, qui audiunt, vides. <strong>Tu vero, inquam, ducas licet, si sequetur;</strong> Et nunc quidem quod eam tuetur, ut de vite potissimum loquar, est id extrinsecus; Illa argumenta propria videamus, cur omnia sint paria peccata. Illa videamus, quae a te de amicitia dicta sunt. Sed nimis multa. Non autem hoc: igitur ne illud quidem. <strong>Sed residamus, inquit, si placet.</strong> Nam si beatus umquam fuisset, beatam vitam usque ad illum a Cyro extructum rogum pertulisset. Illud dico, ea, quae dicat, praeclare inter se cohaerere. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quae possunt eadem contra Carneadeum illud summum bonum dici, quod is non tam, ut probaret, protulit, quam ut Stoicis, quibuscum bellum gerebat, opponeret. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Nihil sane. <em>At multis se probavit.</em> Sed est forma eius disciplinae, sicut fere ceterarum, triplex: una pars est naturae, disserendi altera, vivendi tertia. Potius inflammat, ut coercendi magis quam dedocendi esse videantur. <em>Nihil opus est exemplis hoc facere longius.</em> Minime id quidem, inquam, alienum, multumque ad ea, quae quaerimus, explicatio tua ista profecerit. Quibusnam praeteritis? <strong>Quae quidem sapientes sequuntur duce natura tamquam videntes;</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":6} --> +<h6>At quicum ioca seria, ut dicitur, quicum arcana, quicum occulta omnia?</h6> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hoc loco discipulos quaerere videtur, ut, qui asoti esse velint, philosophi ante fiant. <strong>Sint ista Graecorum;</strong> <strong>Si quae forte-possumus.</strong> Servari enim iustitia nisi a forti viro, nisi a sapiente non potest. <strong>Atqui reperies, inquit, in hoc quidem pertinacem;</strong> Piso, familiaris noster, et alia multa et hoc loco Stoicos irridebat: Quid enim? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Qua ex cognitione facilior facta est investigatio rerum occultissimarum.</li><li>Sed quae tandem ista ratio est?</li><li>Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster?</li><li>Nihil minus, contraque illa hereditate dives ob eamque rem laetus.</li></ul> +<!-- /wp:list --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quis enim tam inimicus paene nomini Romano est, qui Ennii Medeam aut Antiopam Pacuvii spernat aut reiciat, quod se isdem Euripidis fabulis delectari dicat, Latinas litteras oderit? +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Deinde qui fit, ut ego nesciam, sciant omnes, quicumque Epicurei esse voluerunt? Philosophi autem in suis lectulis plerumque moriuntur. Ergo adhuc, quantum equidem intellego, causa non videtur fuisse mutandi nominis. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Non quam nostram quidem, inquit Pomponius iocans; Ergo ita: non posse honeste vivi, nisi honeste vivatur? Neque enim disputari sine reprehensione nec cum iracundia aut pertinacia recte disputari potest. Quae similitudo in genere etiam humano apparet. </p> +<!-- /wp:paragraph --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Etsi dedit talem mentem, quae omnem virtutem accipere posset, ingenuitque sine doctrina notitias parvas rerum maximarum et quasi instituit docere et induxit in ea, quae inerant, tamquam elementa virtutis. +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">Equidem, sed audistine modo de Carneade?</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Si quicquam extra virtutem habeatur in bonis.</a> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Graece donan, Latine voluptatem vocant.</a> Quorum sine causa fieri nihil putandum est. Quis animo aequo videt eum, quem inpure ac flagitiose putet vivere? Unum est sine dolore esse, alterum cum voluptate. Intellegi quidem, ut propter aliam quampiam rem, verbi gratia propter voluptatem, nos amemus; Iam quae corporis sunt, ea nec auctoritatem cum animi partibus, comparandam et cognitionem habent faciliorem. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Gerendus est mos, modo recte sentiat.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Ergo instituto veterum, quo etiam Stoici utuntur, hinc capiamus exordium. Immo vero, inquit, ad beatissime vivendum parum est, ad beate vero satis. Si stante, hoc natura videlicet vult, salvam esse se, quod concedimus; Rationis enim perfectio est virtus; Ut pulsi recurrant? Inde sermone vario sex illa a Dipylo stadia confecimus. Quae quidem sapientes sequuntur duce natura tamquam videntes; Ne discipulum abducam, times. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Tollenda est atque extrahenda radicitus.</a> <em>Bonum incolumis acies: misera caecitas.</em> Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Illa sunt similia: hebes acies est cuipiam oculorum, corpore alius senescit;</li><li>Eodem modo is enim tibi nemo dabit, quod, expetendum sit, id esse laudabile.</li><li>Quarum ambarum rerum cum medicinam pollicetur, luxuriae licentiam pollicetur.</li></ul> +<!-- /wp:list --> + +<!-- wp:paragraph --> +<p>Quid turpius quam sapientis vitam ex insipientium sermone pendere? Quia dolori non voluptas contraria est, sed doloris privatio. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><em>Quis hoc dicit?</em> Quis est enim, in quo sit cupiditas, quin recte cupidus dici possit? Et harum quidem rerum facilis est et expedita distinctio. Dic in quovis conventu te omnia facere, ne doleas. <em>An nisi populari fama?</em> Idem iste, inquam, de voluptate quid sentit? </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":4} --> +<h4>Nos commodius agimus.</h4> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Hanc in motu voluptatem -sic enim has suaves et quasi dulces voluptates appellat-interdum ita extenuat, ut M. Itaque rursus eadem ratione, qua sum paulo ante usus, haerebitis. Nonne videmus quanta perturbatio rerum omnium consequatur, quanta confusio? Maximus dolor, inquit, brevis est. <strong>Deprehensus omnem poenam contemnet.</strong> <strong>Hoc Hieronymus summum bonum esse dixit.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Compensabatur, inquit, cum summis doloribus laetitia. Urgent tamen et nihil remittunt. <em>Tu vero, inquam, ducas licet, si sequetur;</em> <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Sedulo, inquam, faciam.</a> </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":1} --> +<h1>Quis enim confidit semper sibi illud stabile et firmum permansurum, quod fragile et caducum sit?</h1> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Audax negotium, dicerem impudens, nisi hoc institutum postea translatum ad philosophos nostros esset. Sin autem eos non probabat, quid attinuit cum iis, quibuscum re concinebat, verbis discrepare? Nam quibus rebus efficiuntur voluptates, eae non sunt in potestate sapientis. Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus; Conferam tecum, quam cuique verso rem subicias; </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Si quicquam extra virtutem habeatur in bonis. Qui bonum omne in virtute ponit, is potest dicere perfici beatam vitam perfectione virtutis; Vos autem cum perspicuis dubia debeatis illustrare, dubiis perspicua conamini tollere. His enim rebus detractis negat se reperire in asotorum vita quod reprehendat. Non quaero, quid dicat, sed quid convenienter possit rationi et sententiae suae dicere. <strong>Non enim iam stirpis bonum quaeret, sed animalis.</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><strong>Igitur neque stultorum quisquam beatus neque sapientium non beatus.</strong> Minime vero, inquit ille, consentit. Tum Quintus: Est plane, Piso, ut dicis, inquit. Quid autem habent admirationis, cum prope accesseris? <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Res enim concurrent contrariae.</a> Satisne vobis videor pro meo iure in vestris auribus commentatus? Videmus in quodam volucrium genere non nulla indicia pietatis, cognitionem, memoriam, in multis etiam desideria videmus. <strong>Quis istud possit, inquit, negare?</strong> </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Quae quidem sapientes sequuntur duce natura tamquam videntes; Ita enim vivunt quidam, ut eorum vita refellatur oratio. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">De hominibus dici non necesse est.</a> Eam tum adesse, cum dolor omnis absit; At enim sequor utilitatem. Quod autem principium officii quaerunt, melius quam Pyrrho; </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Quaesita enim virtus est, non quae relinqueret naturam, sed quae tueretur.</li><li>Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest.</li><li>Quod si ita est, sequitur id ipsum, quod te velle video, omnes semper beatos esse sapientes.</li><li>A villa enim, credo, et: Si ibi te esse scissem, ad te ipse venissem.</li></ul> +<!-- /wp:list --> + +<!-- wp:heading {"level":3} --> +<h3>Sed potestne rerum maior esse dissensio?</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Indicant pueri, in quibus ut in speculis natura cernitur. Rem unam praeclarissimam omnium maximeque laudandam, penitus viderent, quonam gaudio complerentur, cum tantopere eius adumbrata opinione laetentur? Ait enim se, si uratur, Quam hoc suave! dicturum. Atqui eorum nihil est eius generis, ut sit in fine atque extrerno bonorum. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p><a href="http://example.com/" target="_blank" rel="noreferrer noopener">At multis se probavit.</a> Non est enim vitium in oratione solum, sed etiam in moribus. <a href="http://example.com/" target="_blank" rel="noreferrer noopener">Invidiosum nomen est, infame, suspectum.</a> Cetera illa adhibebat, quibus demptis negat se Epicurus intellegere quid sit bonum. Quam ob rem tandem, inquit, non satisfacit? An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? Atqui iste locus est, Piso, tibi etiam atque etiam confirmandus, inquam; Nunc vides, quid faciat. <em>Frater et T.</em> Tanti autem aderant vesicae et torminum morbi, ut nihil ad eorum magnitudinem posset accedere. </p> +<!-- /wp:paragraph --> + +<!-- wp:heading {"level":3} --> +<h3>Et ille ridens: Video, inquit, quid agas;</h3> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Te enim iudicem aequum puto, modo quae dicat ille bene noris. Non est ista, inquam, Piso, magna dissensio. Ita cum ea volunt retinere, quae superiori sententiae conveniunt, in Aristonem incidunt; Ego quoque, inquit, didicerim libentius si quid attuleris, quam te reprehenderim. Que Manilium, ab iisque M. Sic, et quidem diligentius saepiusque ista loquemur inter nos agemusque communiter. Hoc est non modo cor non habere, sed ne palatum quidem. Nobis Heracleotes ille Dionysius flagitiose descivisse videtur a Stoicis propter oculorum dolorem. </p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Tu autem negas fortem esse quemquam posse, qui dolorem malum putet. Portenta haec esse dicit, neque ea ratione ullo modo posse vivi; Nec vero alia sunt quaerenda contra Carneadeam illam sententiam. Ut proverbia non nulla veriora sint quam vestra dogmata. Qui autem esse poteris, nisi te amor ipse ceperit? Ego vero volo in virtute vim esse quam maximam; Cur fortior sit, si illud, quod tute concedis, asperum et vix ferendum putabit? Si stante, hoc natura videlicet vult, salvam esse se, quod concedimus; </p> +<!-- /wp:paragraph --> + +<!-- wp:image --> +<figure class="wp-block-image"><img src="http://via.placeholder.com/640x360" alt=""/></figure> +<!-- /wp:image --> + +<!-- wp:quote --> +<blockquote class="wp-block-quote"><p> + Quos ille, di inmortales, cum omnes artus ardere viderentur, cruciatus perferebat! nec tamen miser esse, quia summum id malum non erat, tantum modo laboriosus videbatur; +</p></blockquote> +<!-- /wp:quote --> + +<!-- wp:paragraph --> +<p>Intrandum est igitur in rerum naturam et penitus quid ea postulet pervidendum; <strong>Vide, quaeso, rectumne sit.</strong> Est enim tanti philosophi tamque nobilis audacter sua decreta defendere. Tum Piso: Quoniam igitur aliquid omnes, quid Lucius noster? <em>Quae cum dixisset paulumque institisset, Quid est?</em> Quae sequuntur igitur? </p> +<!-- /wp:paragraph --> + +<!-- wp:list --> +<ul><li>Eadem fortitudinis ratio reperietur.</li><li>Quid, quod homines infima fortuna, nulla spe rerum gerendarum, opifices denique delectantur historia?</li><li>Cum ageremus, inquit, vitae beatum et eundem supremum diem, scribebamus haec.</li><li>Scientiam pollicentur, quam non erat mirum sapientiae cupido patria esse cariorem.</li><li>Varietates autem iniurasque fortunae facile veteres philosophorum praeceptis instituta vita superabat.</li><li>Quamquam tu hanc copiosiorem etiam soles dicere.</li></ul> +<!-- /wp:list --> diff --git a/packages/e2e-tests/config/performance-reporter.js b/packages/e2e-tests/config/performance-reporter.js new file mode 100644 index 00000000000000..681bb60f41618e --- /dev/null +++ b/packages/e2e-tests/config/performance-reporter.js @@ -0,0 +1,29 @@ +const { readFileSync, existsSync } = require( 'fs' ); + +function average( array ) { + return array.reduce( ( a, b ) => a + b ) / array.length; +} + +class PerformanceReporter { + onRunComplete() { + const path = __dirname + '/../specs/results.json'; + + if ( ! existsSync( path ) ) { + return; + } + + const results = readFileSync( path, 'utf8' ); + const { load, domcontentloaded, type } = JSON.parse( results ); + + // eslint-disable-next-line no-console + console.log( ` +Average time to load: ${ average( load ) }ms +Average time to DOM content load: ${ average( domcontentloaded ) }ms +Average time to type character: ${ average( type ) }ms +Slowest time to type character: ${ Math.max( ...type ) }ms +Fastest time to type character: ${ Math.min( ...type ) }ms + ` ); + } +} + +module.exports = PerformanceReporter; diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index eeb88d9aa0daf0..5a4e0a5416a617 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -9,4 +9,7 @@ module.exports = { '@wordpress/jest-puppeteer-axe', 'expect-puppeteer', ], + testPathIgnorePatterns: [ + 'e2e-tests/specs/performance.test.js', + ], }; diff --git a/packages/e2e-tests/jest.performance.config.js b/packages/e2e-tests/jest.performance.config.js new file mode 100644 index 00000000000000..f8fe4cf8ff1ff6 --- /dev/null +++ b/packages/e2e-tests/jest.performance.config.js @@ -0,0 +1,23 @@ +module.exports = { + ...require( '@wordpress/scripts/config/jest-e2e.config' ), + testMatch: [ + '**/performance.test.js', + ], + setupFiles: [ + '<rootDir>/config/gutenberg-phase.js', + ], + setupFilesAfterEnv: [ + '<rootDir>/config/setup-test-framework.js', + '@wordpress/jest-console', + '@wordpress/jest-puppeteer-axe', + 'expect-puppeteer', + ], + transformIgnorePatterns: [ + 'node_modules', + 'scripts/config/puppeteer.config.js', + ], + reporters: [ + 'default', + '<rootDir>/config/performance-reporter.js', + ], +}; diff --git a/packages/e2e-tests/specs/.gitignore b/packages/e2e-tests/specs/.gitignore new file mode 100644 index 00000000000000..a6c57f5fb2ffba --- /dev/null +++ b/packages/e2e-tests/specs/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/packages/e2e-tests/specs/performance.test.js b/packages/e2e-tests/specs/performance.test.js new file mode 100644 index 00000000000000..1d547f8075139c --- /dev/null +++ b/packages/e2e-tests/specs/performance.test.js @@ -0,0 +1,72 @@ +/** + * External dependencies + */ +import { join } from 'path'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; + +/** + * WordPress dependencies + */ +import { + createNewPost, + saveDraft, + insertBlock, +} from '@wordpress/e2e-test-utils'; + +function readFile( filePath ) { + return existsSync( filePath ) ? readFileSync( filePath, 'utf8' ).trim() : ''; +} + +describe( 'Performance', () => { + it( '1000 paragraphs', async () => { + const html = readFile( join( __dirname, '../assets/large-post.html' ) ); + + await createNewPost(); + await page.evaluate( ( _html ) => { + const { parse } = window.wp.blocks; + const { dispatch } = window.wp.data; + const blocks = parse( _html ); + + blocks.forEach( ( block ) => { + if ( block.name === 'core/image' ) { + delete block.attributes.id; + delete block.attributes.url; + } + } ); + + dispatch( 'core/editor' ).resetBlocks( blocks ); + }, html ); + await saveDraft(); + + const results = { + load: [], + domcontentloaded: [], + type: [], + }; + + let i = 1; + let startTime; + + await page.on( 'load', () => results.load.push( new Date() - startTime ) ); + await page.on( 'domcontentloaded', () => results.domcontentloaded.push( new Date() - startTime ) ); + + while ( i-- ) { + startTime = new Date(); + await page.reload( { waitUntil: [ 'domcontentloaded', 'load' ] } ); + } + + await insertBlock( 'Paragraph' ); + + i = 200; + + while ( i-- ) { + startTime = new Date(); + await page.keyboard.type( 'x' ); + results.type.push( new Date() - startTime ); + } + + writeFileSync( __dirname + '/results.json', JSON.stringify( results, null, 2 ) ); + + expect( true ).toBe( true ); + } ); +} ); From bb24bbef1790b165ca8608d4cee7a5ec6e85c234 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 28 May 2019 13:51:27 +0100 Subject: [PATCH 204/664] Move transform styles function to blockEditor package. (#15572) This commit moves the transform styles function to blockEditor package so they can safely be used by the blocks. --- packages/block-editor/src/index.js | 1 + packages/block-editor/src/utils/index.js | 4 ++++ .../src/utils/transform-styles}/ast/index.js | 0 .../src/utils/transform-styles}/ast/parse.js | 0 .../utils/transform-styles}/ast/stringify/compiler.js | 0 .../utils/transform-styles}/ast/stringify/compress.js | 0 .../utils/transform-styles}/ast/stringify/identity.js | 0 .../src/utils/transform-styles}/ast/stringify/index.js | 0 .../src/utils/transform-styles}/index.js | 0 .../test/__snapshots__/traverse.js.snap | 0 .../src/utils/transform-styles}/test/traverse.js | 0 .../transforms/test/__snapshots__/url-rewrite.js.snap | 0 .../transforms/test/__snapshots__/wrap.js.snap | 0 .../transform-styles}/transforms/test/url-rewrite.js | 0 .../src/utils/transform-styles}/transforms/test/wrap.js | 0 .../utils/transform-styles}/transforms/url-rewrite.js | 0 .../src/utils/transform-styles}/transforms/wrap.js | 0 .../src/utils/transform-styles}/traverse.js | 0 packages/block-library/src/html/edit.js | 9 ++++++--- packages/editor/src/components/provider/index.js | 5 ++--- packages/editor/src/index.js | 6 +++++- 21 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 packages/block-editor/src/utils/index.js rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/ast/index.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/ast/parse.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/ast/stringify/compiler.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/ast/stringify/compress.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/ast/stringify/identity.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/ast/stringify/index.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/index.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/test/__snapshots__/traverse.js.snap (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/test/traverse.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/transforms/test/__snapshots__/url-rewrite.js.snap (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/transforms/test/__snapshots__/wrap.js.snap (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/transforms/test/url-rewrite.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/transforms/test/wrap.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/transforms/url-rewrite.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/transforms/wrap.js (100%) rename packages/{editor/src/editor-styles => block-editor/src/utils/transform-styles}/traverse.js (100%) diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index fe526a2352a428..1d8827e2a33b49 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -13,5 +13,6 @@ import './store'; import './hooks'; export * from './components'; +export * from './utils'; export { SETTINGS_DEFAULTS } from './store/defaults'; diff --git a/packages/block-editor/src/utils/index.js b/packages/block-editor/src/utils/index.js new file mode 100644 index 00000000000000..cb35b8d3420d15 --- /dev/null +++ b/packages/block-editor/src/utils/index.js @@ -0,0 +1,4 @@ +/** + * Internal dependencies + */ +export { default as __experimentalTransformStyles } from './transform-styles'; diff --git a/packages/editor/src/editor-styles/ast/index.js b/packages/block-editor/src/utils/transform-styles/ast/index.js similarity index 100% rename from packages/editor/src/editor-styles/ast/index.js rename to packages/block-editor/src/utils/transform-styles/ast/index.js diff --git a/packages/editor/src/editor-styles/ast/parse.js b/packages/block-editor/src/utils/transform-styles/ast/parse.js similarity index 100% rename from packages/editor/src/editor-styles/ast/parse.js rename to packages/block-editor/src/utils/transform-styles/ast/parse.js diff --git a/packages/editor/src/editor-styles/ast/stringify/compiler.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js similarity index 100% rename from packages/editor/src/editor-styles/ast/stringify/compiler.js rename to packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js diff --git a/packages/editor/src/editor-styles/ast/stringify/compress.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js similarity index 100% rename from packages/editor/src/editor-styles/ast/stringify/compress.js rename to packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js diff --git a/packages/editor/src/editor-styles/ast/stringify/identity.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js similarity index 100% rename from packages/editor/src/editor-styles/ast/stringify/identity.js rename to packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js diff --git a/packages/editor/src/editor-styles/ast/stringify/index.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js similarity index 100% rename from packages/editor/src/editor-styles/ast/stringify/index.js rename to packages/block-editor/src/utils/transform-styles/ast/stringify/index.js diff --git a/packages/editor/src/editor-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js similarity index 100% rename from packages/editor/src/editor-styles/index.js rename to packages/block-editor/src/utils/transform-styles/index.js diff --git a/packages/editor/src/editor-styles/test/__snapshots__/traverse.js.snap b/packages/block-editor/src/utils/transform-styles/test/__snapshots__/traverse.js.snap similarity index 100% rename from packages/editor/src/editor-styles/test/__snapshots__/traverse.js.snap rename to packages/block-editor/src/utils/transform-styles/test/__snapshots__/traverse.js.snap diff --git a/packages/editor/src/editor-styles/test/traverse.js b/packages/block-editor/src/utils/transform-styles/test/traverse.js similarity index 100% rename from packages/editor/src/editor-styles/test/traverse.js rename to packages/block-editor/src/utils/transform-styles/test/traverse.js diff --git a/packages/editor/src/editor-styles/transforms/test/__snapshots__/url-rewrite.js.snap b/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/url-rewrite.js.snap similarity index 100% rename from packages/editor/src/editor-styles/transforms/test/__snapshots__/url-rewrite.js.snap rename to packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/url-rewrite.js.snap diff --git a/packages/editor/src/editor-styles/transforms/test/__snapshots__/wrap.js.snap b/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap similarity index 100% rename from packages/editor/src/editor-styles/transforms/test/__snapshots__/wrap.js.snap rename to packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap diff --git a/packages/editor/src/editor-styles/transforms/test/url-rewrite.js b/packages/block-editor/src/utils/transform-styles/transforms/test/url-rewrite.js similarity index 100% rename from packages/editor/src/editor-styles/transforms/test/url-rewrite.js rename to packages/block-editor/src/utils/transform-styles/transforms/test/url-rewrite.js diff --git a/packages/editor/src/editor-styles/transforms/test/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js similarity index 100% rename from packages/editor/src/editor-styles/transforms/test/wrap.js rename to packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js diff --git a/packages/editor/src/editor-styles/transforms/url-rewrite.js b/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js similarity index 100% rename from packages/editor/src/editor-styles/transforms/url-rewrite.js rename to packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js diff --git a/packages/editor/src/editor-styles/transforms/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js similarity index 100% rename from packages/editor/src/editor-styles/transforms/wrap.js rename to packages/block-editor/src/utils/transform-styles/transforms/wrap.js diff --git a/packages/editor/src/editor-styles/traverse.js b/packages/block-editor/src/utils/transform-styles/traverse.js similarity index 100% rename from packages/editor/src/editor-styles/traverse.js rename to packages/block-editor/src/utils/transform-styles/traverse.js diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 99df666bb55ec8..4ddc146ccf3d66 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -3,8 +3,11 @@ */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { BlockControls, PlainText } from '@wordpress/block-editor'; -import { transformStyles } from '@wordpress/editor'; +import { + BlockControls, + PlainText, + __experimentalTransformStyles, +} from '@wordpress/block-editor'; import { Disabled, SandBox } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; @@ -35,7 +38,7 @@ class HTMLEdit extends Component { this.setState( { styles: [ defaultStyles, - ...transformStyles( styles ), + ...__experimentalTransformStyles( styles ), ] } ); } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 2a4dd5ccbf26f5..601a44c0260cfe 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -11,7 +11,7 @@ import { compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEditorProvider } from '@wordpress/block-editor'; +import { BlockEditorProvider, __experimentalTransformStyles } from '@wordpress/block-editor'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; @@ -19,7 +19,6 @@ import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies */ -import transformStyles from '../../editor-styles'; import { mediaUpload } from '../../utils'; import ReusableBlocksButtons from '../reusable-blocks-buttons'; @@ -111,7 +110,7 @@ class EditorProvider extends Component { return; } - const updatedStyles = transformStyles( this.props.settings.styles, '.editor-styles-wrapper' ); + const updatedStyles = __experimentalTransformStyles( this.props.settings.styles, '.editor-styles-wrapper' ); map( updatedStyles, ( updatedCSS ) => { if ( updatedCSS ) { diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index 1f5baad7285f2f..a55a7b1c0bfc1b 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -17,4 +17,8 @@ import './hooks'; export * from './components'; export * from './utils'; -export { default as transformStyles } from './editor-styles'; + +/* + * Backward compatibility + */ +export { __experimentalTransformStyles as transformStyles } from '@wordpress/block-editor'; From c0ac81a6465e15125f1b54ee2b1bf63076d21e62 Mon Sep 17 00:00:00 2001 From: fritz-cwp <50090586+fritz-cwp@users.noreply.github.com> Date: Wed, 29 May 2019 04:56:16 +0900 Subject: [PATCH 205/664] Fix @wordpress/data documentation (#15831) * Make import more conservative in sample code As `data` is only used on the next line in the example, it seems preferable to not reserve that variable name for the scope of the entire file. * Fix syntax errors and improper object handling in sample code The `existingActions`/`existingSelectors` objects were being treated like arrays with `.map()`. This commit changes the code to treat them like plain JS objects. --- packages/data/README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/data/README.md b/packages/data/README.md index 8984e86b1c5770..a5bdfd30a5c375 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -19,8 +19,8 @@ _This package assumes that your code will run in an **ES2015+** environment. If Use the `registerStore` function to add your own store to the centralized data registry. This function accepts two arguments: a name to identify the module, and an object with values describing how your state is represented, modified, and accessed. At a minimum, you must provide a reducer function describing the shape of your state and how it changes in response to actions dispatched to the store. ```js -const { data, apiFetch } = wp; -const { registerStore } = data; +const { apiFetch } = wp; +const { registerStore } = wp.data; const DEFAULT_STATE = { prices: {}, @@ -166,17 +166,20 @@ import existingSelectors from './existing-app/selectors'; import existingActions from './existing-app/actions'; import createStore from './existing-app/store'; +const { registerGenericStore } = wp.data; + const reduxStore = createStore(); -const mappedSelectors = existingSelectors.map( ( selector ) => { - return ( ...args ) => selector( reduxStore.getState(), ...args ); -} ); +const mappedSelectors = Object.keys( existingSelectors ).reduce( ( acc, selectorKey ) => { + acc[ selectorKey ] = ( ...args ) => + existingSelectors[ selectorKey ]( reduxStore.getState(), ...args ); + return acc; +}, {} ); -const mappedActions = existingActions.map( ( action ) => { - return actions.map( ( action ) => { - return ( ...args ) => reduxStore.dispatch( action( ...args ) ); - } ); -} ); +const mappedActions = Object.keys( existingActions ).reduce( ( acc, actionKey ) => { + acc[ actionKey ] = ( ...args ) => reduxStore.dispatch( existingActions[ actionKey ]( ...args ) ); + return acc; +}, {} ); const genericStore = { getSelectors() { @@ -185,10 +188,10 @@ const genericStore = { getActions() { return mappedActions; }, - subscribe: reduxStore.subscribe; + subscribe: reduxStore.subscribe, }; -registry.registerGenericStore( 'existing-app', genericStore ); +registerGenericStore( 'existing-app', genericStore ); ``` It is also possible to implement a completely custom store from scratch: @@ -196,18 +199,20 @@ It is also possible to implement a completely custom store from scratch: _Example:_ ```js +const { registerGenericStore } = wp.data; + function createCustomStore() { let storeChanged = () => {}; const prices = { hammer: 7.50 }; const selectors = { - getPrice( itemName ): { + getPrice( itemName ) { return prices[ itemName ]; }, }; const actions = { - setPrice( itemName, price ): { + setPrice( itemName, price ) { prices[ itemName ] = price; storeChanged(); }, @@ -226,7 +231,7 @@ function createCustomStore() { }; } -registry.registerGenericStore( 'custom-data', createCustomStore() ); +registerGenericStore( 'custom-data', createCustomStore() ); ``` ## Comparison with Redux From a606c4b9b9d3cfc5ba28687a1fb0c61596782c7b Mon Sep 17 00:00:00 2001 From: Thorsten Frommen <info@tfrommen.de> Date: Wed, 29 May 2019 09:09:31 +0200 Subject: [PATCH 206/664] ESLint Plugin: Fix rule selectors (#15839) * Fix selectors * Fix handling of _nx() * Update CHANGELOG.md * ESLint Plugin: Add CHANGELOG entry for `_nx` arguments placement --- packages/eslint-plugin/CHANGELOG.md | 7 +++++++ packages/eslint-plugin/configs/custom.js | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 2f66b8cab95140..a88430165222ef 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,10 @@ +## Master + +### Bug Fix + +- Fixed custom regular expression for the `no-restricted-syntax` rule enforcing translate function arguments. [#15839](https://github.com/WordPress/gutenberg/pull/15839). +- Fixed arguments checking of `_nx` for the `no-restricted-syntax` rule enforcing translate function arguments. [#15839](https://github.com/WordPress/gutenberg/pull/15839). + ## 2.2.0 (2019-05-21) ### New Features diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 7a51d736c3f290..a09c22148cc592 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -11,15 +11,15 @@ module.exports = { 'no-restricted-syntax': [ 'error', { - selector: 'CallExpression[callee.name=/^__|_n|_x$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', + selector: 'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', message: 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=/^_n|_x$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', + selector: 'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', message: 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=_nx]:not([arguments.2.type=/^Literal|BinaryExpression$/])', + selector: 'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])', message: 'Translate function arguments must be string literals.', }, ], From 48afde88f887700cbc87089625b054095bf62446 Mon Sep 17 00:00:00 2001 From: Thorsten Frommen <info@tfrommen.de> Date: Wed, 29 May 2019 09:33:17 +0200 Subject: [PATCH 207/664] Copy no-restricted-syntax rules from ESLint plugin to config file (#15877) Also, remove unused import of lodash.map --- .eslintrc.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index a3762e98c368bb..464b6e57cf3eb9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,7 @@ /** * External dependencies */ -const { escapeRegExp, map } = require( 'lodash' ); +const { escapeRegExp } = require( 'lodash' ); /** * Internal dependencies @@ -37,6 +37,18 @@ module.exports = { selector: 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + majorMinorRegExp + '/]', message: 'Deprecated functions must be removed before releasing this version.', }, + { + selector: 'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + { + selector: 'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, + { + selector: 'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])', + message: 'Translate function arguments must be string literals.', + }, { selector: 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] Literal[value=/\\.{3}/]', message: 'Use ellipsis character (…) in place of three dots', From c7242c34be4fab29b86aed69bb40de49439cbb7a Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 29 May 2019 08:38:04 +0100 Subject: [PATCH 208/664] Fix: Embeds crash when nested inside a block with locking (#15866) --- packages/block-library/src/embed/edit.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index 97c28876280160..b92d07d2927d30 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -42,12 +42,14 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { handleIncomingPreview() { this.setMergedAttributes(); - const upgradedBlock = createUpgradedEmbedBlock( - this.props, - this.getMergedAttributes() - ); - if ( upgradedBlock ) { - this.props.onReplace( upgradedBlock ); + if ( this.props.onReplace ) { + const upgradedBlock = createUpgradedEmbedBlock( + this.props, + this.getMergedAttributes() + ); + if ( upgradedBlock ) { + this.props.onReplace( upgradedBlock ); + } } } From 2fea85d7f9f25795b306f738fcc33eaa88606b56 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 29 May 2019 10:20:44 +0100 Subject: [PATCH 209/664] Support releasing stable Github releases using the release tool (#15848) --- bin/commander.js | 495 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 419 insertions(+), 76 deletions(-) diff --git a/bin/commander.js b/bin/commander.js index ce92015e2216fe..3412492ce062f4 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -15,14 +15,14 @@ const Octokit = require( '@octokit/rest' ); const os = require( 'os' ); const uuid = require( 'uuid/v4' ); -// Common info -const workingDirectoryPath = path.join( os.tmpdir(), uuid() ); -const packageJsonPath = workingDirectoryPath + '/package.json'; -const packageLockPath = workingDirectoryPath + '/package-lock.json'; -const pluginFilePath = workingDirectoryPath + '/gutenberg.php'; -const gutenbergZipPath = workingDirectoryPath + '/gutenberg.zip'; -const repoOwner = 'WordPress'; -const repoURL = 'git@github.com:' + repoOwner + '/gutenberg.git'; +// Config +const gitRepoOwner = 'WordPress'; +const gitRepoURL = 'git@github.com:' + gitRepoOwner + '/gutenberg.git'; +const svnRepoURL = 'https://plugins.svn.wordpress.org/gutenberg'; + +// Working Directories +const gitWorkingDirectoryPath = path.join( os.tmpdir(), uuid() ); +const svnWorkingDirectoryPath = path.join( os.tmpdir(), uuid() ); // UI const error = chalk.bold.red; @@ -74,51 +74,198 @@ async function runStep( name, abortMessage, handler ) { } /** - * Command used to release an RC version of the plugin + * Utility to run a child script + * + * @param {string} script Script to run. + * @param {string} cwd Working directory. */ -async function releasePluginRC() { - console.log( - chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), - 'Welcome! This tool is going to help you release a new RC version of the Gutenberg Plugin.\n', - 'It goes throught different steps : creating the release branch, bumping the plugin version, tagging and creating the github release, building the zip...\n', - 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' - ); +function runShellScript( script, cwd ) { + childProcess.execSync( script, { + cwd, + env: { + NO_CHECKS: true, + PATH: process.env.PATH, + }, + stdio: [ 'inherit', 'ignore', 'inherit' ], + } ); +} - // This is a variable that contains the abort message shown when the script is aborted. - let abortMessage = 'Aborting!'; - await askForConfirmationToContinue( 'Ready to go? ' ); +// Steps +/** + * Clone the Gutenberg repository to the working directory. + * + * @param {string} abortMessage Abort message. + */ +async function runGitRepositoryCloneStep( abortMessage ) { // Cloning the repository - await runStep( 'Cloning the repository', abortMessage, async () => { - console.log( '>> Cloning the repository' ); + await runStep( 'Cloning the Git repository', abortMessage, async () => { + console.log( '>> Cloning the Git repository' ); const simpleGit = SimpleGit(); - await simpleGit.clone( repoURL, workingDirectoryPath, [ '--depth=1' ] ); - console.log( '>> The gutenberg repository has been successfully cloned in the following temporary folder: ' + success( workingDirectoryPath ) ); + await simpleGit.clone( gitRepoURL, gitWorkingDirectoryPath ); + console.log( '>> The Gutenberg Git repository has been successfully cloned in the following temporary folder: ' + success( gitWorkingDirectoryPath ) ); } ); +} - // Creating the release branch - const simpleGit = SimpleGit( workingDirectoryPath ); - let nextVersion; - let nextVersionLabel; - let releaseBranch; - const packageJson = require( packageJsonPath ); - const packageLock = require( packageLockPath ); +/** + * Fetching the SVN Gutenberg repository to the working directory. + * + * @param {string} abortMessage Abort message. + */ +async function runSvnRepositoryCloneStep( abortMessage ) { + // Cloning the repository + await runStep( 'Fetching the SVN repository', abortMessage, async () => { + console.log( '>> Fetching the SVN repository' ); + runShellScript( 'svn checkout ' + svnRepoURL + '/trunk ' + svnWorkingDirectoryPath ); + console.log( '>> The Gutenberg SVN repository has been successfully fetched in the following temporary folder: ' + success( svnWorkingDirectoryPath ) ); + } ); +} + +/** + * Updates and commits the content of the SVN repo using the new plugin zip. + * + * @param {string} version Version. + * @param {string} changelog Changelog. + * @param {string} abortMessage Abort Message. + */ +async function runUpdateTrunkContentStep( version, changelog, abortMessage ) { + // Updating the content of the svn + await runStep( 'Updating trunk content', abortMessage, async () => { + console.log( '>> Replacing trunk content using the new plugin zip' ); + + // Delete everything except readme.txt and changelog.txt + runShellScript( 'find . -maxdepth 1 -not -name "changelog.txt" -not -name "readme.txt" -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +', svnWorkingDirectoryPath ); + + // Update the content using the plugin zip + const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; + runShellScript( 'unzip ' + gutenbergZipPath + ' -d ' + svnWorkingDirectoryPath ); + + console.log( '>> Updating the changelog in readme.txt and changelog.txt' ); + + // Update the content of the readme.txt file + const readmePath = svnWorkingDirectoryPath + '/readme.txt'; + const readmeFileContent = fs.readFileSync( readmePath, 'utf8' ); + const newReadmeContent = + readmeFileContent.substr( 0, readmeFileContent.indexOf( '== Changelog ==' ) ) + + '== Changelog ==\n\n' + + changelog + '\n'; + fs.writeFileSync( readmePath, newReadmeContent ); + + // Update the content of the changelog.txt file + const changelogPath = svnWorkingDirectoryPath + '/changelog.txt'; + const changelogFileContent = fs.readFileSync( changelogPath, 'utf8' ); + const newChangelogContent = + '== Changelog ==\n\n' + + '= ' + version + ' =\n\n' + + changelog + + changelogFileContent.substr( changelogFileContent.indexOf( '== Changelog ==' ) + 16 ); + fs.writeFileSync( changelogPath, newChangelogContent ); + + // Commit the content changes + runShellScript( "svn st | grep '^\?' | awk '{print $2}' | xargs svn add", svnWorkingDirectoryPath ); + runShellScript( "svn st | grep '^!' | awk '{print $2}' | xargs svn rm", svnWorkingDirectoryPath ); + await askForConfirmationToContinue( + 'Trunk content has been updated, please check the SVN diff. Commit the changes?', + true, + abortMessage + ); + + runShellScript( 'svn commit -m "Committing Gutenberg version ' + version + '"', svnWorkingDirectoryPath ); + + console.log( '>> Trunk has been successfully updated' ); + } ); +} + +/** + * Creates a new SVN Tag + * + * @param {string} version Version. + * @param {string} abortMessage Abort Message. + */ +async function runSvnTagStep( version, abortMessage ) { + await runStep( 'Creating the SVN Tag', abortMessage, async () => { + await askForConfirmationToContinue( + 'Proceed with the creation of the SVN Tag?', + true, + abortMessage + ); + runShellScript( 'svn cp ' + svnRepoURL + '/trunk ' + svnRepoURL + '/tags/' + version + ' -m "Tagging Gutenberg version ' + version + '"' ); + + console.log( '>> The SVN ' + success( version ) + ' tag has been successfully created' ); + } ); +} + +/** + * Updates the stable version of the plugin in the SVN repository. + * + * @param {string} version Version. + * @param {string} abortMessage Abort Message. + */ +async function updateThePluginStableVersion( version, abortMessage ) { + // Updating the content of the svn + await runStep( 'Updating the plugin\'s stable version', abortMessage, async () => { + const readmePath = svnWorkingDirectoryPath + '/readme.txt'; + const readmeFileContent = fs.readFileSync( readmePath, 'utf8' ); + const newReadmeContent = readmeFileContent.replace( + /Stable tag: [0-9]+.[0-9]+.[0-9]+\s*\n/, + 'Stable tag: ' + version + '\n' + ); + fs.writeFileSync( readmePath, newReadmeContent ); + + // Commit the content changes + await askForConfirmationToContinue( + 'The stable version is updated in the readme.txt file. Commit the changes?', + true, + abortMessage + ); + + runShellScript( 'svn commit -m "Releasing Gutenberg version ' + version + '"', svnWorkingDirectoryPath ); + + console.log( '>> Stable version updated successfully' ); + } ); +} + +/** + * Clean the working directory. + * + * @param {string} abortMessage Abort message. + */ +async function runCleanLocalCloneStep( abortMessage ) { + await runStep( 'Cleaning the temporary folder', abortMessage, async () => { + await fs.remove( gitWorkingDirectoryPath ); + await fs.remove( svnWorkingDirectoryPath ); + } ); +} + +/** + * Creates a new release branch based on the last package.json version + * and chooses the next RC version number. + * + * @param {string} abortMessage Abort Message. + * + * @return {Object} chosen version and versionLabels. + */ +async function runReleaseBranchCreationStep( abortMessage ) { + let version, releaseBranch, versionLabel; await runStep( 'Creating the release branch', abortMessage, async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; + const packageJson = require( packageJsonPath ); const parsedVersion = semver.parse( packageJson.version ); // Follow the WordPress version guidelines to compute the version to be used // By default, increase the "minor" number but if we reach 9, bump to the next major. if ( parsedVersion.minor === 9 ) { - nextVersion = ( parsedVersion.major + 1 ) + '.0.0-rc.1'; + version = ( parsedVersion.major + 1 ) + '.0.0-rc.1'; releaseBranch = 'release/' + ( parsedVersion.major + 1 ) + '.0'; - nextVersionLabel = ( parsedVersion.major + 1 ) + '.0.0 RC1'; + versionLabel = ( parsedVersion.major + 1 ) + '.0.0 RC1'; } else { - nextVersion = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0-rc.1'; + version = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0-rc.1'; releaseBranch = 'release/' + parsedVersion.major + '.' + ( parsedVersion.minor + 1 ); - nextVersionLabel = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0 RC1'; + versionLabel = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0 RC1'; } await askForConfirmationToContinue( - 'The RC Version to be applied is ' + success( nextVersion ) + '. Proceed with the creation of the release branch?', + 'The Plugin version to be used is ' + success( version ) + '. Proceed with the creation of the release branch?', true, abortMessage ); @@ -128,21 +275,84 @@ async function releasePluginRC() { console.log( '>> The local release branch ' + success( releaseBranch ) + ' has been successfully created.' ); } ); - // Bumping the version in the different files (package.json, package-lock.json, gutenberg.php) + return { + version, + versionLabel, + }; +} + +/** + * Checkouts out the release branch and chooses a stable version number. + * + * @param {string} abortMessage Abort Message. + * + * @return {Object} chosen version and versionLabels. + */ +async function runReleaseBranchCheckoutStep( abortMessage ) { + let releaseBranch, version; + await runStep( 'Getting into the release branch', abortMessage, async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; + const masterPackageJson = require( packageJsonPath ); + const masterParsedVersion = semver.parse( masterPackageJson.version ); + releaseBranch = 'release/' + masterParsedVersion.major + '.' + masterParsedVersion.minor; + + // Creating the release branch + await simpleGit.checkout( releaseBranch ); + console.log( '>> The local release branch ' + success( releaseBranch ) + ' has been successfully checked out.' ); + + const releaseBranchPackageJson = require( packageJsonPath ); + const releaseBranchParsedVersion = semver.parse( releaseBranchPackageJson.version ); + + if ( releaseBranchParsedVersion.prerelease && releaseBranchParsedVersion.prerelease.length ) { + version = releaseBranchParsedVersion.major + '.' + releaseBranchParsedVersion.minor + '.' + releaseBranchParsedVersion.patch; + } else { + version = releaseBranchParsedVersion.major + '.' + releaseBranchParsedVersion.minor + '.' + ( releaseBranchParsedVersion.patch + 1 ); + } + + await askForConfirmationToContinue( + 'The Version to release is ' + success( version ) + '. Proceed?', + true, + abortMessage + ); + } ); + + return { + version, + versionLabel: version, + }; +} + +/** + * Bump the version in the different files (package.json, package-lock.json, gutenberg.php) + * and commit the changes. + * + * @param {string} version Version to use. + * @param {string} abortMessage Abort message. + * + * @return {string} hash of the version bump commit. + */ +async function runBumpPluginVersionAndCommitStep( version, abortMessage ) { let commitHash; await runStep( 'Updating the plugin version', abortMessage, async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; + const packageLockPath = gitWorkingDirectoryPath + '/package-lock.json'; + const pluginFilePath = gitWorkingDirectoryPath + '/gutenberg.php'; + const packageJson = require( packageJsonPath ); + const packageLock = require( packageLockPath ); const newPackageJson = { ...packageJson, - version: nextVersion, + version, }; fs.writeFileSync( packageJsonPath, JSON.stringify( newPackageJson, null, '\t' ) + '\n' ); const newPackageLock = { ...packageLock, - version: nextVersion, + version, }; fs.writeFileSync( packageLockPath, JSON.stringify( newPackageLock, null, '\t' ) + '\n' ); const content = fs.readFileSync( pluginFilePath, 'utf8' ); - fs.writeFileSync( pluginFilePath, content.replace( ' * Version: ' + packageJson.version, ' * Version: ' + nextVersion ) ); + fs.writeFileSync( pluginFilePath, content.replace( ' * Version: ' + packageJson.version, ' * Version: ' + version ) ); console.log( '>> The plugin version has been updated successfully.' ); // Commit the version bump @@ -156,53 +366,81 @@ async function releasePluginRC() { packageLockPath, pluginFilePath, ] ); - const commitData = await simpleGit.commit( 'Bump plugin version to ' + nextVersion ); + const commitData = await simpleGit.commit( 'Bump plugin version to ' + version ); commitHash = commitData.commit; - console.log( '>> The plugin version bump has been commited succesfully.' ); + console.log( '>> The plugin version bump has been commited successfully.' ); } ); - // Plugin ZIP creation + return commitHash; +} + +/** + * Run the Plugin ZIP Creation step. + * + * @param {string} abortMessage Abort message. + */ +async function runPluginZIPCreationStep( abortMessage ) { await runStep( 'Plugin ZIP creation', abortMessage, async () => { + const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; await askForConfirmationToContinue( 'Proceed and build the plugin zip? (It takes a few minutes)', true, abortMessage ); - childProcess.execSync( '/bin/bash bin/build-plugin-zip.sh', { - cwd: workingDirectoryPath, - env: { - NO_CHECKS: true, - PATH: process.env.PATH, - }, - stdio: [ 'inherit', 'ignore', 'inherit' ], - } ); + runShellScript( '/bin/bash bin/build-plugin-zip.sh', gitWorkingDirectoryPath ); - console.log( '>> The plugin zip has been built succesfully. Path: ' + success( gutenbergZipPath ) ); + console.log( '>> The plugin zip has been built successfully. Path: ' + success( gutenbergZipPath ) ); } ); +} - // Creating the git tag +/** + * Create a local Git Tag. + * + * @param {string} version Version to use. + * @param {string} abortMessage Abort message. + */ +async function runCreateGitTagStep( version, abortMessage ) { await runStep( 'Creating the git tag', abortMessage, async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); await askForConfirmationToContinue( 'Proceed with the creation of the git tag?', true, abortMessage ); - await simpleGit.addTag( 'v' + nextVersion ); - console.log( '>> The ' + success( 'v' + nextVersion ) + ' tag has been created succesfully.' ); + await simpleGit.addTag( 'v' + version ); + console.log( '>> The ' + success( 'v' + version ) + ' tag has been created successfully.' ); } ); +} +/** + * Push the local Git Changes and Tags to the remote repository. + * + * @param {string} abortMessage Abort message. + */ +async function runPushGitChangesStep( abortMessage ) { await runStep( 'Pushing the release branch and the tag', abortMessage, async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); await askForConfirmationToContinue( 'The release branch and the tag are going to be pushed to the remote repository. Continue?', true, abortMessage ); - await simpleGit.push( 'origin', releaseBranch ); + await simpleGit.push( 'origin' ); await simpleGit.pushTags( 'origin' ); } ); - abortMessage = 'Aborting! Make sure to remove remote release branch and tag.'; +} - // Creating the GitHub Release +/** + * Creates the github release and uploads the Gutenberg ZIP file into it. + * + * @param {string} version Released version. + * @param {string} versionLabel Label of the released Version. + * @param {boolean} isPrerelease is a pre-release. + * @param {string} abortMessage Abort message. + * + * @return {Object} Github release object. + */ +async function runGithubReleaseStep( version, versionLabel, isPrerelease, abortMessage ) { let octokit; let release; await runStep( 'Creating the GitHub release', abortMessage, async () => { @@ -228,21 +466,22 @@ async function releasePluginRC() { } ); const releaseData = await octokit.repos.createRelease( { - owner: repoOwner, + owner: gitRepoOwner, repo: 'gutenberg', - tag_name: 'v' + nextVersion, - name: nextVersionLabel, + tag_name: 'v' + version, + name: versionLabel, body: changelog, - prerelease: true, + prerelease: isPrerelease, } ); release = releaseData.data; - console.log( '>> The GitHub release has been created succesfully.' ); + console.log( '>> The GitHub release has been created.' ); } ); - abortMessage = 'Aborting! Make sure to remove the remote release branch, the git tag and the GitHub release.'; + abortMessage = abortMessage + ' Make sure to remove the the GitHub release as well.'; // Uploading the Gutenberg Zip to the release await runStep( 'Uploading the plugin zip', abortMessage, async () => { + const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; const filestats = fs.statSync( gutenbergZipPath ); await octokit.repos.uploadReleaseAsset( { url: release.upload_url, @@ -253,14 +492,25 @@ async function releasePluginRC() { name: 'gutenberg.zip', file: fs.createReadStream( gutenbergZipPath ), } ); - console.log( '>> The plugin zip has been succesfully uploaded.' ); + console.log( '>> The plugin zip has been successfully uploaded.' ); } ); - abortMessage = 'Aborting! Make sure to manually cherry-pick the ' + success( commitHash ) + ' commit to the master branch.'; - // Cherry-picking the bump commit into master + console.log( '>> The Github release is available here: ' + success( release.html_url ) ); + + return release; +} + +/** + * Cherry-picks the version bump commit into master. + * + * @param {string} commitHash Commit to cherry-pick. + * @param {string} abortMessage Abort message. + */ +async function runCherrypickBumpCommitIntoMasterStep( commitHash, abortMessage ) { await runStep( 'Cherry-picking the bump commit into master', abortMessage, async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); await askForConfirmationToContinue( - 'The plugin RC is now released. Proceed with the version bump in the master branch?', + 'The plugin is now released. Proceed with the version bump in the master branch?', true, abortMessage ); @@ -271,23 +521,116 @@ async function releasePluginRC() { await simpleGit.raw( [ 'cherry-pick', commitHash ] ); await simpleGit.push( 'origin', 'master' ); } ); +} + +/** + * Release a new Gutenberg version. + * + * @param {boolean} isRC Whether it's an RC release or not. + * + * @return {Object} Github release object. + */ +async function releasePlugin( isRC = true ) { + // This is a variable that contains the abort message shown when the script is aborted. + let abortMessage = 'Aborting!'; + await askForConfirmationToContinue( 'Ready to go? ' ); + + // Cloning the Git repository + await runGitRepositoryCloneStep( abortMessage ); + + // Creating the release branch + const { version, versionLabel } = isRC ? + await runReleaseBranchCreationStep( abortMessage ) : + await runReleaseBranchCheckoutStep( abortMessage ); + + // Bumping the version and commit. + const commitHash = await runBumpPluginVersionAndCommitStep( version, abortMessage ); + + // Plugin ZIP creation + await runPluginZIPCreationStep(); + + // Creating the git tag + await runCreateGitTagStep( version, abortMessage ); + + // Push the local changes + await runPushGitChangesStep( abortMessage ); + abortMessage = 'Aborting! Make sure to ' + isRC ? 'remove' : 'reset' + ' the remote release branch and remove the git tag.'; + + // Creating the GitHub Release + const release = await runGithubReleaseStep( version, versionLabel, isRC, abortMessage ); + abortMessage = 'Aborting! Make sure to manually cherry-pick the ' + success( commitHash ) + ' commit to the master branch.'; + if ( ! isRC ) { + abortMessage += ' Make sure to perform the SVN release manually as well.'; + } + + // Cherry-picking the bump commit into master + await runCherrypickBumpCommitIntoMasterStep( commitHash, abortMessage ); + + if ( ! isRC ) { + abortMessage = 'Aborting! The Github release is done. Make sure to perform the SVN release manually.'; + + await askForConfirmationToContinue( 'The Gihub release is complete. Proceed with the SVN release? ', abortMessage ); + + // Fetching the SVN repository + await runSvnRepositoryCloneStep( abortMessage ); + + // Updating the SVN trunk content + await runUpdateTrunkContentStep( version, release.body, abortMessage ); + + abortMessage = 'Aborting! The Github release is done, SVN trunk updated. Make sure to create the SVN tag and update the stable version manually.'; + await runSvnTagStep( version, abortMessage ); + + abortMessage = 'Aborting! The Github release is done, SVN tagged. Make sure to update the stable version manually.'; + await updateThePluginStableVersion( version, abortMessage ); + } abortMessage = 'Aborting! The release is finished though.'; - await runStep( 'Cleaning the temporary folder', abortMessage, async () => { - await fs.remove( workingDirectoryPath ); - } ); + await runCleanLocalCloneStep( abortMessage ); - console.log( - '\n>> 🎉 The Gutenberg ' + success( nextVersionLabel ) + ' has been successfully released.\n', - 'You can access the Github release here: ' + success( release.html_url ) + '\n', - 'Thanks for performing the release!' - ); + return release; } program .command( 'release-plugin-rc' ) .alias( 'rc' ) .description( 'Release an RC version of the plugin (supports only rc.1 for now)' ) - .action( releasePluginRC ); + .action( async () => { + console.log( + chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), + 'Welcome! This tool is going to help you release a new RC version of the Gutenberg Plugin.\n', + 'It goes throught different steps : creating the release branch, bumping the plugin version, tagging and creating the github release, building the zip...\n', + 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' + ); + + const release = await releasePlugin( true ); + + console.log( + '\n>> 🎉 The Gutenberg ' + success( release.name ) + ' has been successfully released.\n', + 'You can access the Github release here: ' + success( release.html_url ) + '\n', + 'Thanks for performing the release!' + ); + } ); + +program + .command( 'release-plugin-stable' ) + .alias( 'stable' ) + .description( 'Release a stable version of the plugin' ) + .action( async () => { + console.log( + chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), + 'Welcome! This tool is going to help you release a new stable version of the Gutenberg Plugin.\n', + 'It goes throught different steps : bumping the plugin version, tagging and creating the github release, building the zip, pushing the release to the SVN repository...\n', + 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' + ); + + const release = await releasePlugin( false ); + + console.log( + '\n>> 🎉 The Gutenberg ' + success( release.name ) + ' has been successfully released.\n', + 'You can access the Github release here: ' + success( release.html_url ) + '\n', + 'In a few seconds, you\'ll be able to update the plugin from the WordPress repository.\n', + 'Thanks for performing the release! and don\'t forget to publish the release post.' + ); + } ); program.parse( process.argv ); From 03f04f30be3f3ece4a63db331cf6e30dea74abcd Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 29 May 2019 10:25:48 +0100 Subject: [PATCH 210/664] Bump plugin version to 5.8.0 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 1d166f153faacf..e1f216512fceac 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.8.0-rc.1 + * Version: 5.8.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 8d51cc11b6bb2a..0ad23808055801 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.8.0-rc.1", + "version": "5.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2d4e39666f7fbf..527d358b982b37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.8.0-rc.1", + "version": "5.8.0", "private": true, "description": "A new WordPress editor experience", "repository": "git+https://github.com/WordPress/gutenberg.git", From 93a1cdf408eba9b6bbf748f050d378742e459832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 29 May 2019 13:27:28 +0200 Subject: [PATCH 211/664] Build: Skip downloading Chromium when building plugin`s zip (#15886) --- bin/build-plugin-zip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index bcd431a3c62ac0..c57e0788060b3f 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -99,7 +99,7 @@ done # Run the build. status "Installing dependencies... 📦" -npm install +PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install status "Generating build... 👷‍♀️" npm run build From 45cd52d7a9e6defa007bf3cef039c938fe4d0399 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Wed, 29 May 2019 08:18:43 -0400 Subject: [PATCH 212/664] Vertically center default appender controls. (#15868) --- .../src/components/default-block-appender/style.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/style.scss index b98472ede59b87..c5cb3ac744571b 100644 --- a/packages/block-editor/src/components/default-block-appender/style.scss +++ b/packages/block-editor/src/components/default-block-appender/style.scss @@ -91,6 +91,9 @@ right: $grid-size; // Show to the right on mobile. @include break-small { + display: flex; + align-items: center; + height: 100%; left: -$block-side-ui-width - $block-padding - $block-side-ui-clearance; right: auto; } @@ -124,7 +127,9 @@ z-index: z-index(".block-editor-inserter-with-shortcuts"); // Elevate above the sibling inserter. @include break-small { - right: 0; display: flex; + align-items: center; + height: 100%; + right: 0; } } From 495608246d3ccdc8e3ff3271eee94d51474acb7e Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 29 May 2019 13:39:09 +0100 Subject: [PATCH 213/664] Try using snackbar notices instead of prominent ones for saving/failure notices (#15594) --- assets/stylesheets/_z-index.scss | 4 + docs/manifest-devhub.json | 6 ++ docs/manifest.json | 6 ++ packages/components/CHANGELOG.md | 1 + packages/components/src/index.js | 2 + packages/components/src/snackbar/README.md | 48 +++++++++++ packages/components/src/snackbar/index.js | 86 +++++++++++++++++++ packages/components/src/snackbar/list.js | 42 +++++++++ packages/components/src/snackbar/style.scss | 57 ++++++++++++ packages/components/src/style.scss | 1 + ...h-post-with-pre-publish-checks-disabled.js | 2 +- packages/e2e-test-utils/src/publish-post.js | 2 +- .../e2e-tests/specs/reusable-blocks.test.js | 8 +- .../edit-post/src/components/layout/index.js | 3 +- .../src/components/layout/style.scss | 38 +++----- .../src/components/editor-notices/index.js | 39 +++++++-- .../src/components/editor-notices/style.scss | 37 ++++++++ .../components/editor-notices/test/index.js | 35 -------- packages/editor/src/store/actions.js | 4 +- .../src/store/effects/reusable-blocks.js | 2 + packages/editor/src/store/test/actions.js | 2 +- .../editor/src/store/utils/notice-builder.js | 10 ++- .../src/store/utils/test/notice-builder.js | 2 +- packages/editor/src/style.scss | 1 + packages/notices/CHANGELOG.md | 6 ++ packages/notices/src/store/actions.js | 8 +- packages/notices/src/store/controls.js | 2 +- packages/notices/src/store/test/actions.js | 8 ++ packages/notices/src/store/test/reducer.js | 5 ++ 29 files changed, 379 insertions(+), 88 deletions(-) create mode 100644 packages/components/src/snackbar/README.md create mode 100644 packages/components/src/snackbar/index.js create mode 100644 packages/components/src/snackbar/list.js create mode 100644 packages/components/src/snackbar/style.scss create mode 100644 packages/editor/src/components/editor-notices/style.scss delete mode 100644 packages/editor/src/components/editor-notices/test/index.js diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 733a5c3ed756e9..92a044a7b899a3 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -76,6 +76,10 @@ $z-layers: ( // .edit-post-header { z-index: 30 } ".components-notice-list": 29, + + // Show snackbars above everything (similar to popovers) + ".components-snackbar-list": 100000, + // Show modal under the wp-admin menus and the popover ".components-modal__screen-overlay": 100000, diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index bfdf2e267fef62..f762d1422c454d 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -911,6 +911,12 @@ "markdown_source": "../packages/components/src/slot-fill/README.md", "parent": "components" }, + { + "title": "Snackbar", + "slug": "snackbar", + "markdown_source": "../packages/components/src/snackbar/README.md", + "parent": "components" + }, { "title": "Spinner", "slug": "spinner", diff --git a/docs/manifest.json b/docs/manifest.json index 429fd5bc835ae2..6fbe894c39d69e 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -887,6 +887,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/slot-fill/README.md", "parent": "components" }, + { + "title": "Snackbar", + "slug": "snackbar", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/components/src/snackbar/README.md", + "parent": "components" + }, { "title": "Spinner", "slug": "spinner", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 50165750d8a058..e6c72c7dbb190d 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -9,6 +9,7 @@ ### New Feature - Added a new `HorizontalRule` component. +- Added a new `Snackbar` component. ### Bug Fix diff --git a/packages/components/src/index.js b/packages/components/src/index.js index ce378c0b09e3a8..ab0540fa2f232f 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -50,6 +50,8 @@ export { default as ResizableBox } from './resizable-box'; export { default as ResponsiveWrapper } from './responsive-wrapper'; export { default as SandBox } from './sandbox'; export { default as SelectControl } from './select-control'; +export { default as Snackbar } from './snackbar'; +export { default as SnackbarList } from './snackbar/list'; export { default as Spinner } from './spinner'; export { default as ServerSideRender } from './server-side-render'; export { default as TabPanel } from './tab-panel'; diff --git a/packages/components/src/snackbar/README.md b/packages/components/src/snackbar/README.md new file mode 100644 index 00000000000000..c25956fba04c81 --- /dev/null +++ b/packages/components/src/snackbar/README.md @@ -0,0 +1,48 @@ +# Snackbar + +Use Snackbars to communicate low priority, non-interruptive messages to the user. + +## Table of contents + +1. [Design guidelines](#design-guidelines) +2. [Development guidelines](#development-guidelines) +3. [Related components](#related-components) + +## Design guidelines + +A Snackbar displays a succinct message that is cleared out after a small delay. It can also offer the user options, like viewing a published post but these options should also be available elsewhere in the UI. + +## Development guidelines + +### Usage + +To display a plain snackbar, pass the message as a `children` prop: + +```jsx +const MySnackbarNotice = () => ( + <Snackbar> + Post published successfully. + </Snackbar> +); +``` + +For more complex markup, you can pass any JSX element: + +```jsx +const MySnackbarNotice = () => ( + <Snackbar> + <p>An error occurred: <code>{ errorDetails }</code>.</p> + </Snackbar> +); +``` + +#### Props + +The following props are used to control the display of the component. + +* `onRemove`: function called when dismissing the notice. +* `actions`: (array) an array of action objects. Each member object should contain a `label` and either a `url` link string or `onClick` callback function. A `className` property can be used to add custom classes to the button styles. + +## Related components + +- To create a prominent message that requires a higher-level of attention, use a Notice. diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js new file mode 100644 index 00000000000000..c5d5674ff408da --- /dev/null +++ b/packages/components/src/snackbar/index.js @@ -0,0 +1,86 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { Button } from '../'; + +const NOTICE_TIMEOUT = 10000; + +function Snackbar( { + className, + children, + actions = [], + onRemove = noop, +} ) { + useEffect( () => { + // This rule doesn't account yet for React Hooks + // eslint-disable-next-line @wordpress/react-no-unsafe-timeout + const timeoutHandle = setTimeout( () => { + onRemove(); + }, NOTICE_TIMEOUT ); + + return () => clearTimeout( timeoutHandle ); + }, [] ); + + const classes = classnames( className, 'components-snackbar' ); + + return ( + <div + className={ classes } + onClick={ onRemove } + tabIndex="0" + role="button" + onKeyPress={ onRemove } + label={ __( 'Dismiss this notice' ) } + > + <div className="components-snackbar__content"> + { children } + { actions.map( + ( + { + className: buttonCustomClasses, + label, + onClick, + url, + }, + index + ) => { + return ( + <Button + key={ index } + href={ url } + isTertiary + onClick={ ( event ) => { + event.stopPropagation(); + if ( onClick ) { + onClick( event ); + } + } } + className={ classnames( + 'components-snackbar__action', + buttonCustomClasses + ) } + > + { label } + </Button> + ); + } + + ) } + </div> + </div> + ); +} + +export default Snackbar; diff --git a/packages/components/src/snackbar/list.js b/packages/components/src/snackbar/list.js new file mode 100644 index 00000000000000..c7cff210a138a3 --- /dev/null +++ b/packages/components/src/snackbar/list.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { omit, noop } from 'lodash'; + +/** + * Internal dependencies + */ +import Snackbar from './'; + +/** +* Renders a list of notices. +* +* @param {Object} $0 Props passed to the component. +* @param {Array} $0.notices Array of notices to render. +* @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. +* @param {Object} $0.className Name of the class used by the component. +* @param {Object} $0.children Array of children to be rendered inside the notice list. +* @return {Object} The rendered notices list. +*/ +function SnackbarList( { notices, className, children, onRemove = noop } ) { + className = classnames( 'components-snackbar-list', className ); + const removeNotice = ( id ) => () => onRemove( id ); + + return ( + <div className={ className }> + { children } + { notices.map( ( notice ) => ( + <Snackbar + { ...omit( notice, [ 'content' ] ) } + key={ notice.id } + onRemove={ removeNotice( notice.id ) } + > + { notice.content } + </Snackbar> + ) ) } + </div> + ); +} + +export default SnackbarList; diff --git a/packages/components/src/snackbar/style.scss b/packages/components/src/snackbar/style.scss new file mode 100644 index 00000000000000..40e8f75837ae6b --- /dev/null +++ b/packages/components/src/snackbar/style.scss @@ -0,0 +1,57 @@ +.components-snackbar { + font-family: $default-font; + font-size: $default-font-size; + background-color: $dark-gray-700; + border-radius: $radius-round-rectangle; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + color: $white; + padding: 16px 24px; + width: 100%; + max-width: 600px; + margin: 8px 0 0; + + @include break-small() { + width: fit-content; + } + + &:hover { + background-color: $dark-gray-900; + } + + &:focus { + background-color: $dark-gray-900; + box-shadow: + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; + } +} + +.components-snackbar__action.components-button { + margin-left: 32px; + color: $white; + height: auto; + flex-shrink: 0; + line-height: $default-line-height; + + &:not(:disabled):not([aria-disabled="true"]):not(.is-default) { + text-decoration: underline; + + &:hover { + color: $white; + text-decoration: none; + } + } +} + +.components-snackbar__content { + display: flex; + align-items: baseline; + justify-content: space-between; + line-height: $default-line-height; +} + +.components-snackbar-list { + position: absolute; + z-index: z-index(".components-snackbar-list"); + width: 100%; +} diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 66d20f3427df80..39c566be5ad526 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -35,6 +35,7 @@ @import "./sandbox/style.scss"; @import "./scroll-lock/style.scss"; @import "./select-control/style.scss"; +@import "./snackbar/style.scss"; @import "./spinner/style.scss"; @import "./text-control/style.scss"; @import "./textarea-control/style.scss"; diff --git a/packages/e2e-test-utils/src/publish-post-with-pre-publish-checks-disabled.js b/packages/e2e-test-utils/src/publish-post-with-pre-publish-checks-disabled.js index ab7914e4eb508a..e8e2acdd04dbc8 100644 --- a/packages/e2e-test-utils/src/publish-post-with-pre-publish-checks-disabled.js +++ b/packages/e2e-test-utils/src/publish-post-with-pre-publish-checks-disabled.js @@ -6,5 +6,5 @@ */ export async function publishPostWithPrePublishChecksDisabled() { await page.click( '.editor-post-publish-button' ); - return page.waitForSelector( '.components-notice.is-success' ); + return page.waitForSelector( '.components-snackbar' ); } diff --git a/packages/e2e-test-utils/src/publish-post.js b/packages/e2e-test-utils/src/publish-post.js index 106e6c2f9abfb9..694f40ec095178 100644 --- a/packages/e2e-test-utils/src/publish-post.js +++ b/packages/e2e-test-utils/src/publish-post.js @@ -16,5 +16,5 @@ export async function publishPost() { await page.click( '.editor-post-publish-button' ); // A success notice should show up - return page.waitForSelector( '.components-notice.is-success' ); + return page.waitForSelector( '.components-snackbar' ); } diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index 73d7b9117bf43b..a8ab98c2dd3181 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -42,7 +42,7 @@ describe( 'Reusable Blocks', () => { // Wait for creation to finish await page.waitForXPath( - '//*[contains(@class, "components-notice") and contains(@class, "is-success")]/*[text()="Block created."]' + '//*[contains(@class, "components-snackbar")]/*[text()="Block created."]' ); // Select all of the text in the title field by triple-clicking on it. We @@ -84,7 +84,7 @@ describe( 'Reusable Blocks', () => { // Wait for creation to finish await page.waitForXPath( - '//*[contains(@class, "components-notice") and contains(@class, "is-success")]/*[text()="Block created."]' + '//*[contains(@class, "components-snackbar")]/*[text()="Block created."]' ); // Save the reusable block @@ -184,7 +184,7 @@ describe( 'Reusable Blocks', () => { // Wait for deletion to finish await page.waitForXPath( - '//*[contains(@class, "components-notice") and contains(@class, "is-success")]/*[text()="Block deleted."]' + '//*[contains(@class, "components-snackbar")]/*[text()="Block deleted."]' ); // Check that we have an empty post again @@ -221,7 +221,7 @@ describe( 'Reusable Blocks', () => { // Wait for creation to finish await page.waitForXPath( - '//*[contains(@class, "components-notice") and contains(@class, "is-success")]/*[text()="Block created."]' + '//*[contains(@class, "components-snackbar")]/*[text()="Block created."]' ); // Select all of the text in the title field by triple-clicking on it. We diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 364208959d1b63..ee2b55bbed5d24 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -84,8 +84,7 @@ function Layout( { aria-label={ __( 'Editor content' ) } tabIndex="-1" > - <EditorNotices dismissible={ false } className="is-pinned" /> - <EditorNotices dismissible={ true } /> + <EditorNotices /> <PreserveScrollInReorder /> <EditorModeKeyboardShortcuts /> <KeyboardShortcutHelpModal /> diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index dc40e6321ced31..4c6458afe2f051 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -6,34 +6,6 @@ .edit-post-layout { position: relative; - .components-notice-list { - position: sticky; - top: $header-height; - right: 0; - color: $dark-gray-900; - - @include break-small { - top: 0; - } - - // Non-dismissible notices. - &.is-pinned { - position: relative; - left: 0; - top: 0; - } - } - - .components-notice { - margin: 0 0 5px; - padding: 6px 12px; - min-height: $panel-header-height; - - .components-notice__dismiss { - margin: 10px 5px; - } - } - // Beyond the mobile breakpoint, the editor bar is fixed, so make room for it eabove the layout content. @include break-small { padding-top: $header-height; @@ -54,6 +26,16 @@ } } +// Adjust the position of the notices +.edit-post-layout__content .components-editor-notices__snackbar { + position: fixed; + right: 0; + bottom: 20px; + padding-left: 16px; + padding-right: 16px; +} +@include editor-left(".edit-post-layout__content .components-editor-notices__snackbar"); + .edit-post-layout__content { display: flex; flex-direction: column; diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index f899f4a61e0ff2..fdab17a01b523c 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -6,7 +6,7 @@ import { filter } from 'lodash'; /** * WordPress dependencies */ -import { NoticeList } from '@wordpress/components'; +import { NoticeList, SnackbarList } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -15,17 +15,38 @@ import { compose } from '@wordpress/compose'; */ import TemplateValidationNotice from '../template-validation-notice'; -export function EditorNotices( { dismissible, notices, ...props } ) { - if ( dismissible !== undefined ) { - notices = filter( notices, { isDismissible: dismissible } ); - } +export function EditorNotices( { notices, onRemove } ) { + const dismissibleNotices = filter( notices, { + isDismissible: true, + type: 'default', + } ); + const nonDismissibleNotices = filter( notices, { + isDismissible: false, + type: 'default', + } ); + const snackbarNotices = filter( notices, { + type: 'snackbar', + } ); return ( - <NoticeList notices={ notices } { ...props }> - { dismissible !== false && ( + <> + <NoticeList + notices={ nonDismissibleNotices } + className="components-editor-notices__pinned" + /> + <NoticeList + notices={ dismissibleNotices } + className="components-editor-notices__dismissible" + onRemove={ onRemove } + > <TemplateValidationNotice /> - ) } - </NoticeList> + </NoticeList> + <SnackbarList + notices={ snackbarNotices } + className="components-editor-notices__snackbar" + onRemove={ onRemove } + /> + </> ); } diff --git a/packages/editor/src/components/editor-notices/style.scss b/packages/editor/src/components/editor-notices/style.scss new file mode 100644 index 00000000000000..e3387e1e653311 --- /dev/null +++ b/packages/editor/src/components/editor-notices/style.scss @@ -0,0 +1,37 @@ +// Dismissible notices. +.components-editor-notices__dismissible { + position: sticky; + top: $header-height; + right: 0; + color: $dark-gray-900; + + @include break-small { + top: 0; + } +} + +// Non-dismissible notices. +.components-editor-notices__pinned { + position: relative; + left: 0; + top: 0; + right: 0; + color: $dark-gray-900; +} + +.components-editor-notices__dismissible, +.components-editor-notices__pinned { + .components-notice { + margin: 0 0 5px; + padding: 6px 12px; + min-height: $panel-header-height; + + .components-notice__dismiss { + margin: 10px 5px; + } + } +} + +.components-editor-notices__snackbar { + width: 100%; +} diff --git a/packages/editor/src/components/editor-notices/test/index.js b/packages/editor/src/components/editor-notices/test/index.js deleted file mode 100644 index 655a990aa174fe..00000000000000 --- a/packages/editor/src/components/editor-notices/test/index.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - -/** - * Internal dependencies - */ -import { EditorNotices } from '../'; - -describe( 'EditorNotices', () => { - const notices = [ - { content: 'Eat your vegetables!', isDismissible: true }, - { content: 'Brush your teeth!', isDismissible: true }, - { content: 'Existence is fleeting!', isDismissible: false }, - ]; - - it( 'renders all notices', () => { - const wrapper = shallow( <EditorNotices notices={ notices } /> ); - expect( wrapper.prop( 'notices' ) ).toHaveLength( 3 ); - expect( wrapper.children() ).toHaveLength( 1 ); - } ); - - it( 'renders only dismissible notices', () => { - const wrapper = shallow( <EditorNotices notices={ notices } dismissible={ true } /> ); - expect( wrapper.prop( 'notices' ) ).toHaveLength( 2 ); - expect( wrapper.children() ).toHaveLength( 1 ); - } ); - - it( 'renders only non-dismissible notices', () => { - const wrapper = shallow( <EditorNotices notices={ notices } dismissible={ false } /> ); - expect( wrapper.prop( 'notices' ) ).toHaveLength( 1 ); - expect( wrapper.children() ).toHaveLength( 0 ); - } ); -} ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 4c340057c64713..03548de3f93abc 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -356,12 +356,12 @@ export function* savePost( options = {} ) { yield dispatch( 'core/notices', 'removeNotice', - SAVE_POST_NOTICE_ID, + SAVE_POST_NOTICE_ID ); yield dispatch( 'core/notices', 'removeNotice', - 'autosave-exists', + 'autosave-exists' ); } diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index 63bc00169c543d..ebd4caee7fbed8 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -133,6 +133,7 @@ export const saveReusableBlocks = async ( action, store ) => { const message = isTemporary ? __( 'Block created.' ) : __( 'Block updated.' ); dataDispatch( 'core/notices' ).createSuccessNotice( message, { id: REUSABLE_BLOCK_NOTICE_ID, + type: 'snackbar', } ); dataDispatch( 'core/block-editor' ).__unstableSaveReusableBlock( id, updatedReusableBlock.id ); @@ -199,6 +200,7 @@ export const deleteReusableBlocks = async ( action, store ) => { const message = __( 'Block deleted.' ); dataDispatch( 'core/notices' ).createSuccessNotice( message, { id: REUSABLE_BLOCK_NOTICE_ID, + type: 'snackbar', } ); } catch ( error ) { dispatch( { diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index f0ec7c84dab15c..ab698ea82a7315 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -409,7 +409,7 @@ describe( 'Post generator actions', () => { 'createSuccessNotice', ...[ savedPostMessage, - { actions: [], id: 'SAVE_POST_NOTICE_ID' }, + { actions: [], id: 'SAVE_POST_NOTICE_ID', type: 'snackbar' }, ] ); expect( value ).toEqual( expected ); diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index 4ef98c74e3a548..9732cff6fa7cfd 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -67,10 +67,12 @@ export function getNotificationArgumentsForSaveSuccess( data ) { noticeMessage, { id: SAVE_POST_NOTICE_ID, + type: 'snackbar', actions, }, ]; } + return []; } @@ -103,7 +105,9 @@ export function getNotificationArgumentsForSaveFail( data ) { messages[ edits.status ] : __( 'Updating failed' ); - return [ noticeMessage, { id: SAVE_POST_NOTICE_ID } ]; + return [ noticeMessage, { + id: SAVE_POST_NOTICE_ID, + } ]; } /** @@ -118,6 +122,8 @@ export function getNotificationArgumentsForTrashFail( data ) { data.error.message && data.error.code !== 'unknown_error' ? data.error.message : __( 'Trashing failed' ), - { id: TRASH_POST_NOTICE_ID }, + { + id: TRASH_POST_NOTICE_ID, + }, ]; } diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js index a78d03f81fad79..e67215113c1f19 100644 --- a/packages/editor/src/store/utils/test/notice-builder.js +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -28,7 +28,7 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { link: 'some_link', }; const post = { ...previousPost }; - const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID, actions: [] }; + const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID, actions: [], type: 'snackbar' }; [ [ 'when previous post is not published and post will not be published', diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 63f0e18c9974db..949f751bd72af5 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -1,5 +1,6 @@ @import "./components/autocompleters/style.scss"; @import "./components/document-outline/style.scss"; +@import "./components/editor-notices/style.scss"; @import "./components/error-boundary/style.scss"; @import "./components/page-attributes/style.scss"; @import "./components/post-excerpt/style.scss"; diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 3ceb48ea97a3ff..7a6fde4663c907 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Features + +- Support a new `snackbar` notice type in the `createNotice` action. + ## 1.1.2 (2019-01-03) ## 1.1.1 (2018-12-12) diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js index af74f5b6116fea..5ea46e30a4d65d 100644 --- a/packages/notices/src/store/actions.js +++ b/packages/notices/src/store/actions.js @@ -38,6 +38,7 @@ export function* createNotice( status = DEFAULT_STATUS, content, options = {} ) context = DEFAULT_CONTEXT, id = uniqueId( context ), actions = [], + type = 'default', __unstableHTML, } = options; @@ -47,7 +48,11 @@ export function* createNotice( status = DEFAULT_STATUS, content, options = {} ) content = String( content ); if ( speak ) { - yield { type: 'SPEAK', message: content }; + yield { + type: 'SPEAK', + message: content, + ariaLive: type === 'snackbar' ? 'polite' : 'assertive', + }; } yield { @@ -60,6 +65,7 @@ export function* createNotice( status = DEFAULT_STATUS, content, options = {} ) __unstableHTML, isDismissible, actions, + type, }, }; } diff --git a/packages/notices/src/store/controls.js b/packages/notices/src/store/controls.js index c23e1cbd171682..80c20900dd5f3a 100644 --- a/packages/notices/src/store/controls.js +++ b/packages/notices/src/store/controls.js @@ -5,6 +5,6 @@ import { speak } from '@wordpress/a11y'; export default { SPEAK( action ) { - speak( action.message, 'assertive' ); + speak( action.message, action.ariaLive || 'assertive' ); }, }; diff --git a/packages/notices/src/store/test/actions.js b/packages/notices/src/store/test/actions.js index ac6bc522f273ac..082949031cbd96 100644 --- a/packages/notices/src/store/test/actions.js +++ b/packages/notices/src/store/test/actions.js @@ -34,6 +34,7 @@ describe( 'actions', () => { isDismissible: true, id: expect.any( String ), actions: [], + type: 'default', }, } ); } ); @@ -55,6 +56,7 @@ describe( 'actions', () => { isDismissible: true, id: expect.any( String ), actions: [], + type: 'default', }, } ); } ); @@ -83,6 +85,7 @@ describe( 'actions', () => { content, isDismissible: false, actions: [], + type: 'default', }, } ); } ); @@ -109,6 +112,7 @@ describe( 'actions', () => { __unstableHTML: true, isDismissible: false, actions: [], + type: 'default', }, } ); } ); @@ -133,6 +137,7 @@ describe( 'actions', () => { content, isDismissible: true, id: expect.any( String ), + type: 'default', }, } ); } ); @@ -157,6 +162,7 @@ describe( 'actions', () => { content, isDismissible: true, id: expect.any( String ), + type: 'default', }, } ); } ); @@ -181,6 +187,7 @@ describe( 'actions', () => { content, isDismissible: true, id: expect.any( String ), + type: 'default', }, } ); } ); @@ -205,6 +212,7 @@ describe( 'actions', () => { content, isDismissible: true, id: expect.any( String ), + type: 'default', }, } ); } ); diff --git a/packages/notices/src/store/test/reducer.js b/packages/notices/src/store/test/reducer.js index 04318796773ecd..77869efe8d51b2 100644 --- a/packages/notices/src/store/test/reducer.js +++ b/packages/notices/src/store/test/reducer.js @@ -36,6 +36,7 @@ describe( 'reducer', () => { status: 'error', isDismissible: true, actions: [], + type: 'default', }, ], } ); @@ -53,6 +54,7 @@ describe( 'reducer', () => { status: 'error', isDismissible: true, actions: [], + type: 'default', }, ], } ); @@ -73,6 +75,7 @@ describe( 'reducer', () => { status: 'error', isDismissible: true, actions: [], + type: 'default', }, { id: expect.any( String ), @@ -80,6 +83,7 @@ describe( 'reducer', () => { status: 'success', isDismissible: true, actions: [], + type: 'default', }, ], } ); @@ -134,6 +138,7 @@ describe( 'reducer', () => { status: 'error', isDismissible: true, actions: [], + type: 'default', }, ], } ); From 95e73d998c1c8c17727a306ccc4fb1c0a82bc0a3 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Wed, 29 May 2019 15:54:00 +0200 Subject: [PATCH 214/664] Do not set the background color for Android (#15857) --- packages/block-library/src/video/edit.native.js | 2 +- packages/block-library/src/video/style.ios.scss | 8 ++++++++ packages/block-library/src/video/style.native.scss | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 packages/block-library/src/video/style.ios.scss diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 2d295ead99f7d8..17b6c416f016d9 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -214,7 +214,7 @@ class VideoEdit extends React.Component { { showVideo && isURL( src ) && <Video isSelected={ isSelected } - style={ [ videoStyle, { backgroundColor: 'black' } ] } + style={ videoStyle } source={ { uri: src } } paused={ true } muted={ true } diff --git a/packages/block-library/src/video/style.ios.scss b/packages/block-library/src/video/style.ios.scss new file mode 100644 index 00000000000000..d13a8ad1801291 --- /dev/null +++ b/packages/block-library/src/video/style.ios.scss @@ -0,0 +1,8 @@ +// @format + +@import "./style.native.scss"; + +.video { + background-color: #000; +} + diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss index c9f3279bdd9fbc..9825ae09babe83 100644 --- a/packages/block-library/src/video/style.native.scss +++ b/packages/block-library/src/video/style.native.scss @@ -1,7 +1,6 @@ // @format .video { - background-color: $gray-lighten-30; width: 100%; } From 13e58516cc34dfd3fabc569ef2177c332091964e Mon Sep 17 00:00:00 2001 From: Thorsten Frommen <info@tfrommen.de> Date: Wed, 29 May 2019 16:28:36 +0200 Subject: [PATCH 215/664] ESLint: Explicitly unignore ESLint config (#15887) --- .eslintignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.eslintignore b/.eslintignore index 416bce0a8e0567..9cd32291e32608 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,5 @@ node_modules packages/e2e-tests/plugins vendor packages/block-serialization-spec-parser/parser.js + +!.eslintrc.js From 992a30221ae4e0e0252ec2d110c54a16fa08fe3e Mon Sep 17 00:00:00 2001 From: Matthew Kevins <mkevins@users.noreply.github.com> Date: Thu, 30 May 2019 17:40:18 +1000 Subject: [PATCH 216/664] [Mobile] Fix caret position after inline paste - after selection refactor (#15701) * Rename variables for clarity in inline paste * Update selection via onSelectionUpdate after inline paste * Remove unused forceSelectionUpdate function --- .../src/components/rich-text/index.native.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 13fcc41771e1c7..5779e6459db14d 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -552,14 +552,14 @@ export class RichText extends Component { if ( typeof pastedContent === 'string' ) { const recordToInsert = create( { html: pastedContent } ); - const insertedContent = insert( currentRecord, recordToInsert ); - const newContent = this.valueToFormat( insertedContent ); + const resultingRecord = insert( currentRecord, recordToInsert ); + const resultingContent = this.valueToFormat( resultingRecord ); this.lastEventCount = undefined; - this.value = newContent; + this.value = resultingContent; // explicitly set selection after inline paste - this.forceSelectionUpdate( insertedContent.start, insertedContent.end ); + this.onSelectionChange( resultingRecord.start, resultingRecord.end ); this.props.onChange( this.value ); } else if ( onSplit ) { @@ -673,15 +673,6 @@ export class RichText extends Component { return value; } - forceSelectionUpdate( start, end ) { - if ( ! this.needsSelectionUpdate ) { - this.needsSelectionUpdate = true; - this.selectionStart = start; - this.selectionEnd = end; - this.forceUpdate(); - } - } - shouldComponentUpdate( nextProps ) { if ( nextProps.tagName !== this.props.tagName ) { this.lastEventCount = undefined; From 3f987b5968e287606f9f252b4db96d139df1d80a Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 30 May 2019 10:44:32 +0100 Subject: [PATCH 217/664] Add end 2 end test heading color (#15784) --- .../blocks/__snapshots__/heading.test.js.snap | 24 ++++++++ .../e2e-tests/specs/blocks/heading.test.js | 60 ++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap index 71bb261abc29a8..184fb44ef12bc2 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap @@ -12,6 +12,30 @@ exports[`Heading can be created by prefixing number sign and a space 1`] = ` <!-- /wp:heading -->" `; +exports[`Heading it should correctly apply custom colors 1`] = ` +"<!-- wp:heading {\\"level\\":3,\\"customBackgroundColor\\":\\"#ab4567\\"} --> +<h3 class=\\"has-background\\" style=\\"background-color:#ab4567\\">Heading</h3> +<!-- /wp:heading -->" +`; + +exports[`Heading it should correctly apply custom colors 2`] = ` +"<!-- wp:heading {\\"level\\":3,\\"customTextColor\\":\\"#181717\\",\\"customBackgroundColor\\":\\"#ab4567\\"} --> +<h3 class=\\"has-background\\" style=\\"background-color:#ab4567;color:#181717\\">Heading</h3> +<!-- /wp:heading -->" +`; + +exports[`Heading it should correctly apply named colors 1`] = ` +"<!-- wp:heading {\\"backgroundColor\\":\\"primary\\"} --> +<h2 class=\\"has-background has-primary-background-color\\">Heading</h2> +<!-- /wp:heading -->" +`; + +exports[`Heading it should correctly apply named colors 2`] = ` +"<!-- wp:heading {\\"textColor\\":\\"white\\",\\"backgroundColor\\":\\"primary\\"} --> +<h2 class=\\"has-background has-white-color has-primary-background-color\\">Heading</h2> +<!-- /wp:heading -->" +`; + exports[`Heading should create a paragraph block above when pressing enter at the start 1`] = ` "<!-- wp:paragraph --> <p></p> diff --git a/packages/e2e-tests/specs/blocks/heading.test.js b/packages/e2e-tests/specs/blocks/heading.test.js index 2690ad4690b5b6..c895538b97671d 100644 --- a/packages/e2e-tests/specs/blocks/heading.test.js +++ b/packages/e2e-tests/specs/blocks/heading.test.js @@ -3,11 +3,21 @@ */ import { clickBlockAppender, - getEditedPostContent, createNewPost, + getEditedPostContent, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; describe( 'Heading', () => { + const BACKGROUND_COLOR_TEXT = 'Background Color'; + const TEXT_COLOR_TEXT = 'Text Color'; + const CUSTOM_COLOR_TEXT = 'Custom Color'; + const BACKGROUND_COLOR_UI_X_SELECTOR = `//div[./span[contains(text(),'${ BACKGROUND_COLOR_TEXT }')]]`; + const TEXT_COLOR_UI_X_SELECTOR = `//div[./span[contains(text(),'${ TEXT_COLOR_TEXT }')]]`; + const CUSTOM_COLOR_BUTTON_X_SELECTOR = `//button[contains(text(),'${ CUSTOM_COLOR_TEXT }')]`; + const COLOR_INPUT_FIELD_SELECTOR = '.components-color-palette__picker .components-text-control__input'; + const COLOR_PANEL_TOGGLE_X_SELECTOR = '//button[./span[contains(text(),\'Color Settings\')]]'; + beforeEach( async () => { await createNewPost(); } ); @@ -44,4 +54,52 @@ describe( 'Heading', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'it should correctly apply custom colors', async () => { + await clickBlockAppender(); + await page.keyboard.type( '### Heading' ); + const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); + await colorPanelToggle.click(); + const [ customBackgroundColorButton ] = await page.$x( + `${ BACKGROUND_COLOR_UI_X_SELECTOR }${ CUSTOM_COLOR_BUTTON_X_SELECTOR }` + ); + await customBackgroundColorButton.click(); + await page.click( COLOR_INPUT_FIELD_SELECTOR ); + await pressKeyWithModifier( 'primary', 'A' ); + await page.keyboard.type( '#ab4567' ); + await page.click( '.wp-block-heading' ); + await page.waitForSelector( '.component-color-indicator[aria-label="(background color: #ab4567)"]' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + + const [ customTextColorButton ] = await page.$x( + `${ TEXT_COLOR_UI_X_SELECTOR }${ CUSTOM_COLOR_BUTTON_X_SELECTOR }` + ); + await customTextColorButton.click(); + await page.click( COLOR_INPUT_FIELD_SELECTOR ); + await pressKeyWithModifier( 'primary', 'A' ); + await page.keyboard.type( '#181717' ); + await page.click( '.wp-block-heading' ); + await page.waitForSelector( '.component-color-indicator[aria-label="(text color: #181717)"]' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'it should correctly apply named colors', async () => { + await clickBlockAppender(); + await page.keyboard.type( '## Heading' ); + const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); + await colorPanelToggle.click(); + const primaryColorButtonSelector = `${ BACKGROUND_COLOR_UI_X_SELECTOR }//button[@aria-label='Color: Primary']`; + const [ primaryColorButton ] = await page.$x( primaryColorButtonSelector ); + await primaryColorButton.click(); + await page.click( '.wp-block-heading' ); + await page.waitForXPath( `${ primaryColorButtonSelector }[@aria-pressed='true']` ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + + const whiteColorButtonSelector = `${ TEXT_COLOR_UI_X_SELECTOR }//button[@aria-label='Color: White']`; + const [ whiteColorButton ] = await page.$x( whiteColorButtonSelector ); + await whiteColorButton.click(); + await page.click( '.wp-block-heading' ); + await page.waitForXPath( `${ whiteColorButtonSelector }[@aria-pressed='true']` ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 7cef03f62adf740fea9413bea1fb9d2c250fe5e6 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Thu, 30 May 2019 15:00:42 +0300 Subject: [PATCH 218/664] [RNMobile] Focus RichText on mount if block is selected and says so (#15878) * Focus RichText on mount if block is selected and says so Some blocks have multiple RichText or a RichText among other children. Example: Quote blocks has 2 RichTexts and Image block has a RichText for the caption. We want to control when and which of those RichTexts will request focus. On the web side, the DOM is used to search for a inputbox to focus but on RN, we don't have a DOM to search. Instead, this commit makes the assumption that a RichText always request focus if its parent has passed `true` in `isSelected` and only if the parent hasn't inhibited that behavior by using the `noFocusOnMount` prop. * Simplify the passing of noFocusOnMount * Fix grammar in comment * Rename prop to mark it as unstable --- .../src/components/rich-text/index.native.js | 15 +++++++++------ packages/block-library/src/image/edit.native.js | 1 + packages/block-library/src/quote/edit.js | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 5779e6459db14d..a89f293ff8efba 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -716,7 +716,11 @@ export class RichText extends Component { } componentDidMount() { - if ( this.props.isSelected ) { + // Request focus if wrapping block is selected and parent hasn't inhibited the focus request. This method of focusing + // is trying to implement the web-side counterpart of BlockList's `focusTabbable` where the BlockList is focusing an + // inputbox by searching the DOM. We don't have the DOM in RN so, using the combination of blockIsSelected and __unstableMobileNoFocusOnMount + // to determine if we should focus the RichText. + if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { this._editor.focus(); this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } @@ -880,16 +884,13 @@ RichText.defaultProps = { const RichTextContainer = compose( [ withInstanceId, withBlockEditContext( ( { clientId, onFocus, onCaretVerticalPositionChange, isSelected }, ownProps ) => { - // ownProps.onFocus and isSelected needs precedence over the block edit context - if ( ownProps.isSelected !== undefined ) { - isSelected = ownProps.isSelected; - } + // ownProps.onFocus needs precedence over the block edit context if ( ownProps.onFocus !== undefined ) { onFocus = ownProps.onFocus; } return { - isSelected, clientId, + blockIsSelected: ownProps.isSelected !== undefined ? ownProps.isSelected : isSelected, onFocus, onCaretVerticalPositionChange, }; @@ -899,6 +900,7 @@ const RichTextContainer = compose( [ instanceId, identifier = instanceId, isSelected, + blockIsSelected, } ) => { const { getFormatTypes } = select( 'core/rich-text' ); const { @@ -921,6 +923,7 @@ const RichTextContainer = compose( [ selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, + blockIsSelected, }; } ), withDispatch( ( dispatch, { diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 7ef23a0b0f9b89..0869ca4fd2a52e 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -370,6 +370,7 @@ class ImageEdit extends React.Component { onFocus={ this.onFocusCaption } onBlur={ this.props.onBlur } // always assign onBlur as props isSelected={ this.state.isCaptionSelected } + __unstableMobileNoFocusOnMount fontSize={ 14 } underlineColorAndroid="transparent" textAlign={ 'center' } diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 8955b78588bc3f..079db78e86493e 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -48,6 +48,7 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg citation: nextCitation, } ) } + __unstableMobileNoFocusOnMount placeholder={ // translators: placeholder text used for the citation __( 'Write citation…' ) From efcb62c4519ba10a958964f3c179f2e900585568 Mon Sep 17 00:00:00 2001 From: Arun Sathiya <arun@sathiya.me> Date: Thu, 30 May 2019 17:40:34 +0530 Subject: [PATCH 219/664] Update link on the Gutenberg build files notice (#15697) --- gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gutenberg.php b/gutenberg.php index e1f216512fceac..1cef2b3380e4bf 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -89,7 +89,7 @@ function gutenberg_wordpress_version_notice() { */ function gutenberg_build_files_notice() { echo '<div class="error"><p>'; - _e( 'Gutenberg development mode requires files to be built. Run <code>npm install</code> to install dependencies, <code>npm run build</code> to build the files or <code>npm run dev</code> to build the files and watch for changes. Read the <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md">contributing</a> file for more information.', 'gutenberg' ); + _e( 'Gutenberg development mode requires files to be built. Run <code>npm install</code> to install dependencies, <code>npm run build</code> to build the files or <code>npm run dev</code> to build the files and watch for changes. Read the <a href="https://github.com/WordPress/gutenberg/blob/master/docs/contributors/getting-started.md">contributing</a> file for more information.', 'gutenberg' ); echo '</p></div>'; } From e88b5ccca01d9dee29874e6d3fb7dc3b21b3361a Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 30 May 2019 13:33:04 +0100 Subject: [PATCH 220/664] Remove background color text from heading; Fix end to end tests; (#15917) --- .../blocks/__snapshots__/heading.test.js.snap | 20 ++++--------------- .../e2e-tests/specs/blocks/heading.test.js | 18 ----------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap index 184fb44ef12bc2..093ceb0da4e9d5 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap @@ -13,26 +13,14 @@ exports[`Heading can be created by prefixing number sign and a space 1`] = ` `; exports[`Heading it should correctly apply custom colors 1`] = ` -"<!-- wp:heading {\\"level\\":3,\\"customBackgroundColor\\":\\"#ab4567\\"} --> -<h3 class=\\"has-background\\" style=\\"background-color:#ab4567\\">Heading</h3> -<!-- /wp:heading -->" -`; - -exports[`Heading it should correctly apply custom colors 2`] = ` -"<!-- wp:heading {\\"level\\":3,\\"customTextColor\\":\\"#181717\\",\\"customBackgroundColor\\":\\"#ab4567\\"} --> -<h3 class=\\"has-background\\" style=\\"background-color:#ab4567;color:#181717\\">Heading</h3> +"<!-- wp:heading {\\"level\\":3,\\"customTextColor\\":\\"#181717\\"} --> +<h3 style=\\"color:#181717\\">Heading</h3> <!-- /wp:heading -->" `; exports[`Heading it should correctly apply named colors 1`] = ` -"<!-- wp:heading {\\"backgroundColor\\":\\"primary\\"} --> -<h2 class=\\"has-background has-primary-background-color\\">Heading</h2> -<!-- /wp:heading -->" -`; - -exports[`Heading it should correctly apply named colors 2`] = ` -"<!-- wp:heading {\\"textColor\\":\\"white\\",\\"backgroundColor\\":\\"primary\\"} --> -<h2 class=\\"has-background has-white-color has-primary-background-color\\">Heading</h2> +"<!-- wp:heading {\\"textColor\\":\\"white\\"} --> +<h2 class=\\"has-white-color\\">Heading</h2> <!-- /wp:heading -->" `; diff --git a/packages/e2e-tests/specs/blocks/heading.test.js b/packages/e2e-tests/specs/blocks/heading.test.js index c895538b97671d..cb46f7072b0688 100644 --- a/packages/e2e-tests/specs/blocks/heading.test.js +++ b/packages/e2e-tests/specs/blocks/heading.test.js @@ -9,10 +9,8 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'Heading', () => { - const BACKGROUND_COLOR_TEXT = 'Background Color'; const TEXT_COLOR_TEXT = 'Text Color'; const CUSTOM_COLOR_TEXT = 'Custom Color'; - const BACKGROUND_COLOR_UI_X_SELECTOR = `//div[./span[contains(text(),'${ BACKGROUND_COLOR_TEXT }')]]`; const TEXT_COLOR_UI_X_SELECTOR = `//div[./span[contains(text(),'${ TEXT_COLOR_TEXT }')]]`; const CUSTOM_COLOR_BUTTON_X_SELECTOR = `//button[contains(text(),'${ CUSTOM_COLOR_TEXT }')]`; const COLOR_INPUT_FIELD_SELECTOR = '.components-color-palette__picker .components-text-control__input'; @@ -60,16 +58,6 @@ describe( 'Heading', () => { await page.keyboard.type( '### Heading' ); const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); await colorPanelToggle.click(); - const [ customBackgroundColorButton ] = await page.$x( - `${ BACKGROUND_COLOR_UI_X_SELECTOR }${ CUSTOM_COLOR_BUTTON_X_SELECTOR }` - ); - await customBackgroundColorButton.click(); - await page.click( COLOR_INPUT_FIELD_SELECTOR ); - await pressKeyWithModifier( 'primary', 'A' ); - await page.keyboard.type( '#ab4567' ); - await page.click( '.wp-block-heading' ); - await page.waitForSelector( '.component-color-indicator[aria-label="(background color: #ab4567)"]' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); const [ customTextColorButton ] = await page.$x( `${ TEXT_COLOR_UI_X_SELECTOR }${ CUSTOM_COLOR_BUTTON_X_SELECTOR }` @@ -88,12 +76,6 @@ describe( 'Heading', () => { await page.keyboard.type( '## Heading' ); const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); await colorPanelToggle.click(); - const primaryColorButtonSelector = `${ BACKGROUND_COLOR_UI_X_SELECTOR }//button[@aria-label='Color: Primary']`; - const [ primaryColorButton ] = await page.$x( primaryColorButtonSelector ); - await primaryColorButton.click(); - await page.click( '.wp-block-heading' ); - await page.waitForXPath( `${ primaryColorButtonSelector }[@aria-pressed='true']` ); - expect( await getEditedPostContent() ).toMatchSnapshot(); const whiteColorButtonSelector = `${ TEXT_COLOR_UI_X_SELECTOR }//button[@aria-label='Color: White']`; const [ whiteColorButton ] = await page.$x( whiteColorButtonSelector ); From 36ac702bd9604645331391de33a39260d48f34b4 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <asmussen@gmail.com> Date: Thu, 30 May 2019 14:58:26 +0200 Subject: [PATCH 221/664] Fix menu item colors (#15531) * Fix menu item colors Fixes #15387 This PR fixes an inconsistency in menu item colors. There were some redundant CSS that had not been cleaned up properly with the move to the color mixins. Additionally it replaces the use of opacity for the lighter text in the menus, and uses the color variables instead, as those colors do not need to work on differently colored backgrounds, just white. * Address feedback. --- .../src/components/block-settings-menu/style.scss | 2 +- packages/components/src/menu-item/style.scss | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/block-settings-menu/style.scss b/packages/block-editor/src/components/block-settings-menu/style.scss index 39b62ec34b59de..197303805352e6 100644 --- a/packages/block-editor/src/components/block-settings-menu/style.scss +++ b/packages/block-editor/src/components/block-settings-menu/style.scss @@ -39,9 +39,9 @@ background: none; outline: none; border-radius: 0; - color: $dark-gray-500; text-align: left; cursor: pointer; + color: $dark-gray-600; @include menu-style__neutral; &:hover:not(:disabled):not([aria-disabled="true"]) { diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 8f750618c2a159..7ef38dfc8c3036 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -4,6 +4,7 @@ padding: $grid-size ($grid-size-large - $border-width); text-align: left; color: $dark-gray-600; + @include menu-style__neutral; // Target plugin icons that can have arbitrary classes by using an aggressive selector. .dashicon, @@ -18,7 +19,6 @@ } &:hover:not(:disabled):not([aria-disabled="true"]) { - color: $dark-gray-500; // Disable hover style on mobile to prevent odd scroll behaviour. // See: https://github.com/WordPress/gutenberg/pull/10333 @include break-medium() { @@ -26,7 +26,7 @@ } .components-menu-item__shortcut { - opacity: 1; + color: $dark-gray-600; } } @@ -43,12 +43,12 @@ .components-menu-item__info { margin-top: $grid-size-small; font-size: $default-font-size - 1px; - opacity: 0.84; + color: $dark-gray-300; } .components-menu-item__shortcut { align-self: center; - opacity: 0.84; + color: $dark-gray-300; margin-right: 0; margin-left: auto; padding-left: $grid-size; From 3d007a053eb4199e15c2ba394688333d5284f28f Mon Sep 17 00:00:00 2001 From: Yorick Brown <yisforyorick@gmail.com> Date: Thu, 30 May 2019 16:18:13 +0200 Subject: [PATCH 222/664] Update extending-the-block-editor.md (#14841) --- .../tutorials/javascript/extending-the-block-editor.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md index 8a451c3d72361f..19a18057187629 100644 --- a/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md +++ b/docs/designers-developers/developers/tutorials/javascript/extending-the-block-editor.md @@ -22,7 +22,7 @@ Plugin Name: Fancy Quote function myguten_enqueue() { wp_enqueue_script( 'myguten-script', plugins_url( 'myguten.js', __FILE__ ), - array( 'wp-blocks') + array( 'wp-blocks' ) ); } add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); @@ -34,7 +34,9 @@ See [Packages](/docs/designers-developers/developers/packages.md) for list of av After you have updated both JavaScript and PHP files, go to the block editor and create a new post. -Add a quote block, and in the right sidebar under Styles, you will see your new Fancy Quote style listed. Click the Fancy Quote to select and apply that style to your quote block: +Add a quote block, and in the right sidebar under Styles, you will see your new Fancy Quote style listed. + +Click the Fancy Quote to select and apply that style to your quote block: ![Fancy Quote Style in Inspector](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/fancy-quote-in-inspector.png) @@ -42,7 +44,7 @@ Add a quote block, and in the right sidebar under Styles, you will see your new Even if you Preview or Publish the post you will not see a visible change. However, if you look at the source, you will see the `is-style-fancy-quote` class name is now attached to your quote block. -Let's add some style. Go ahead and create a `style.css` file with: +Let's add some style. In your plugin folder, create a `style.css` file with: ```css .is-style-fancy-quote { @@ -59,7 +61,7 @@ function myguten_stylesheet() { add_action( 'enqueue_block_assets', 'myguten_stylesheet' ); ``` -Now when you view in the editor and published, you will see your Fancy Quote style, a delicious tomato color text: +Now when you view in the editor and publish, you will see your Fancy Quote style, a delicious tomato color text: ![Fancy Quote with Style](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/fancy-quote-with-style.png) From 54d6af897521f02e5ba00d9b20c28d7315e77f0f Mon Sep 17 00:00:00 2001 From: Marty Helmick <info@martyhelmick.com> Date: Thu, 30 May 2019 10:18:55 -0400 Subject: [PATCH 223/664] Move misplaced style imports to editor.css (#14607) * Move misplaced editor styles to editor.css * Add comment & rename files. --- .../src/block/edit-panel/{style.scss => editor.scss} | 0 .../src/block/indicator/{style.scss => editor.scss} | 0 packages/block-library/src/editor.scss | 5 +++++ packages/block-library/src/style.scss | 2 -- 4 files changed, 5 insertions(+), 2 deletions(-) rename packages/block-library/src/block/edit-panel/{style.scss => editor.scss} (100%) rename packages/block-library/src/block/indicator/{style.scss => editor.scss} (100%) diff --git a/packages/block-library/src/block/edit-panel/style.scss b/packages/block-library/src/block/edit-panel/editor.scss similarity index 100% rename from packages/block-library/src/block/edit-panel/style.scss rename to packages/block-library/src/block/edit-panel/editor.scss diff --git a/packages/block-library/src/block/indicator/style.scss b/packages/block-library/src/block/indicator/editor.scss similarity index 100% rename from packages/block-library/src/block/indicator/style.scss rename to packages/block-library/src/block/indicator/editor.scss diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index c0c465d33ffbf3..99493394ed43f4 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -34,6 +34,11 @@ @import "./verse/editor.scss"; @import "./video/editor.scss"; +/** + * Import styles from internal editor components used by the blocks. + */ +@import "./block/edit-panel/editor.scss"; +@import "./block/indicator/editor.scss"; /** * Editor Normalization Styles diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 73325d5d9167c1..7ebad4a855135e 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -1,6 +1,4 @@ @import "./audio/style.scss"; -@import "./block/edit-panel/style.scss"; -@import "./block/indicator/style.scss"; @import "./button/style.scss"; @import "./calendar/style.scss"; @import "./categories/style.scss"; From 1a6660c3ba6c8474512ca3a675aa3dda8caf2c8d Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Thu, 30 May 2019 10:35:56 -0400 Subject: [PATCH 224/664] Correct default appender insertter jumpiness in Safari (#15892) --- .../src/components/default-block-appender/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/style.scss index c5cb3ac744571b..565d12230d0be5 100644 --- a/packages/block-editor/src/components/default-block-appender/style.scss +++ b/packages/block-editor/src/components/default-block-appender/style.scss @@ -32,6 +32,7 @@ .block-editor-inserter__toggle:not([aria-expanded="true"]) { opacity: 0; transition: opacity 0.2s; + will-change: opacity; } &:hover { From 5c388082ca7025bf82c2cf3aae3abee5f05b8fe9 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 30 May 2019 10:50:17 -0400 Subject: [PATCH 225/664] Build Tooling: Optimize build by spawning worker pool (#15230) * Build Tooling: Create worker pool for build * Build Tooling: Define build concurrency explicitly * Revert "Build Tooling: Define build concurrency explicitly" This reverts commit 4338f9e09d9d502409fee00ea1062ea369d0f8c6. * Build Tooling: Implement build files sourcing as stream * Build Tooling: Reinstate error handling to build script * Build Tooling: Log build error to stderr Co-Authored-By: Daniel Richards <daniel.p.richards@gmail.com> * Build: Assign non-zero exit code on build error Co-Authored-By: Daniel Richards <talldan@users.noreply.github.com> --- bin/packages/build-worker.js | 159 +++++++++++++++++++++ bin/packages/build.js | 261 +++++++++-------------------------- package-lock.json | 114 ++++++++++++--- package.json | 7 +- 4 files changed, 327 insertions(+), 214 deletions(-) create mode 100644 bin/packages/build-worker.js diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js new file mode 100644 index 00000000000000..7e3e636c013a1d --- /dev/null +++ b/bin/packages/build-worker.js @@ -0,0 +1,159 @@ +/** + * External dependencies + */ +const { promisify } = require( 'util' ); +const fs = require( 'fs' ); +const path = require( 'path' ); +const babel = require( '@babel/core' ); +const makeDir = require( 'make-dir' ); +const sass = require( 'node-sass' ); +const postcss = require( 'postcss' ); + +/** + * Internal dependencies + */ +const getBabelConfig = require( './get-babel-config' ); + +/** + * Path to packages directory. + * + * @type {string} + */ +const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); + +/** + * Mapping of JavaScript environments to corresponding build output. + * + * @type {Object} + */ +const JS_ENVIRONMENTS = { + main: 'build', + module: 'build-module', +}; + +/** + * Promisified fs.readFile. + * + * @type {Function} + */ +const readFile = promisify( fs.readFile ); + +/** + * Promisified fs.writeFile. + * + * @type {Function} + */ +const writeFile = promisify( fs.writeFile ); + +/** + * Promisified sass.render. + * + * @type {Function} + */ +const renderSass = promisify( sass.render ); + +/** + * Get the package name for a specified file + * + * @param {string} file File name + * @return {string} Package name + */ +function getPackageName( file ) { + return path.relative( PACKAGES_DIR, file ).split( path.sep )[ 0 ]; +} + +/** + * Get Build Path for a specified file. + * + * @param {string} file File to build + * @param {string} buildFolder Output folder + * @return {string} Build path + */ +function getBuildPath( file, buildFolder ) { + const pkgName = getPackageName( file ); + const pkgSrcPath = path.resolve( PACKAGES_DIR, pkgName, 'src' ); + const pkgBuildPath = path.resolve( PACKAGES_DIR, pkgName, buildFolder ); + const relativeToSrcPath = path.relative( pkgSrcPath, file ); + return path.resolve( pkgBuildPath, relativeToSrcPath ); +} + +/** + * Object of build tasks per file extension. + * + * @type {Object<string,Function>} + */ +const BUILD_TASK_BY_EXTENSION = { + async '.scss'( file ) { + const outputFile = getBuildPath( file.replace( '.scss', '.css' ), 'build-style' ); + const outputFileRTL = getBuildPath( file.replace( '.scss', '-rtl.css' ), 'build-style' ); + + const [ , contents ] = await Promise.all( [ + makeDir( path.dirname( outputFile ) ), + readFile( file, 'utf8' ), + ] ); + + const builtSass = await renderSass( { + file, + includePaths: [ path.resolve( __dirname, '../../assets/stylesheets' ) ], + data: ( + [ + 'colors', + 'breakpoints', + 'variables', + 'mixins', + 'animations', + 'z-index', + ].map( ( imported ) => `@import "${ imported }";` ).join( ' ' ) + + contents + ), + } ); + + const result = await postcss( require( './post-css-config' ) ).process( builtSass.css, { + from: 'src/app.css', + to: 'dest/app.css', + } ); + + const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( result.css, { + from: 'src/app.css', + to: 'dest/app.css', + } ); + + await Promise.all( [ + writeFile( outputFile, result.css ), + writeFile( outputFileRTL, resultRTL.css ), + ] ); + }, + + async '.js'( file ) { + for ( const [ environment, buildDir ] of Object.entries( JS_ENVIRONMENTS ) ) { + const destPath = getBuildPath( file, buildDir ); + const babelOptions = getBabelConfig( environment, file.replace( PACKAGES_DIR, '@wordpress' ) ); + + const [ , transformed ] = await Promise.all( [ + makeDir( path.dirname( destPath ) ), + babel.transformFileAsync( file, babelOptions ), + ] ); + + await Promise.all( [ + writeFile( destPath + '.map', JSON.stringify( transformed.map ) ), + writeFile( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ), + ] ); + } + }, +}; + +module.exports = async ( file, callback ) => { + const extension = path.extname( file ); + const task = BUILD_TASK_BY_EXTENSION[ extension ]; + + if ( ! task ) { + return; + } + + try { + await task( file ); + callback(); + } catch ( error ) { + callback( error ); + } +}; diff --git a/bin/packages/build.js b/bin/packages/build.js index bb6954b4102e7c..0f77d13ff06df3 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -1,220 +1,91 @@ -/** - * script to build WordPress packages into `build/` directory. - * - * Example: - * node ./scripts/build.js - */ +/* eslint-disable no-console */ /** * External dependencies */ -const fs = require( 'fs' ); const path = require( 'path' ); -const glob = require( 'glob' ); -const babel = require( '@babel/core' ); -const chalk = require( 'chalk' ); -const mkdirp = require( 'mkdirp' ); -const sass = require( 'node-sass' ); -const postcss = require( 'postcss' ); -const deasync = require( 'deasync' ); - -/** - * Internal dependencies - */ -const getPackages = require( './get-packages' ); -const getBabelConfig = require( './get-babel-config' ); - -/** - * Module Constants - */ -const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); -const SRC_DIR = 'src'; -const BUILD_DIR = { - main: 'build', - module: 'build-module', - style: 'build-style', -}; -const DONE = chalk.reset.inverse.bold.green( ' DONE ' ); - -/** - * Get the package name for a specified file - * - * @param {string} file File name - * @return {string} Package name - */ -function getPackageName( file ) { - return path.relative( PACKAGES_DIR, file ).split( path.sep )[ 0 ]; -} - -const isJsFile = ( filepath ) => { - return /.\.js$/.test( filepath ); -}; +const glob = require( 'fast-glob' ); +const ProgressBar = require( 'progress' ); +const workerFarm = require( 'worker-farm' ); +const { Readable } = require( 'stream' ); -const isScssFile = ( filepath ) => { - return /.\.scss$/.test( filepath ); -}; +const files = process.argv.slice( 2 ); /** - * Get Build Path for a specified file + * Path to packages directory. * - * @param {string} file File to build - * @param {string} buildFolder Output folder - * @return {string} Build path + * @type {string} */ -function getBuildPath( file, buildFolder ) { - const pkgName = getPackageName( file ); - const pkgSrcPath = path.resolve( PACKAGES_DIR, pkgName, SRC_DIR ); - const pkgBuildPath = path.resolve( PACKAGES_DIR, pkgName, buildFolder ); - const relativeToSrcPath = path.relative( pkgSrcPath, file ); - return path.resolve( pkgBuildPath, relativeToSrcPath ); -} +const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); -/** - * Given a list of scss and js filepaths, divide them into sets them and rebuild. - * - * @param {Array} files list of files to rebuild - */ -function buildFiles( files ) { - // Reduce files into a unique sets of javaScript files and scss packages. - const buildPaths = files.reduce( ( accumulator, filePath ) => { - if ( isJsFile( filePath ) ) { - accumulator.jsFiles.add( filePath ); - } else if ( isScssFile( filePath ) ) { - const pkgName = getPackageName( filePath ); - const pkgPath = path.resolve( PACKAGES_DIR, pkgName ); - accumulator.scssPackagePaths.add( pkgPath ); - } - return accumulator; - }, { jsFiles: new Set(), scssPackagePaths: new Set() } ); +let onFileComplete = () => {}; - buildPaths.jsFiles.forEach( buildJsFile ); - buildPaths.scssPackagePaths.forEach( buildPackageScss ); -} +let stream; -/** - * Build a javaScript file for the required environments (node and ES5) - * - * @param {string} file File path to build - * @param {boolean} silent Show logs - */ -function buildJsFile( file, silent ) { - buildJsFileFor( file, silent, 'main' ); - buildJsFileFor( file, silent, 'module' ); -} - -/** - * Build a package's scss styles - * - * @param {string} packagePath The path to the package. - */ -function buildPackageScss( packagePath ) { - const srcDir = path.resolve( packagePath, SRC_DIR ); - const scssFiles = glob.sync( `${ srcDir }/*.scss` ); - - // Build scss files individually. - scssFiles.forEach( buildScssFile ); -} - -function buildScssFile( styleFile ) { - const outputFile = getBuildPath( styleFile.replace( '.scss', '.css' ), BUILD_DIR.style ); - const outputFileRTL = getBuildPath( styleFile.replace( '.scss', '-rtl.css' ), BUILD_DIR.style ); - mkdirp.sync( path.dirname( outputFile ) ); - const builtSass = sass.renderSync( { - file: styleFile, - includePaths: [ path.resolve( __dirname, '../../assets/stylesheets' ) ], - data: ( - [ - 'colors', - 'breakpoints', - 'variables', - 'mixins', - 'animations', - 'z-index', - ].map( ( imported ) => `@import "${ imported }";` ).join( ' ' ) + - fs.readFileSync( styleFile, 'utf8' ) - ), +if ( files.length ) { + stream = new Readable( { encoding: 'utf8' } ); + files.forEach( ( file ) => stream.push( file ) ); + stream.push( null ); +} else { + const bar = new ProgressBar( 'Build Progress: [:bar] :percent', { + width: 30, + incomplete: ' ', + total: 1, } ); - const postCSSSync = ( callback ) => { - postcss( require( './post-css-config' ) ) - .process( builtSass.css, { from: 'src/app.css', to: 'dest/app.css' } ) - .then( ( result ) => callback( null, result ) ); - }; - - const postCSSRTLSync = ( ltrCSS, callback ) => { - postcss( [ require( 'rtlcss' )() ] ) - .process( ltrCSS, { from: 'src/app.css', to: 'dest/app.css' } ) - .then( ( result ) => callback( null, result ) ); - }; - - const result = deasync( postCSSSync )(); - fs.writeFileSync( outputFile, result.css ); - - const resultRTL = deasync( postCSSRTLSync )( result ); - fs.writeFileSync( outputFileRTL, resultRTL ); -} - -/** - * Build a file for a specific environment - * - * @param {string} file File path to build - * @param {boolean} silent Show logs - * @param {string} environment Dist environment (node or es5) - */ -function buildJsFileFor( file, silent, environment ) { - const buildDir = BUILD_DIR[ environment ]; - const destPath = getBuildPath( file, buildDir ); - const babelOptions = getBabelConfig( environment, file.replace( PACKAGES_DIR, '@wordpress' ) ); - - mkdirp.sync( path.dirname( destPath ) ); - const transformed = babel.transformFileSync( file, babelOptions ); - fs.writeFileSync( destPath + '.map', JSON.stringify( transformed.map ) ); - fs.writeFileSync( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ); - - if ( ! silent ) { - process.stdout.write( - chalk.green( ' \u2022 ' ) + - path.relative( PACKAGES_DIR, file ) + - chalk.green( ' \u21D2 ' ) + - path.relative( PACKAGES_DIR, destPath ) + - '\n' - ); - } -} + bar.tick( 0 ); -/** - * Build the provided package path - * - * @param {string} packagePath absolute package path - */ -function buildPackage( packagePath ) { - const srcDir = path.resolve( packagePath, SRC_DIR ); - const jsFiles = glob.sync( `${ srcDir }/**/*.js`, { + stream = glob.stream( [ + `${ PACKAGES_DIR }/*/src/**/*.js`, + `${ PACKAGES_DIR }/*/src/*.scss`, + ], { ignore: [ - `${ srcDir }/**/test/**/*.js`, - `${ srcDir }/**/__mocks__/**/*.js`, + `**/test/**`, + `**/__mocks__/**`, ], - nodir: true, + onlyFiles: true, } ); - process.stdout.write( `${ path.basename( packagePath ) }\n` ); + // Pause to avoid data flow which would begin on the `data` event binding, + // but should wait until worker processing below. + // + // See: https://nodejs.org/api/stream.html#stream_two_reading_modes + stream + .pause() + .on( 'data', ( file ) => { + bar.total = files.push( file ); + } ); + + onFileComplete = () => { + bar.tick(); + }; +} - // Build js files individually. - jsFiles.forEach( ( file ) => buildJsFile( file, true ) ); +const worker = workerFarm( require.resolve( './build-worker' ) ); - // Build package CSS files - buildPackageScss( packagePath ); +let ended = false, + complete = 0; - process.stdout.write( `${ DONE }\n` ); -} +stream + .on( 'data', ( file ) => worker( file, ( error ) => { + onFileComplete(); -const files = process.argv.slice( 2 ); + if ( error ) { + // If an error occurs, the process can't be ended immediately since + // other workers are likely pending. Optimally, it would end at the + // earliest opportunity (after the current round of workers has had + // the chance to complete), but this is not made directly possible + // through `worker-farm`. Instead, ensure at least that when the + // process does exit, it exits with a non-zero code to reflect the + // fact that an error had occurred. + process.exitCode = 1; -if ( files.length ) { - buildFiles( files ); -} else { - process.stdout.write( chalk.inverse( '>> Building packages \n' ) ); - getPackages() - .forEach( buildPackage ); - process.stdout.write( '\n' ); -} + console.error( error ); + } + + if ( ended && ++complete === files.length ) { + workerFarm.end( worker ); + } + } ) ) + .on( 'end', () => ended = true ) + .resume(); diff --git a/package-lock.json b/package-lock.json index 0ad23808055801..00d2e2b1810a07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9352,9 +9352,9 @@ "dev": true }, "fast-glob": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.6.tgz", - "integrity": "sha512-0BvMaZc1k9F+MeWWMe8pL6YltFzZYcJsYU7D4JyDA6PAczaXvxqQQ/z+mDF7/4Mw01DeUc+i3CTKajnkANkV4w==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", "dev": true, "requires": { "@mrmlnc/readdir-enhanced": "^2.2.1", @@ -9520,6 +9520,23 @@ "commondir": "^1.0.1", "make-dir": "^1.0.0", "pkg-dir": "^2.0.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "find-file-up": { @@ -12129,6 +12146,21 @@ "dev": true } } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true } } }, @@ -12181,6 +12213,21 @@ "supports-color": "^6.0.0" }, "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -12214,12 +12261,27 @@ "ms": "^2.1.1" } }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14239,18 +14301,18 @@ } }, "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", "dev": true, "requires": { - "pify": "^3.0.0" + "semver": "^6.0.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", "dev": true } } @@ -18023,9 +18085,9 @@ "dev": true }, "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise": { @@ -21164,6 +21226,15 @@ "uuid": "^3.0.1" }, "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -22821,9 +22892,9 @@ "dev": true }, "worker-farm": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", - "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "dev": true, "requires": { "errno": "~0.1.7" @@ -22902,6 +22973,15 @@ "write-file-atomic": "^2.0.0" }, "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", diff --git a/package.json b/package.json index 527d358b982b37..1270e1adde7bd3 100644 --- a/package.json +++ b/package.json @@ -90,12 +90,12 @@ "core-js": "3.0.1", "cross-env": "3.2.4", "cssnano": "4.1.10", - "deasync": "0.1.14", "deep-freeze": "0.0.1", "doctrine": "2.1.0", "enzyme": "3.9.0", "eslint-plugin-jest": "21.5.0", "espree": "4.0.0", + "fast-glob": "2.2.7", "fbjs": "0.8.17", "fs-extra": "8.0.1", "glob": "7.1.2", @@ -106,6 +106,7 @@ "lerna": "3.14.1", "lint-staged": "8.1.5", "lodash": "4.17.11", + "make-dir": "3.0.0", "mkdirp": "0.5.1", "node-sass": "4.12.0", "node-watch": "0.6.0", @@ -113,6 +114,7 @@ "pegjs": "0.10.0", "phpegjs": "1.0.0-beta7", "postcss": "7.0.13", + "progress": "2.0.3", "react": "16.8.4", "react-dom": "16.8.4", "react-test-renderer": "16.8.4", @@ -128,7 +130,8 @@ "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", - "webpack": "4.8.3" + "webpack": "4.8.3", + "worker-farm": "1.7.0" }, "npmPackageJsonLintConfig": { "extends": "@wordpress/npm-package-json-lint-config", From 2d541eb52fa04bffe7f5d26034cf53ffd5a73c04 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Thu, 30 May 2019 10:52:06 -0400 Subject: [PATCH 226/664] Only apply appender margins when the appender is inside of a block (#15888) * Only apply appender margins when the appender exists within a block. * Add code comment to explain the extra specificity. --- .../src/components/block-list-appender/style.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list-appender/style.scss b/packages/block-editor/src/components/block-list-appender/style.scss index e1725aa00873a6..5f83d4b44b290b 100644 --- a/packages/block-editor/src/components/block-list-appender/style.scss +++ b/packages/block-editor/src/components/block-list-appender/style.scss @@ -1,4 +1,6 @@ -.block-list-appender { +// These styles are only applied to the appender when it appears inside of a block. +// Otherwise the default appender may be improperly positioned in some themes. +.block-editor-block-list__block .block-list-appender { margin: $block-padding; // Add additional margin to the appender when inside a group with a background color. From c12859d12d87bb4194bec203531d2e7883c2bb9d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 30 May 2019 10:53:58 -0400 Subject: [PATCH 227/664] Framework: Remove packages build from build, dev scripts (#15226) * Framework: Omit Babel processing from Webpack configuration * Framework: Remove packages build from build, dev scripts * Build Tooling: Update Webpack config to operate on source files * Revert "Build Tooling: Update Webpack config to operate on source files" This reverts commit 8ca8c18d813cdac1d783cc87dbe006b4595214e0. * Revert "Framework: Remove packages build from build, dev scripts" This reverts commit 0021e5ceecadcc28c46cb4388c68fdc199d392ca. * Scripts: Add flag `--no-babel` for excluding Babel processing * Framework: Exclude Babel processing by build script flag * Framework: Avoid extending default configuration in Webpack build --- package.json | 1 + webpack.config.js | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1270e1adde7bd3..54a3034abafc94 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "shallow-equals": "1.0.0", "shallowequal": "1.1.0", "simple-git": "1.113.0", + "source-map-loader": "0.2.4", "sprintf-js": "1.1.1", "stylelint-config-wordpress": "13.1.0", "uuid": "3.3.2", diff --git a/webpack.config.js b/webpack.config.js index ba130ce0992d4a..3de56a819e0de8 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,7 @@ const { DefinePlugin } = require( 'webpack' ); const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); const postcss = require( 'postcss' ); -const { get, escapeRegExp } = require( 'lodash' ); +const { get, escapeRegExp, compact } = require( 'lodash' ); const { basename, sep } = require( 'path' ); /** @@ -12,7 +12,7 @@ const { basename, sep } = require( 'path' ); */ const CustomTemplatedPathPlugin = require( '@wordpress/custom-templated-path-webpack-plugin' ); const LibraryExportDefaultPlugin = require( '@wordpress/library-export-default-webpack-plugin' ); -const defaultConfig = require( '@wordpress/scripts/config/webpack.config' ); +const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); const { camelCaseDash } = require( '@wordpress/scripts/utils' ); /** @@ -20,6 +20,11 @@ const { camelCaseDash } = require( '@wordpress/scripts/utils' ); */ const { dependencies } = require( './package' ); +const { + NODE_ENV: mode = 'development', + WP_DEVTOOL: devtool = ( mode === 'production' ? false : 'source-map' ), +} = process.env; + const WORDPRESS_NAMESPACE = '@wordpress/'; const gutenbergPackages = Object.keys( dependencies ) @@ -27,7 +32,7 @@ const gutenbergPackages = Object.keys( dependencies ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); module.exports = { - ...defaultConfig, + mode, entry: gutenbergPackages.reduce( ( memo, packageName ) => { const name = camelCaseDash( packageName ); memo[ name ] = `./packages/${ packageName }`; @@ -39,8 +44,16 @@ module.exports = { library: [ 'wp', '[name]' ], libraryTarget: 'this', }, + module: { + rules: compact( [ + mode !== 'production' && { + test: /\.js$/, + use: require.resolve( 'source-map-loader' ), + enforce: 'pre', + }, + ] ), + }, plugins: [ - ...defaultConfig.plugins, new DefinePlugin( { // Inject the `GUTENBERG_PHASE` global, used for feature flagging. // eslint-disable-next-line @wordpress/gutenberg-phase @@ -82,7 +95,7 @@ module.exports = { to: `./build/${ packageName }/`, flatten: true, transform: ( content ) => { - if ( defaultConfig.mode === 'production' ) { + if ( mode === 'production' ) { return postcss( [ require( 'cssnano' )( { preset: [ 'default', { @@ -134,5 +147,7 @@ module.exports = { }, }, ] ), + new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), ], + devtool, }; From 7cb7c506299d29e856d618570a755f4bd219563c Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 31 May 2019 02:05:41 -0400 Subject: [PATCH 228/664] Build: Rebuild package stylesheet entrypoints for file updates (#15920) --- bin/packages/build.js | 49 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index 0f77d13ff06df3..9639f8b232f70e 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -7,7 +7,7 @@ const path = require( 'path' ); const glob = require( 'fast-glob' ); const ProgressBar = require( 'progress' ); const workerFarm = require( 'worker-farm' ); -const { Readable } = require( 'stream' ); +const { Readable, Transform } = require( 'stream' ); const files = process.argv.slice( 2 ); @@ -18,6 +18,52 @@ const files = process.argv.slice( 2 ); */ const PACKAGES_DIR = path.resolve( __dirname, '../../packages' ); +/** + * Get the package name for a specified file + * + * @param {string} file File name + * @return {string} Package name + */ +function getPackageName( file ) { + return path.relative( PACKAGES_DIR, file ).split( path.sep )[ 0 ]; +} + +/** + * Returns a stream transform which maps an individual stylesheet to its + * package entrypoint. Unlike JavaScript which uses an external bundler to + * efficiently manage rebuilds by entrypoints, stylesheets are rebuilt fresh + * in their entirety from the build script. + * + * @return {Transform} Stream transform instance. + */ +function createStyleEntryTransform() { + const packages = new Set; + + return new Transform( { + objectMode: true, + async transform( file, encoding, callback ) { + // Only stylesheets are subject to this transform. + if ( path.extname( file ) !== '.scss' ) { + this.push( file ); + callback(); + return; + } + + // Only operate once per package, assuming entries are common. + const packageName = getPackageName( file ); + if ( packages.has( packageName ) ) { + callback(); + return; + } + + packages.add( packageName ); + const entries = await glob( path.resolve( PACKAGES_DIR, packageName, 'src/*.scss' ) ); + entries.forEach( ( entry ) => this.push( entry ) ); + callback(); + }, + } ); +} + let onFileComplete = () => {}; let stream; @@ -26,6 +72,7 @@ if ( files.length ) { stream = new Readable( { encoding: 'utf8' } ); files.forEach( ( file ) => stream.push( file ) ); stream.push( null ); + stream = stream.pipe( createStyleEntryTransform() ); } else { const bar = new ProgressBar( 'Build Progress: [:bar] :percent', { width: 30, From fc2835eeef3c91edb1a7b40bd86c13473ce0ab8f Mon Sep 17 00:00:00 2001 From: Truong Giang <truongwp@gmail.com> Date: Fri, 31 May 2019 13:28:59 +0700 Subject: [PATCH 229/664] Update writing-your-first-block-type.md (#15717) Fix some wrong strings in writing-your-first-block-type.md --- .../block-tutorial/writing-your-first-block-type.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md index c959b4cd5aec58..a4ac0f7a33de9b 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md @@ -90,16 +90,16 @@ registerBlockType( 'gutenberg-examples/example-01-basic-esnext', { icon: 'universal-access-alt', category: 'layout', edit() { - return <div style={ blockStyle }>Basic example with JSX! (editor)</div>; + return <div style={ blockStyle }>Hello World, step 1 (from the editor).</div>; }, save() { - return <div style={ blockStyle }>Basic example with JSX! (front)</div>; + return <div style={ blockStyle }>Hello World, step 1 (from the frontend).</div>; }, } ); ``` {% end %} -_By now you should be able to see `Hello editor` in the admin side and `Hello saved content` on the frontend side._ +_By now you should be able to see `Hello World, step 1 (from the editor).` in the admin side and `Hello World, step 1 (from the frontend).` on the frontend side._ Once a block is registered, you should immediately see that it becomes available as an option in the editor inserter dialog, using values from `title`, `icon`, and `category` to organize its display. You can choose an icon from any included in the built-in [Dashicons icon set](https://developer.wordpress.org/resource/dashicons/), or provide a [custom svg element](/docs/designers-developers/developers/block-api/block-registration.md#icon-optional). From 196a485e3de1c1e0f148d65237b1f80a8a9f576f Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Fri, 31 May 2019 04:47:17 -0400 Subject: [PATCH 230/664] Stack consecutive buttons in the featured image area at all times. (#15928) --- packages/editor/src/components/post-featured-image/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss index 3258e6191124b2..ccdbafe2e34e35 100644 --- a/packages/editor/src/components/post-featured-image/style.scss +++ b/packages/editor/src/components/post-featured-image/style.scss @@ -5,10 +5,10 @@ margin: 0; } - // Space consecutive buttons evenly. + // Stack consecutive buttons. .components-button + .components-button { + display: block; margin-top: 1em; - margin-right: 8px; } // This keeps images at their intrinsic size (eg. a 50px From 18983b3e1bdd7921ceac61993e60ab0c61ca63ff Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 31 May 2019 10:59:39 +0100 Subject: [PATCH 231/664] Allow core widgets to be used in the legacy widgets block (#15396) * Allow core widgets to be used in the legacy widgets block (although not selectable). * Extract the placeholder. * Don't show 'Change widget' button when displaying a core widget * Move LegacyWidgetPlaceholder to its own file --- lib/widgets.php | 23 ++++--- .../src/legacy-widget/edit/index.js | 66 +++++-------------- .../src/legacy-widget/edit/placeholder.js | 57 ++++++++++++++++ 3 files changed, 86 insertions(+), 60 deletions(-) create mode 100644 packages/block-library/src/legacy-widget/edit/placeholder.js diff --git a/lib/widgets.php b/lib/widgets.php index 28ebc003674759..7ea5d3a5e770dd 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -102,18 +102,17 @@ function gutenberg_get_legacy_widget_settings() { $available_legacy_widgets = array(); global $wp_widget_factory, $wp_registered_widgets; foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { - if ( ! in_array( $class, $core_widgets ) ) { - $available_legacy_widgets[ $class ] = array( - 'name' => html_entity_decode( $widget_obj->name ), - // wp_widget_description is not being used because its input parameter is a Widget Id. - // Widgets id's reference to a specific widget instance. - // Here we are iterating on all the available widget classes even if no widget instance exists for them. - 'description' => isset( $widget_obj->widget_options['description'] ) ? - html_entity_decode( $widget_obj->widget_options['description'] ) : - null, - 'isCallbackWidget' => false, - ); - } + $available_legacy_widgets[ $class ] = array( + 'name' => html_entity_decode( $widget_obj->name ), + // wp_widget_description is not being used because its input parameter is a Widget Id. + // Widgets id's reference to a specific widget instance. + // Here we are iterating on all the available widget classes even if no widget instance exists for them. + 'description' => isset( $widget_obj->widget_options['description'] ) ? + html_entity_decode( $widget_obj->widget_options['description'] ) : + null, + 'isCallbackWidget' => false, + 'isHidden' => in_array( $class, $core_widgets, true ), + ); } foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { if ( diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index 4b645318eb2277..ac38184d22989b 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { map } from 'lodash'; - /** * WordPress dependencies */ @@ -11,15 +6,12 @@ import { Button, IconButton, PanelBody, - Placeholder, - SelectControl, Toolbar, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; import { BlockControls, - BlockIcon, InspectorControls, } from '@wordpress/block-editor'; import { ServerSideRender } from '@wordpress/editor'; @@ -28,6 +20,7 @@ import { ServerSideRender } from '@wordpress/editor'; * Internal dependencies */ import LegacyWidgetEditHandler from './handler'; +import LegacyWidgetPlaceholder from './placeholder'; class LegacyWidgetEdit extends Component { constructor() { @@ -51,41 +44,17 @@ class LegacyWidgetEdit extends Component { const { identifier, isCallbackWidget } = attributes; const widgetObject = identifier && availableLegacyWidgets[ identifier ]; if ( ! widgetObject ) { - let placeholderContent; - - if ( ! hasPermissionsToManageWidgets ) { - placeholderContent = __( 'You don\'t have permissions to use widgets on this site.' ); - } else if ( availableLegacyWidgets.length === 0 ) { - placeholderContent = __( 'There are no widgets available.' ); - } else { - placeholderContent = ( - <SelectControl - label={ __( 'Select a legacy widget to display:' ) } - value={ identifier || 'none' } - onChange={ ( value ) => setAttributes( { - instance: {}, - identifier: value, - isCallbackWidget: availableLegacyWidgets[ value ].isCallbackWidget, - } ) } - options={ [ { value: 'none', label: 'Select widget' } ].concat( - map( availableLegacyWidgets, ( widget, key ) => { - return { - value: key, - label: widget.name, - }; - } ) - ) } - /> - ); - } - return ( - <Placeholder - icon={ <BlockIcon icon="admin-customizer" /> } - label={ __( 'Legacy Widget' ) } - > - { placeholderContent } - </Placeholder> + <LegacyWidgetPlaceholder + availableLegacyWidgets={ availableLegacyWidgets } + currentWidget={ identifier } + hasPermissionsToManageWidgets={ hasPermissionsToManageWidgets } + onChangeWidget={ ( newWidget ) => setAttributes( { + instance: {}, + identifier: newWidget, + isCallbackWidget: availableLegacyWidgets[ newWidget ].isCallbackWidget, + } ) } + /> ); } @@ -109,12 +78,13 @@ class LegacyWidgetEdit extends Component { <> <BlockControls> <Toolbar> - <IconButton - onClick={ this.changeWidget } - label={ __( 'Change widget' ) } - icon="update" - > - </IconButton> + { ! widgetObject.isHidden && ( + <IconButton + onClick={ this.changeWidget } + label={ __( 'Change widget' ) } + icon="update" + /> + ) } { ! isCallbackWidget && ( <> <Button diff --git a/packages/block-library/src/legacy-widget/edit/placeholder.js b/packages/block-library/src/legacy-widget/edit/placeholder.js new file mode 100644 index 00000000000000..b19f5654ab4c6f --- /dev/null +++ b/packages/block-library/src/legacy-widget/edit/placeholder.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import { pickBy, isEmpty, map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { SelectControl, Placeholder } from '@wordpress/components'; +import { BlockIcon } from '@wordpress/block-editor'; + +export default function LegacyWidgetPlaceholder( { + availableLegacyWidgets, + currentWidget, + hasPermissionsToManageWidgets, + onChangeWidget, +} ) { + const visibleLegacyWidgets = useMemo( + () => pickBy( + availableLegacyWidgets, + ( { isHidden } ) => ! isHidden + ), + [ availableLegacyWidgets ] + ); + let placeholderContent; + if ( ! hasPermissionsToManageWidgets ) { + placeholderContent = __( 'You don\'t have permissions to use widgets on this site.' ); + } + if ( isEmpty( visibleLegacyWidgets ) ) { + placeholderContent = __( 'There are no widgets available.' ); + } + placeholderContent = ( + <SelectControl + label={ __( 'Select a legacy widget to display:' ) } + value={ currentWidget || 'none' } + onChange={ onChangeWidget } + options={ [ { value: 'none', label: 'Select widget' } ].concat( + map( visibleLegacyWidgets, ( widget, key ) => { + return { + value: key, + label: widget.name, + }; + } ) + ) } + /> + ); + return ( + <Placeholder + icon={ <BlockIcon icon="admin-customizer" /> } + label={ __( 'Legacy Widget' ) } + > + { placeholderContent } + </Placeholder> + ); +} From 69f100676c283f13e6c6ec56003682bd86a68f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?sarah=20=E2=9C=88=20semark?= <sarah@triggersandsparks.com> Date: Fri, 31 May 2019 11:54:25 +0100 Subject: [PATCH 232/664] Remove stray flexbox CSS that's no longer needed; closes #11566. (#15936) --- packages/components/src/date-time/style.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 3b0609258f88ca..9d2f8ea61240ca 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -119,9 +119,6 @@ } .components-datetime__time-field { - align-self: center; - flex: 0 1 auto; - order: 1; &.am-pm button { font-size: 11px; From 31e0e4496aa2a238bff1b0be67898f651f2d1ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= <public@oxy.fi> Date: Fri, 31 May 2019 11:35:40 -0300 Subject: [PATCH 233/664] Add overlayColor class name to cover blocks editor markup (#15897) (#15939) --- packages/block-library/src/cover/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index f908be2423d654..b7cb4ea6984eb1 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -256,6 +256,7 @@ class CoverEdit extends Component { 'is-dark-theme': this.state.isDark, 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, + [ overlayColor.class ]: overlayColor.class, } ); From 42afdbf879d8e2a7de4b158d053d3449e2d68b3b Mon Sep 17 00:00:00 2001 From: Hardip Parmar <parmarhardip1995@gmail.com> Date: Fri, 31 May 2019 21:18:13 +0530 Subject: [PATCH 234/664] #15941 <br> tag showing issue solved. (#15942) --- .../developers/tutorials/metabox/meta-block-3-add.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md index 0a173a539a82be..67bec79789d844 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-3-add.md @@ -60,7 +60,6 @@ Add this code to your JavaScript file (this tutorial will call the file `myguten ``` {% ESNext %} ```jsx - const { registerBlockType } = wp.blocks; const { TextControl } = wp.components; From 1d2d1fc9dc88c97cb8f23472b03cc6a2d8241df0 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Fri, 31 May 2019 17:48:41 +0200 Subject: [PATCH 235/664] Bail early in createUpgradedEmbedBlock for invalid block types (#15885) * Bail early in createUpgradedEmbedBlock if block type does not exists * Add changelog entry * Add tests --- packages/block-library/CHANGELOG.md | 4 +++ .../block-library/src/embed/test/index.js | 32 ++++++++++++++++++- packages/block-library/src/embed/util.js | 6 +++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index b0576be7c47fdd..4df19d8dee8ca9 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,3 +1,7 @@ +## Master + +- Fixed an issue with creating upgraded embed blocks that are not registered ([#15883](https://github.com/WordPress/gutenberg/issues/15883)). + ## 2.5.0 (2019-05-21) - Add vertical alignment controls to Columns Block ([#13899](https://github.com/WordPress/gutenberg/pull/13899/)). diff --git a/packages/block-library/src/embed/test/index.js b/packages/block-library/src/embed/test/index.js index 3931ae7b581e18..5f01d8ff77ee84 100644 --- a/packages/block-library/src/embed/test/index.js +++ b/packages/block-library/src/embed/test/index.js @@ -3,11 +3,16 @@ */ import { render } from 'enzyme'; +/** + * WordPress dependencies + */ +import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; + /** * Internal dependencies */ import { getEmbedEditComponent } from '../edit'; -import { findBlock, getClassNames } from '../util'; +import { findBlock, getClassNames, createUpgradedEmbedBlock } from '../util'; describe( 'core/embed', () => { test( 'block edit matches snapshot', () => { @@ -44,4 +49,29 @@ describe( 'core/embed', () => { const expected = 'lovely'; expect( getClassNames( html, 'lovely wp-embed-aspect-16-9 wp-has-aspect-ratio', false ) ).toEqual( expected ); } ); + + test( 'createUpgradedEmbedBlock bails early when block type does not exist', () => { + const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + + expect( createUpgradedEmbedBlock( { attributes: { url: youtubeURL } }, {} ) ).toBeUndefined(); + } ); + + test( 'createUpgradedEmbedBlock returns a YouTube embed block when given a YouTube URL', () => { + const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; + + registerBlockType( + 'core-embed/youtube', + { + title: 'YouTube', + category: 'embed', + } + ); + + const result = createUpgradedEmbedBlock( { attributes: { url: youtubeURL } }, {} ); + + unregisterBlockType( 'core-embed/youtube' ); + + expect( result ).not.toBeUndefined(); + expect( result.name ).toBe( 'core-embed/youtube' ); + } ); } ); diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index f52716f2cb96f2..80bcbed310e9a0 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -15,7 +15,7 @@ import memoize from 'memize'; * WordPress dependencies */ import { renderToString } from '@wordpress/element'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, getBlockType } from '@wordpress/blocks'; /** * Returns true if any of the regular expressions match the URL. @@ -82,6 +82,10 @@ export const createUpgradedEmbedBlock = ( props, attributesFromPreview ) => { const matchingBlock = findBlock( url ); + if ( ! getBlockType( matchingBlock ) ) { + return; + } + // WordPress blocks can work on multiple sites, and so don't have patterns, // so if we're in a WordPress block, assume the user has chosen it for a WordPress URL. if ( WORDPRESS_EMBED_BLOCK !== name && DEFAULT_EMBED_BLOCK !== matchingBlock ) { From 06438237d51436d937bee22e4de3aecb52fda650 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 31 May 2019 16:34:14 -0400 Subject: [PATCH 236/664] Project Management: Merge lib and REST controllers CODEOWNERS (#15925) --- .github/CODEOWNERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e606dc21e8074f..4a7a5af6e15205 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -82,8 +82,7 @@ /packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao # PHP -/lib @youknowriad @gziolo @aduth -*-controller.php @youknowriad @gziolo @aduth @timothybjacobs +/lib @youknowriad @gziolo @aduth @timothybjacobs # Native (Unowned) *.native.js @ghost From 04ca6b494ddcebc6a78c433c3b85416e6fe64d9b Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 31 May 2019 16:35:24 -0400 Subject: [PATCH 237/664] Data: Documentation: Document undocumented declarations (#15176) * Data: Documentation: Document plugins * Data: Documentation: Document use --- packages/data/README.md | 18 ++++++++++++++++-- packages/data/src/index.js | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/data/README.md b/packages/data/README.md index a5bdfd30a5c375..45a7cdda6f86b9 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -358,7 +358,15 @@ _Returns_ <a name="plugins" href="#plugins">#</a> **plugins** -Undocumented declaration. +Object of available plugins to use with a registry. + +_Related_ + +- [use](#use) + +_Type_ + +- `Object` <a name="registerGenericStore" href="#registerGenericStore">#</a> **registerGenericStore** @@ -470,7 +478,13 @@ _Parameters_ <a name="use" href="#use">#</a> **use** -Undocumented declaration. +Extends a registry to inherit functionality provided by a given plugin. A +plugin is an object with properties aligning to that of a registry, merged +to extend the default registry behavior. + +_Parameters_ + +- _plugin_ `Object`: Plugin object. <a name="useRegistry" href="#useRegistry">#</a> **useRegistry** diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 46c830289b91d7..00f1e367eb794d 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -22,9 +22,17 @@ export { AsyncModeProvider as __experimentalAsyncModeProvider, } from './components/async-mode-provider'; export { createRegistry } from './registry'; -export { plugins }; export { createRegistrySelector, createRegistryControl } from './factory'; +/** + * Object of available plugins to use with a registry. + * + * @see [use](#use) + * + * @type {Object} + */ +export { plugins }; + /** * The combineReducers helper function turns an object whose values are different * reducing functions into a single reducing function you can pass to registerReducer. @@ -140,4 +148,12 @@ export const registerGenericStore = defaultRegistry.registerGenericStore; * @return {Object} Registered store object. */ export const registerStore = defaultRegistry.registerStore; + +/** + * Extends a registry to inherit functionality provided by a given plugin. A + * plugin is an object with properties aligning to that of a registry, merged + * to extend the default registry behavior. + * + * @param {Object} plugin Plugin object. + */ export const use = defaultRegistry.use; From dbc155c3a8a5d505558d79e8a0936d4fc9ac765e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 3 Jun 2019 09:33:36 +0200 Subject: [PATCH 238/664] Build: Improve setup of package.json files (#15879) * Build: Improve setup of package.json files * Address feedback from reviews --- bin/packages/get-packages.js | 17 +++++++++++++++-- packages/browserslist-config/package.json | 3 +++ .../package.json | 1 + packages/dom-ready/package.json | 1 + packages/eslint-plugin/package.json | 6 ++++++ packages/html-entities/package.json | 2 +- packages/is-shallow-equal/package.json | 1 + packages/jest-preset-default/package.json | 1 + .../package.json | 1 + .../npm-package-json-lint-config/CHANGELOG.md | 6 ++++++ packages/npm-package-json-lint-config/index.js | 2 ++ .../npm-package-json-lint-config/package.json | 3 +++ packages/plugins/package.json | 1 + packages/token-list/package.json | 1 + packages/wordcount/package.json | 1 + 15 files changed, 44 insertions(+), 3 deletions(-) diff --git a/bin/packages/get-packages.js b/bin/packages/get-packages.js index ed271db0434f23..de0147435dad23 100644 --- a/bin/packages/get-packages.js +++ b/bin/packages/get-packages.js @@ -3,7 +3,7 @@ */ const fs = require( 'fs' ); const path = require( 'path' ); -const { overEvery } = require( 'lodash' ); +const { isEmpty, overEvery } = require( 'lodash' ); /** * Absolute path to packages directory. @@ -24,6 +24,19 @@ function isDirectory( file ) { return fs.lstatSync( path.resolve( PACKAGES_DIR, file ) ).isDirectory(); } +/** + * Returns true if the given packages has "module" field. + * + * @param {string} file Packages directory file. + * + * @return {boolean} Whether file is a directory. + */ +function hasModuleField( file ) { + const { module } = require( path.resolve( PACKAGES_DIR, file, 'package.json' ) ); + + return ! isEmpty( module ); +} + /** * Filter predicate, returning true if the given base file name is to be * included in the build. @@ -32,7 +45,7 @@ function isDirectory( file ) { * * @return {boolean} Whether to include file in build. */ -const filterPackages = overEvery( isDirectory ); +const filterPackages = overEvery( isDirectory, hasModuleField ); /** * Returns the absolute path of all WordPress packages diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index a1480a4a821de0..331b74b323fadd 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -21,6 +21,9 @@ "engines": { "node": ">=8" }, + "files": [ + "index.js" + ], "main": "index.js", "publishConfig": { "access": "public" diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index b861d700441de3..bf7af1e6a461f3 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -24,6 +24,7 @@ "files": [ "index.js" ], + "main": "index.js", "dependencies": { "escape-string-regexp": "^1.0.5" }, diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 8213415b081db5..d390d8db4ea3b0 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -19,6 +19,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 736b49aa1bcccd..9d009fd22b1b3f 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -17,6 +17,12 @@ "bugs": { "url": "https://github.com/WordPress/gutenberg/issues" }, + "files": [ + "configs", + "rules", + "index.js" + ], + "main": "index.js", "dependencies": { "babel-eslint": "^10.0.1", "eslint-plugin-jsx-a11y": "^6.2.1", diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index f395b10b352477..85c504daaad1c3 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -4,7 +4,6 @@ "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", - "react-native": "src/index", "keywords": [ "wordpress", "html", @@ -21,6 +20,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index af3f44c51a85f3..5e1effc9c00767 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -21,6 +21,7 @@ }, "files": [ "arrays.js", + "index.js", "objects.js" ], "main": "index.js", diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index 9f5eeeaf8117c5..fa4e413adbfeeb 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -26,6 +26,7 @@ }, "files": [ "scripts", + "index.js", "jest-preset.json" ], "main": "index.js", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index d9fc8d12afc763..fcaf8a3de299e5 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -24,6 +24,7 @@ "files": [ "index.js" ], + "main": "index.js", "dependencies": { "lodash": "^4.17.11", "webpack-sources": "^1.1.0" diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index 11259b591308d6..2fbfc480934313 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Braking Change + +- Added `type` and `react-native` to the order of preferred properties. + ## 1.2.0 (2019-03-06) ### Internal diff --git a/packages/npm-package-json-lint-config/index.js b/packages/npm-package-json-lint-config/index.js index 5af34472e68b1e..d6a93751371b16 100644 --- a/packages/npm-package-json-lint-config/index.js +++ b/packages/npm-package-json-lint-config/index.js @@ -53,8 +53,10 @@ const defaultConfig = { 'engines', 'directories', 'files', + 'type', 'main', 'module', + 'react-native', 'bin', 'dependencies', 'devDependencies', diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index 885a8a5b66bba8..e9af2edfa81ce9 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -21,6 +21,9 @@ "engines": { "node": ">=8" }, + "files": [ + "index.js" + ], "main": "index.js", "peerDependencies": { "npm-package-json-lint": ">=3.6.0" diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 5e93869577524f..f0859bb278f4fe 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -19,6 +19,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/compose": "file:../compose", diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 5d14fab2f65321..206bb71eef2987 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -18,6 +18,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", "lodash": "^4.17.11" diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index af4448b1d46f53..a8f74123873112 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -19,6 +19,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", "lodash": "^4.17.11" From c2768eccc3ef53be437ad90fece0c43c8a843ce3 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 3 Jun 2019 03:35:13 -0400 Subject: [PATCH 239/664] UI Components: Refactor BlockSettingsMenu and MoreMenu as DropdownMenu (#14843) * Components: Document missing menuLabel, position, className Dropdown props * Components: Add render prop support to DropdownMenu * Block Editor: Refactor BlockSettingsMenu as DropdownMenu * CHANGELOG 7a54cec63 * CHANGELOG 743e4da7c * CHANGELOG ea8265827 * Components: Remove unintended tabs from DropdownMenu tests * Refactor MoreMenu component to use DropdownMenu component * Use single label for DrowdownMenu components * Fix all failing e2e tests * Add all class names for backward compatibility * Fix unit tests by refreshing saved snapshots * Update packages/components/src/menu-group/index.js Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Add back removed changelog update for components during rebase * Mark all props used with DropdownMenu for backward compatibility as unstable * Remove obsolete DropdownMenuSepararator component * Apply changes after rebase to align CSS styles with master * Display dropdown menu indicator only when icon set expliclity to false --- packages/block-editor/CHANGELOG.md | 6 + .../components/block-settings-menu/index.js | 191 +++++++++--------- .../components/block-settings-menu/style.scss | 60 +----- packages/components/CHANGELOG.md | 10 + .../components/src/dropdown-menu/README.md | 76 ++++++- .../components/src/dropdown-menu/index.js | 45 +++-- .../components/src/dropdown-menu/style.scss | 18 +- .../src/dropdown-menu/test/index.js | 65 +++--- packages/components/src/menu-group/index.js | 17 +- .../test/__snapshots__/index.js.snap | 7 +- packages/components/src/menu-item/style.scss | 2 +- .../src/click-on-more-menu-item.js | 2 +- .../src/switch-editor-mode-to.js | 2 +- .../e2e-tests/specs/adding-blocks.test.js | 2 +- .../e2e-tests/specs/block-deletion.test.js | 3 +- .../specs/blocks/preformatted.test.js | 9 +- packages/e2e-tests/specs/editor-modes.test.js | 15 +- .../specs/keyboard-navigable-blocks.test.js | 4 +- packages/e2e-tests/specs/nux.test.js | 4 +- .../specs/plugins/annotations.test.js | 3 +- .../e2e-tests/specs/reusable-blocks.test.js | 9 +- .../src/components/header/more-menu/index.js | 26 +-- .../components/header/more-menu/style.scss | 13 +- .../test/__snapshots__/index.js.snap | 129 +++++++----- .../test/__snapshots__/index.js.snap | 5 +- 25 files changed, 391 insertions(+), 332 deletions(-) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index ac9a9f9f9ebafe..f8f88cb313e74b 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Internal + +- Refactored `BlockSettingsMenu` to use `DropdownMenu` from `@wordpress/components`. + ## 2.0.0 (2019-04-16) ### New Features diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 21899126bae2fa..005be38e380b43 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -1,15 +1,18 @@ /** * External dependencies */ -import classnames from 'classnames'; import { castArray, flow } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Toolbar, Dropdown, NavigableMenu, MenuItem } from '@wordpress/components'; -import { withDispatch } from '@wordpress/data'; +import { + Toolbar, + DropdownMenu, + MenuGroup, + MenuItem, +} from '@wordpress/components'; /** * Internal dependencies @@ -22,7 +25,7 @@ import BlockUnknownConvertButton from './block-unknown-convert-button'; import __experimentalBlockSettingsMenuFirstItem from './block-settings-menu-first-item'; import __experimentalBlockSettingsMenuPluginsExtension from './block-settings-menu-plugins-extension'; -export function BlockSettingsMenu( { clientIds, onSelect } ) { +export function BlockSettingsMenu( { clientIds } ) { const blockClientIds = castArray( clientIds ); const count = blockClientIds.length; const firstBlockClientId = blockClientIds[ 0 ]; @@ -30,105 +33,91 @@ export function BlockSettingsMenu( { clientIds, onSelect } ) { return ( <BlockActions clientIds={ clientIds }> { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => ( - <Dropdown - contentClassName="editor-block-settings-menu__popover block-editor-block-settings-menu__popover" - position="bottom right" - renderToggle={ ( { onToggle, isOpen } ) => { - const toggleClassname = classnames( 'editor-block-settings-menu__toggle block-editor-block-settings-menu__toggle', { - 'is-opened': isOpen, - } ); - const label = isOpen ? __( 'Hide options' ) : __( 'More options' ); - - return ( - <Toolbar controls={ [ { - icon: 'ellipsis', - title: label, - onClick: () => { - if ( count === 1 ) { - onSelect( firstBlockClientId ); - } - onToggle(); - }, - className: toggleClassname, - extraProps: { 'aria-expanded': isOpen }, - } ] } /> - ); - } } - renderContent={ ( { onClose } ) => ( - <NavigableMenu className="editor-block-settings-menu__content block-editor-block-settings-menu__content"> - <__experimentalBlockSettingsMenuFirstItem.Slot fillProps={ { onClose } } /> - { count === 1 && ( - <BlockUnknownConvertButton - clientId={ firstBlockClientId } - /> - ) } - { count === 1 && ( - <BlockHTMLConvertButton - clientId={ firstBlockClientId } - /> - ) } - { ! isLocked && canDuplicate && ( - <MenuItem - className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ flow( onClose, onDuplicate ) } - icon="admin-page" - shortcut={ shortcuts.duplicate.display } - > - { __( 'Duplicate' ) } - </MenuItem> - ) } - { ! isLocked && ( - <> - <MenuItem - className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ flow( onClose, onInsertBefore ) } - icon="insert-before" - shortcut={ shortcuts.insertBefore.display } - > - { __( 'Insert Before' ) } - </MenuItem> - <MenuItem - className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ flow( onClose, onInsertAfter ) } - icon="insert-after" - shortcut={ shortcuts.insertAfter.display } - > - { __( 'Insert After' ) } - </MenuItem> - </> - ) } - { count === 1 && ( - <BlockModeToggle - clientId={ firstBlockClientId } - onToggle={ onClose } - /> - ) } - <__experimentalBlockSettingsMenuPluginsExtension.Slot fillProps={ { clientIds, onClose } } /> - <div className="editor-block-settings-menu__separator block-editor-block-settings-menu__separator" /> - { ! isLocked && ( - <MenuItem - className="editor-block-settings-menu__control block-editor-block-settings-menu__control" - onClick={ flow( onClose, onRemove ) } - icon="trash" - shortcut={ shortcuts.removeBlock.display } - > - { __( 'Remove Block' ) } - </MenuItem> - ) } - </NavigableMenu> - ) } - /> + <Toolbar> + <DropdownMenu + icon="ellipsis" + label={ __( 'More options' ) } + position="bottom right" + className="block-editor-block-settings-menu" + __unstableToggleClassName="block-editor-block-settings-menu__toggle editor-block-settings-menu__toggle" + __unstableMenuClassName="block-editor-block-settings-menu__content editor-block-settings-menu__content" + __unstablePopoverClassName="block-editor-block-settings-menu__popover editor-block-settings-menu__popover" + > + { ( { onClose } ) => ( + <> + <MenuGroup> + <__experimentalBlockSettingsMenuFirstItem.Slot + fillProps={ { onClose } } + /> + { count === 1 && ( + <BlockUnknownConvertButton + clientId={ firstBlockClientId } + /> + ) } + { count === 1 && ( + <BlockHTMLConvertButton + clientId={ firstBlockClientId } + /> + ) } + { ! isLocked && canDuplicate && ( + <MenuItem + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" + onClick={ flow( onClose, onDuplicate ) } + icon="admin-page" + shortcut={ shortcuts.duplicate.display } + > + { __( 'Duplicate' ) } + </MenuItem> + ) } + { ! isLocked && ( + <> + <MenuItem + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" + onClick={ flow( onClose, onInsertBefore ) } + icon="insert-before" + shortcut={ shortcuts.insertBefore.display } + > + { __( 'Insert Before' ) } + </MenuItem> + <MenuItem + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" + onClick={ flow( onClose, onInsertAfter ) } + icon="insert-after" + shortcut={ shortcuts.insertAfter.display } + > + { __( 'Insert After' ) } + </MenuItem> + </> + ) } + { count === 1 && ( + <BlockModeToggle + clientId={ firstBlockClientId } + onToggle={ onClose } + /> + ) } + <__experimentalBlockSettingsMenuPluginsExtension.Slot + fillProps={ { clientIds, onClose } } + /> + </MenuGroup> + <MenuGroup> + { ! isLocked && ( + <MenuItem + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" + onClick={ flow( onClose, onRemove ) } + icon="trash" + shortcut={ shortcuts.removeBlock.display } + > + { __( 'Remove Block' ) } + </MenuItem> + ) } + </MenuGroup> + </> + ) } + </DropdownMenu> + </Toolbar> ) } </BlockActions> ); } -export default withDispatch( ( dispatch ) => { - const { selectBlock } = dispatch( 'core/block-editor' ); - - return { - onSelect( clientId ) { - selectBlock( clientId ); - }, - }; -} )( BlockSettingsMenu ); +export default BlockSettingsMenu; diff --git a/packages/block-editor/src/components/block-settings-menu/style.scss b/packages/block-editor/src/components/block-settings-menu/style.scss index 197303805352e6..0b690745012edd 100644 --- a/packages/block-editor/src/components/block-settings-menu/style.scss +++ b/packages/block-editor/src/components/block-settings-menu/style.scss @@ -1,59 +1,7 @@ -.block-editor-block-settings-menu__toggle .dashicon { - transform: rotate(90deg); +.block-editor-block-settings-menu__content { + padding: 0; } -// Popout menu -.block-editor-block-settings-menu__popover { - &::before, - &::after { - margin-left: 2px; - } - - .block-editor-block-settings-menu__content { - padding: ($grid-size - $border-width) 0; - } - - .block-editor-block-settings-menu__separator { - margin-top: $grid-size; - margin-bottom: $grid-size; - margin-left: 0; - margin-right: 0; - border-top: $border-width solid $light-gray-500; - - // Check if the separator is the last child in the node and if so, hide itself - &:last-child { - display: none; - } - } - - .block-editor-block-settings-menu__title { - display: block; - padding: 6px; - color: $dark-gray-300; - } - - // Menu items - .block-editor-block-settings-menu__control { - width: 100%; - justify-content: flex-start; - background: none; - outline: none; - border-radius: 0; - text-align: left; - cursor: pointer; - color: $dark-gray-600; - @include menu-style__neutral; - - &:hover:not(:disabled):not([aria-disabled="true"]) { - @include menu-style__hover; - } - - &:focus:not(:disabled):not([aria-disabled="true"]) { - @include menu-style__focus; - } - - .dashicon { - margin-right: 5px; - } - } +.block-editor-block-settings-menu__toggle .dashicon { + transform: rotate(90deg); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index e6c72c7dbb190d..e6db0bcd0c8ff4 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -3,6 +3,16 @@ ### New Feature - Add new `BlockQuotation` block to the primitives folder to support blockquote in a multiplatform way. [#15482](https://github.com/WordPress/gutenberg/pull/15482). +- `DropdownMenu` now supports passing a [render prop](https://reactjs.org/docs/render-props.html#using-props-other-than-render) as children for more advanced customization. + +### Internal + +- `MenuGroup` no longer uses `NavigableMenu` internally. It needs to be explicitly wrapped with `NavigableMenu` to bring back the same behavior. + +### Documentation + +- Added missing documentation for `DropdownMenu` props `menuLabel`, `position`, `className`. + ## 7.4.0 (2019-05-21) diff --git a/packages/components/src/dropdown-menu/README.md b/packages/components/src/dropdown-menu/README.md index 9346140bb27ef0..4e2243c653b2f4 100644 --- a/packages/components/src/dropdown-menu/README.md +++ b/packages/components/src/dropdown-menu/README.md @@ -91,6 +91,47 @@ const MyDropdownMenu = () => ( ); ``` +Alternatively, specify a `children` function which returns elements valid for use in a DropdownMenu: `MenuItem`, `MenuItemsChoice`, or `MenuGroup`. + +```jsx +import { Fragment } from '@wordpress/element'; +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; + +const MyDropdownMenu = () => ( + <DropdownMenu + icon="move" + label="Select a direction" + > + { ( { onClose } ) => ( + <Fragment> + <MenuGroup> + <MenuItem + icon="arrow-up-alt" + onClick={ onClose } + > + Move Up + </MenuItem> + <MenuItem + icon="arrow-down-alt" + onClick={ onClose } + > + Move Down + </MenuItem> + </MenuGroup> + <MenuGroup> + <MenuItem + icon="trash" + onClick={ onClose } + > + Remove + </MenuItem> + </MenuGroup> + </Fragment> + ) } + </DropdownMenu> +); +``` + ### Props The component accepts the following props: @@ -112,13 +153,46 @@ A human-readable label to present as accessibility text on the focused collapsed - Type: `String` - Required: Yes +#### menuLabel + +A human-readable label to present as accessibility text on the expanded menu container. + +- Type: `String` +- Required: No + +#### position + +The direction in which the menu should open. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"middle"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis. + +- Type: `String` +- Required: No +- Default: `"top center"` + #### controls An array of objects describing the options to be shown in the expanded menu. Each object should include an `icon` [Dashicon](https://developer.wordpress.org/resource/dashicons/) slug string, a human-readable `title` string, `isDisabled` boolean flag and an `onClick` function callback to invoke when the option is selected. +A valid DropdownMenu must specify one or the other of a `controls` or `children` prop. + - Type: `Array` -- Required: Yes +- Required: No + +#### children + +A [function render prop](https://reactjs.org/docs/render-props.html#using-props-other-than-render) which should return an element or elements valid for use in a DropdownMenu: `MenuItem`, `MenuItemsChoice`, or `MenuGroup`. Its first argument is a props object including the same values as given to a [`Dropdown`'s `renderContent`](/packages/components/src/dropdown#rendercontent) (`isOpen`, `onToggle`, `onClose`). + +A valid DropdownMenu must specify one or the other of a `controls` or `children` prop. + +- Type: `Function` +- Required: No See also: [https://developer.wordpress.org/resource/dashicons/](https://developer.wordpress.org/resource/dashicons/) + +#### className + +A class name to apply to the dropdown wrapper element. + +- Type: `String` +- Required: No diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index e301935a0ac60d..8195a309ec0241 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { flatMap } from 'lodash'; +import { flatMap, isEmpty, isFunction } from 'lodash'; /** * WordPress dependencies @@ -17,27 +17,35 @@ import Dropdown from '../dropdown'; import { NavigableMenu } from '../navigable-container'; function DropdownMenu( { + children, + className, + controls, icon = 'menu', label, menuLabel, - controls, - className, position, + __unstableLabelPosition, + __unstableMenuClassName, + __unstablePopoverClassName, + __unstableToggleClassName, } ) { - if ( ! controls || ! controls.length ) { + if ( isEmpty( controls ) && ! isFunction( children ) ) { return null; } // Normalize controls to nested array of objects (sets of controls) - let controlSets = controls; - if ( ! Array.isArray( controlSets[ 0 ] ) ) { - controlSets = [ controlSets ]; + let controlSets; + if ( ! isEmpty( controls ) ) { + controlSets = controls; + if ( ! Array.isArray( controlSets[ 0 ] ) ) { + controlSets = [ controlSets ]; + } } return ( <Dropdown className={ classnames( 'components-dropdown-menu', className ) } - contentClassName="components-dropdown-menu__popover" + contentClassName={ classnames( 'components-dropdown-menu__popover', __unstablePopoverClassName ) } position={ position } renderToggle={ ( { isOpen, onToggle } ) => { const openOnArrowDown = ( event ) => { @@ -47,35 +55,44 @@ function DropdownMenu( { onToggle(); } }; + return ( <IconButton - className="components-dropdown-menu__toggle" + className={ classnames( 'components-dropdown-menu__toggle', __unstableToggleClassName, { + 'is-opened': isOpen, + } ) } icon={ icon } onClick={ onToggle } onKeyDown={ openOnArrowDown } aria-haspopup="true" aria-expanded={ isOpen } label={ label } + labelPosition={ __unstableLabelPosition } tooltip={ label } > - <span className="components-dropdown-menu__indicator" /> + { ! icon && <span className="components-dropdown-menu__indicator" /> } </IconButton> ); } } - renderContent={ ( { onClose } ) => { + renderContent={ ( props ) => { return ( <NavigableMenu - className="components-dropdown-menu__menu" + className={ classnames( 'components-dropdown-menu__menu', __unstableMenuClassName ) } role="menu" - aria-label={ menuLabel } + aria-label={ menuLabel || label } > + { + isFunction( children ) ? + children( props ) : + null + } { flatMap( controlSets, ( controlSet, indexOfSet ) => ( controlSet.map( ( control, indexOfControl ) => ( <IconButton key={ [ indexOfSet, indexOfControl ].join() } onClick={ ( event ) => { event.stopPropagation(); - onClose(); + props.onClose(); if ( control.onClick ) { control.onClick(); } diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 3a1ac01f332e6c..34f58970bf9f40 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -36,18 +36,20 @@ } } } + .components-dropdown-menu__popover .components-popover__content { width: 200px; } .components-dropdown-menu__menu { width: 100%; - padding: 9px; + padding: ($grid-size - $border-width) 0; font-family: $default-font; font-size: $default-font-size; line-height: $default-line-height; - .components-dropdown-menu__menu-item { + .components-dropdown-menu__menu-item, + .components-menu-item { width: 100%; padding: 6px; outline: none; @@ -92,4 +94,16 @@ @include formatting-button-style__active(); } } + + .components-menu-group:not(:last-child) { + border-bottom: $border-width solid $light-gray-500; + } + + .components-menu-item__button { + padding-left: 2rem; + + &.has-icon { + padding-left: 0.5rem; + } + } } diff --git a/packages/components/src/dropdown-menu/test/index.js b/packages/components/src/dropdown-menu/test/index.js index ef8e900de24f9f..74e0089eed68bb 100644 --- a/packages/components/src/dropdown-menu/test/index.js +++ b/packages/components/src/dropdown-menu/test/index.js @@ -1,21 +1,22 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; -import TestUtils from 'react-dom/test-utils'; +import { shallow, mount } from 'enzyme'; /** * WordPress dependencies */ import { DOWN } from '@wordpress/keycodes'; -import { Component } from '@wordpress/element'; /** * Internal dependencies */ import DropdownMenu from '../'; +import { IconButton, MenuItem, NavigableMenu } from '../../'; describe( 'DropdownMenu', () => { + const children = ( { onClose } ) => <MenuItem onClick={ onClose } />; + let controls; beforeEach( () => { controls = [ @@ -43,42 +44,48 @@ describe( 'DropdownMenu', () => { } ); describe( 'basic rendering', () => { - it( 'should render a null element when controls are not assigned', () => { + it( 'should render a null element when neither controls nor children are assigned', () => { const wrapper = shallow( <DropdownMenu /> ); expect( wrapper.type() ).toBeNull(); } ); - it( 'should render a null element when controls are empty', () => { + it( 'should render a null element when controls are empty and children is not specified', () => { const wrapper = shallow( <DropdownMenu controls={ [] } /> ); expect( wrapper.type() ).toBeNull(); } ); - it( 'should open menu on arrow down', () => { - // needed because TestUtils.renderIntoDocument returns null for stateless - // components - class Menu extends Component { - render() { - return <DropdownMenu { ...this.props } />; - } - } - const wrapper = TestUtils.renderIntoDocument( <Menu controls={ controls } /> ); - const buttonElement = TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-dropdown-menu__toggle' - ); - // Close menu by keyup - TestUtils.Simulate.keyDown( - buttonElement, - { - stopPropagation: () => {}, - preventDefault: () => {}, - keyCode: DOWN, - } - ); - - expect( TestUtils.scryRenderedDOMComponentsWithClass( wrapper, 'components-popover' ) ).toHaveLength( 1 ); + it( 'should open menu on arrow down (controls)', () => { + const wrapper = mount( <DropdownMenu controls={ controls } /> ); + const button = wrapper.find( IconButton ).filter( '.components-dropdown-menu__toggle' ); + + button.simulate( 'keydown', { + stopPropagation: () => {}, + preventDefault: () => {}, + keyCode: DOWN, + } ); + + expect( wrapper.find( NavigableMenu ) ).toHaveLength( 1 ); + expect( wrapper.find( IconButton ).filter( '.components-dropdown-menu__menu-item' ) ).toHaveLength( controls.length ); + } ); + + it( 'should open menu on arrow down (children)', () => { + const wrapper = mount( <DropdownMenu children={ children } /> ); + const button = wrapper.find( IconButton ).filter( '.components-dropdown-menu__toggle' ); + + button.simulate( 'keydown', { + stopPropagation: () => {}, + preventDefault: () => {}, + keyCode: DOWN, + } ); + + expect( wrapper.find( NavigableMenu ) ).toHaveLength( 1 ); + + wrapper.find( MenuItem ).props().onClick(); + wrapper.update(); + + expect( wrapper.find( NavigableMenu ) ).toHaveLength( 0 ); } ); } ); } ); diff --git a/packages/components/src/menu-group/index.js b/packages/components/src/menu-group/index.js index f5b2aff60337ed..ba98c5fea4fd22 100644 --- a/packages/components/src/menu-group/index.js +++ b/packages/components/src/menu-group/index.js @@ -9,11 +9,6 @@ import classnames from 'classnames'; import { Children } from '@wordpress/element'; import { withInstanceId } from '@wordpress/compose'; -/** - * Internal dependencies - */ -import { NavigableMenu } from '../navigable-container'; - export function MenuGroup( { children, className = '', @@ -33,11 +28,17 @@ export function MenuGroup( { return ( <div className={ classNames }> { label && - <div className="components-menu-group__label" id={ labelId }>{ label }</div> + <div + className="components-menu-group__label" + id={ labelId } + aria-hidden="true" + > + { label } + </div> } - <NavigableMenu orientation="vertical" aria-labelledby={ label ? labelId : null }> + <div role="group" aria-labelledby={ label ? labelId : null }> { children } - </NavigableMenu> + </div> </div> ); } diff --git a/packages/components/src/menu-group/test/__snapshots__/index.js.snap b/packages/components/src/menu-group/test/__snapshots__/index.js.snap index c33b2fcad917cd..4a0c0e751a2667 100644 --- a/packages/components/src/menu-group/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-group/test/__snapshots__/index.js.snap @@ -5,18 +5,19 @@ exports[`MenuGroup should match snapshot 1`] = ` className="components-menu-group" > <div + aria-hidden="true" className="components-menu-group__label" id="components-menu-group-label-1" > My group </div> - <ForwardRef(NavigableMenu) + <div aria-labelledby="components-menu-group-label-1" - orientation="vertical" + role="group" > <p> My item </p> - </ForwardRef(NavigableMenu)> + </div> </div> `; diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 7ef38dfc8c3036..8424da18a6f02d 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -10,7 +10,7 @@ .dashicon, .components-menu-items__item-icon, > span > svg { - margin-right: 4px; + margin-right: 5px; } .components-menu-items__item-icon { diff --git a/packages/e2e-test-utils/src/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js index 0465441ec64965..2e9ef6c946499b 100644 --- a/packages/e2e-test-utils/src/click-on-more-menu-item.js +++ b/packages/e2e-test-utils/src/click-on-more-menu-item.js @@ -10,7 +10,7 @@ import { first } from 'lodash'; */ export async function clickOnMoreMenuItem( buttonLabel ) { await expect( page ).toClick( - '.edit-post-more-menu [aria-label="Show more tools & options"]' + '.edit-post-more-menu [aria-label="More tools & options"]' ); const moreMenuContainerSelector = '//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]'; diff --git a/packages/e2e-test-utils/src/switch-editor-mode-to.js b/packages/e2e-test-utils/src/switch-editor-mode-to.js index 6978b2ee812472..c0ddfa274c9c2b 100644 --- a/packages/e2e-test-utils/src/switch-editor-mode-to.js +++ b/packages/e2e-test-utils/src/switch-editor-mode-to.js @@ -5,7 +5,7 @@ */ export async function switchEditorModeTo( mode ) { await page.click( - '.edit-post-more-menu [aria-label="Show more tools & options"]' + '.edit-post-more-menu [aria-label="More tools & options"]' ); const [ button ] = await page.$x( `//button[contains(text(), '${ mode } Editor')]` diff --git a/packages/e2e-tests/specs/adding-blocks.test.js b/packages/e2e-tests/specs/adding-blocks.test.js index aeafc65cd8cc39..332dab5083f3ba 100644 --- a/packages/e2e-tests/specs/adding-blocks.test.js +++ b/packages/e2e-tests/specs/adding-blocks.test.js @@ -97,7 +97,7 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Second paragraph' ); // Switch to Text Mode to check HTML Output - await page.click( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); + await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); const codeEditorButton = ( await page.$x( "//button[contains(text(), 'Code Editor')]" ) )[ 0 ]; await codeEditorButton.click( 'button' ); diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index 6adcb01681bb2a..194ff847328c18 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -3,6 +3,7 @@ */ import { clickBlockAppender, + clickBlockToolbarButton, getEditedPostContent, createNewPost, isInDefaultBlock, @@ -22,7 +23,7 @@ const addThreeParagraphsToNewPost = async () => { }; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { - await expect( page ).toClick( '.block-editor-block-settings-menu__toggle' ); + await clickBlockToolbarButton( 'More options' ); const itemButton = ( await page.$x( `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; diff --git a/packages/e2e-tests/specs/blocks/preformatted.test.js b/packages/e2e-tests/specs/blocks/preformatted.test.js index a5f9965af3c519..ea6575785613cf 100644 --- a/packages/e2e-tests/specs/blocks/preformatted.test.js +++ b/packages/e2e-tests/specs/blocks/preformatted.test.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { + clickBlockToolbarButton, getEditedPostContent, createNewPost, insertBlock, @@ -21,16 +22,12 @@ describe( 'Preformatted', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); - await page.keyboard.press( 'Escape' ); - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); await clickButton( 'Convert to Blocks' ); // Once it's edited, it should be saved as BR tags. await page.keyboard.type( '0' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.press( 'Escape' ); - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); await clickButton( 'Edit as HTML' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js index 1bb525123e197d..eda64312514c98 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -3,6 +3,7 @@ */ import { clickBlockAppender, + clickBlockToolbarButton, createNewPost, switchEditorModeTo, } from '@wordpress/e2e-test-utils'; @@ -23,8 +24,7 @@ describe( 'Editing modes (visual/HTML)', () => { await page.keyboard.press( 'Escape' ); // Change editing mode from "Visual" to "HTML". - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); let changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -36,8 +36,7 @@ describe( 'Editing modes (visual/HTML)', () => { await page.keyboard.press( 'Escape' ); // Change editing mode from "HTML" back to "Visual". - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); changeModeButton = await page.waitForXPath( '//button[text()="Edit visually"]' ); await changeModeButton.click(); @@ -51,8 +50,7 @@ describe( 'Editing modes (visual/HTML)', () => { await page.keyboard.press( 'Escape' ); // Change editing mode from "Visual" to "HTML". - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -67,8 +65,7 @@ describe( 'Editing modes (visual/HTML)', () => { await page.keyboard.press( 'Escape' ); // Change editing mode from "Visual" to "HTML". - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); await changeModeButton.click(); @@ -101,7 +98,7 @@ describe( 'Editing modes (visual/HTML)', () => { // Switch to Code Editor and hide More Menu await switchEditorModeTo( 'Code' ); await page.click( - '.edit-post-more-menu [aria-label="Hide more tools & options"]' + '.edit-post-more-menu [aria-label="More tools & options"]' ); // The Block inspector should not be active anymore diff --git a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js index 5253528a615cd0..64d1afcda1a61e 100644 --- a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js @@ -100,9 +100,7 @@ const tabThroughBlockToolbar = async () => { // Tab to focus on the 'More formatting' dropdown toggle await page.keyboard.press( 'Tab' ); const isFocusedBlockSettingsDropdown = await page.evaluate( () => - document.activeElement.classList.contains( - 'block-editor-block-settings-menu__toggle' - ) + document.activeElement.classList.contains( 'components-dropdown-menu__toggle' ) ); await expect( isFocusedBlockSettingsDropdown ).toBe( true ); }; diff --git a/packages/e2e-tests/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js index d8a64be089ecaf..40c980fac079cc 100644 --- a/packages/e2e-tests/specs/nux.test.js +++ b/packages/e2e-tests/specs/nux.test.js @@ -117,7 +117,7 @@ describe( 'New User Experience (NUX)', () => { await clickAllTips( page ); // Open the "More" menu to check the "Show Tips" element. - await page.click( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); + await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); const showTipsButton = await page.$x( '//button[contains(text(), "Show Tips")][@aria-pressed="false"]' ); expect( showTipsButton ).toHaveLength( 1 ); @@ -132,7 +132,7 @@ describe( 'New User Experience (NUX)', () => { await clickOnMoreMenuItem( 'Show Tips' ); // Open the "More" menu to check the "Show Tips" element. - await page.click( '.edit-post-more-menu [aria-label="Show more tools & options"]' ); + await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); const showTipsButton = await page.$x( '//button[contains(text(), "Show Tips")][@aria-pressed="true"]' ); expect( showTipsButton ).toHaveLength( 1 ); diff --git a/packages/e2e-tests/specs/plugins/annotations.test.js b/packages/e2e-tests/specs/plugins/annotations.test.js index 65474777d3db1d..5bab673a41d806 100644 --- a/packages/e2e-tests/specs/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/plugins/annotations.test.js @@ -3,13 +3,14 @@ */ import { activatePlugin, + clickBlockToolbarButton, clickOnMoreMenuItem, createNewPost, deactivatePlugin, } from '@wordpress/e2e-test-utils'; const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { - await expect( page ).toClick( '.block-editor-block-settings-menu__toggle' ); + await clickBlockToolbarButton( 'More options' ); const itemButton = ( await page.$x( `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; await itemButton.click(); }; diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index a8ab98c2dd3181..7a9174f5b6246a 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -155,7 +155,7 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Surprised greeting block' ); // Convert block to a regular block - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); const convertButton = await page.waitForXPath( '//button[text()="Convert to Regular Block"]' ); @@ -178,7 +178,7 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Surprised greeting block' ); // Delete the block and accept the confirmation dialog - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); const deleteButton = await page.waitForXPath( '//button[text()="Remove from Reusable Blocks"]' ); await Promise.all( [ waitForAndAcceptDialog(), deleteButton.click() ] ); @@ -214,8 +214,7 @@ describe( 'Reusable Blocks', () => { await pressKeyWithModifier( 'primary', 'a' ); // Convert block to a reusable block - await page.waitForSelector( 'button[aria-label="More options"]' ); - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable Blocks"]' ); await convertButton.click(); @@ -256,7 +255,7 @@ describe( 'Reusable Blocks', () => { await insertBlock( 'Multi-selection reusable block' ); // Convert block to a regular block - await page.click( 'button[aria-label="More options"]' ); + await clickBlockToolbarButton( 'More options' ); const convertButton = await page.waitForXPath( '//button[text()="Convert to Regular Block"]' ); diff --git a/packages/edit-post/src/components/header/more-menu/index.js b/packages/edit-post/src/components/header/more-menu/index.js index 9458a73c66b5e3..16d9b16afb77a1 100644 --- a/packages/edit-post/src/components/header/more-menu/index.js +++ b/packages/edit-post/src/components/header/more-menu/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { IconButton, Dropdown, MenuGroup } from '@wordpress/components'; +import { DropdownMenu, MenuGroup } from '@wordpress/components'; /** * Internal dependencies @@ -13,24 +13,16 @@ import ToolsMoreMenuGroup from '../tools-more-menu-group'; import OptionsMenuItem from '../options-menu-item'; import WritingMenu from '../writing-menu'; -const ariaClosed = __( 'Show more tools & options' ); -const ariaOpen = __( 'Hide more tools & options' ); - const MoreMenu = () => ( - <Dropdown + <DropdownMenu className="edit-post-more-menu" - contentClassName="edit-post-more-menu__content" position="bottom left" - renderToggle={ ( { isOpen, onToggle } ) => ( - <IconButton - icon="ellipsis" - label={ isOpen ? ariaOpen : ariaClosed } - labelPosition="bottom" - onClick={ onToggle } - aria-expanded={ isOpen } - /> - ) } - renderContent={ ( { onClose } ) => ( + icon="ellipsis" + label={ __( 'More tools & options' ) } + __unstableLabelPosition="bottom" + __unstablePopoverClassName="edit-post-more-menu__content" + > + { ( { onClose } ) => ( <> <WritingMenu /> <ModeSwitcher /> @@ -41,7 +33,7 @@ const MoreMenu = () => ( </MenuGroup> </> ) } - /> + </DropdownMenu> ); export default MoreMenu; diff --git a/packages/edit-post/src/components/header/more-menu/style.scss b/packages/edit-post/src/components/header/more-menu/style.scss index 011e4eeb91337b..a6e9a1cdc68ae3 100644 --- a/packages/edit-post/src/components/header/more-menu/style.scss +++ b/packages/edit-post/src/components/header/more-menu/style.scss @@ -29,16 +29,7 @@ max-width: $break-mobile; } - .components-menu-group:not(:last-child), - > div:not(:last-child) .components-menu-group { - border-bottom: $border-width solid $light-gray-500; - } - - .components-menu-item__button { - padding-left: 2rem; - - &.has-icon { - padding-left: 0.5rem; - } + .components-dropdown-menu__menu { + padding: 0; } } diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index 78f197fda6035d..cc17255097c02d 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -2,64 +2,70 @@ exports[`MoreMenu should match snapshot 1`] = ` <MoreMenu> - <Dropdown + <DropdownMenu + __unstableLabelPosition="bottom" + __unstablePopoverClassName="edit-post-more-menu__content" className="edit-post-more-menu" - contentClassName="edit-post-more-menu__content" + icon="ellipsis" + label="More tools & options" position="bottom left" - renderContent={[Function]} - renderToggle={[Function]} > - <div - className="edit-post-more-menu" + <Dropdown + className="components-dropdown-menu edit-post-more-menu" + contentClassName="components-dropdown-menu__popover edit-post-more-menu__content" + position="bottom left" + renderContent={[Function]} + renderToggle={[Function]} > - <ForwardRef(IconButton) - aria-expanded={false} - icon="ellipsis" - label="Show more tools & options" - labelPosition="bottom" - onClick={[Function]} + <div + className="components-dropdown-menu edit-post-more-menu" > - <Tooltip - position="bottom" - text="Show more tools & options" + <ForwardRef(IconButton) + aria-expanded={false} + aria-haspopup="true" + className="components-dropdown-menu__toggle" + icon="ellipsis" + label="More tools & options" + labelPosition="bottom" + onClick={[Function]} + onKeyDown={[Function]} + tooltip="More tools & options" > - <ForwardRef(Button) - aria-expanded={false} - aria-label="Show more tools & options" - className="components-icon-button" - onBlur={[Function]} - onClick={[Function]} - onFocus={[Function]} - onMouseEnter={[Function]} - onMouseLeave={[Function]} + <Tooltip + position="bottom" + text="More tools & options" > - <button + <ForwardRef(Button) aria-expanded={false} - aria-label="Show more tools & options" - className="components-button components-icon-button" + aria-haspopup="true" + aria-label="More tools & options" + className="components-icon-button components-dropdown-menu__toggle" onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onKeyDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} - type="button" > - <Dashicon - icon="ellipsis" - key="0,0" + <button + aria-expanded={false} + aria-haspopup="true" + aria-label="More tools & options" + className="components-button components-icon-button components-dropdown-menu__toggle" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" > - <SVG - aria-hidden={true} - className="dashicon dashicons-ellipsis" - focusable="false" - height={20} - role="img" - viewBox="0 0 20 20" - width={20} - xmlns="http://www.w3.org/2000/svg" + <Dashicon + icon="ellipsis" + key="0,0" > - <svg - aria-hidden="true" + <SVG + aria-hidden={true} className="dashicon dashicons-ellipsis" focusable="false" height={20} @@ -68,21 +74,32 @@ exports[`MoreMenu should match snapshot 1`] = ` width={20} xmlns="http://www.w3.org/2000/svg" > - <Path - d="M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" + <svg + aria-hidden="true" + className="dashicon dashicons-ellipsis" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" > - <path + <Path d="M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" - /> - </Path> - </svg> - </SVG> - </Dashicon> - </button> - </ForwardRef(Button)> - </Tooltip> - </ForwardRef(IconButton)> - </div> - </Dropdown> + > + <path + d="M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" + /> + </Path> + </svg> + </SVG> + </Dashicon> + </button> + </ForwardRef(Button)> + </Tooltip> + </ForwardRef(IconButton)> + </div> + </Dropdown> + </DropdownMenu> </MoreMenu> `; diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap index 6e395b2e7ebae9..0a2564900eda2e 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/test/__snapshots__/index.js.snap @@ -5,6 +5,7 @@ exports[`PluginMoreMenuItem renders menu item as button properly 1`] = ` className="components-menu-group" > <div + aria-hidden="true" className="components-menu-group__label" id="components-menu-group-label-0" > @@ -12,9 +13,7 @@ exports[`PluginMoreMenuItem renders menu item as button properly 1`] = ` </div> <div aria-labelledby="components-menu-group-label-0" - aria-orientation="vertical" - onKeyDown={[Function]} - role="menu" + role="group" > <button className="components-button components-icon-button components-menu-item__button has-icon has-text" From 26274151c5a6a5c1b7f58d181d44530dddd9cedc Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Mon, 3 Jun 2019 09:54:19 +0200 Subject: [PATCH 240/664] Port KeyboardAvoidingView, KeyboardAwareFlatList and ReadableContentView to @wordpress/components (#15913) * Port KeyboardAvoidingView, KeyboardAwareFlatList and ReadableContentView to @wordpress/components * Rename readable-content-view style.scss to native.scss * Fix bad Flow to ESNext port --- packages/components/src/index.native.js | 3 + .../keyboard-avoiding-view/index.android.js | 8 +++ .../keyboard-avoiding-view/index.ios.js | 15 +++++ .../keyboard-aware-flat-list/index.android.js | 21 +++++++ .../keyboard-aware-flat-list/index.ios.js | 57 +++++++++++++++++++ .../readable-content-view/index.native.js | 26 +++++++++ .../readable-content-view/style.native.scss | 8 +++ 7 files changed, 138 insertions(+) create mode 100644 packages/components/src/mobile/keyboard-avoiding-view/index.android.js create mode 100644 packages/components/src/mobile/keyboard-avoiding-view/index.ios.js create mode 100644 packages/components/src/mobile/keyboard-aware-flat-list/index.android.js create mode 100644 packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js create mode 100644 packages/components/src/mobile/readable-content-view/index.native.js create mode 100644 packages/components/src/mobile/readable-content-view/style.native.scss diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index f3bd5ba6951b16..881b011c8a08ab 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -22,3 +22,6 @@ export { default as withSpokenMessages } from './higher-order/with-spoken-messag // Mobile Components export { default as BottomSheet } from './mobile/bottom-sheet'; export { default as Picker } from './mobile/picker'; +export { default as KeyboardAvoidingView } from './mobile/keyboard-avoiding-view'; +export { default as KeyboardAwareFlatList } from './mobile/keyboard-aware-flat-list'; +export { default as ReadableContentView } from './mobile/readable-content-view'; diff --git a/packages/components/src/mobile/keyboard-avoiding-view/index.android.js b/packages/components/src/mobile/keyboard-avoiding-view/index.android.js new file mode 100644 index 00000000000000..10641b91b73cd1 --- /dev/null +++ b/packages/components/src/mobile/keyboard-avoiding-view/index.android.js @@ -0,0 +1,8 @@ +/** + * External dependencies + */ +import { KeyboardAvoidingView as AndroidKeyboardAvoidingView } from 'react-native'; + +export const KeyboardAvoidingView = AndroidKeyboardAvoidingView; + +export default KeyboardAvoidingView; diff --git a/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js b/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js new file mode 100644 index 00000000000000..55fac41b263e99 --- /dev/null +++ b/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js @@ -0,0 +1,15 @@ +/** + * External dependencies + */ +import { KeyboardAvoidingView as IOSKeyboardAvoidingView, Dimensions } from 'react-native'; + +export const KeyboardAvoidingView = ( { parentHeight, ...otherProps } ) => { + const { height: fullHeight } = Dimensions.get( 'window' ); + const keyboardVerticalOffset = fullHeight - parentHeight; + + return ( + <IOSKeyboardAvoidingView { ...otherProps } behavior="padding" keyboardVerticalOffset={ keyboardVerticalOffset } /> + ); +}; + +export default KeyboardAvoidingView; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js new file mode 100644 index 00000000000000..0774c845ebcf54 --- /dev/null +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { FlatList } from 'react-native'; + +/** + * Internal dependencies + */ +import KeyboardAvoidingView from '../keyboard-avoiding-view'; + +export const KeyboardAwareFlatList = ( props ) => ( + <KeyboardAvoidingView style={ { flex: 1 } }> + <FlatList { ...props } /> + </KeyboardAvoidingView> +); + +KeyboardAwareFlatList.handleCaretVerticalPositionChange = () => { + //no need to handle on Android, it is system managed +}; + +export default KeyboardAwareFlatList; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js new file mode 100644 index 00000000000000..341737b41bef2b --- /dev/null +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -0,0 +1,57 @@ +/** + * External dependencies + */ +import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; +import { FlatList } from 'react-native'; + +export const KeyboardAwareFlatList = ( { + blockToolbarHeight, + innerToolbarHeight, + shouldPreventAutomaticScroll, + innerRef, + ...listProps +} ) => ( + <KeyboardAwareScrollView + style={ { flex: 1 } } + keyboardDismissMode="none" + enableResetScrollToCoords={ false } + keyboardShouldPersistTaps="handled" + extraScrollHeight={ innerToolbarHeight } + extraBottomInset={ -listProps.safeAreaBottomInset } + inputAccessoryViewHeight={ blockToolbarHeight } + extraHeight={ 0 } + innerRef={ ( ref ) => { + this.scrollViewRef = ref; + innerRef( ref ); + } } + onKeyboardWillHide={ () => { + this.keyboardWillShowIndicator = false; + } } + onKeyboardDidHide={ () => { + setTimeout( () => { + if ( ! this.keyboardWillShowIndicator && + this.latestContentOffsetY !== undefined && + ! shouldPreventAutomaticScroll() ) { + // Reset the content position if keyboard is still closed + this.scrollViewRef.props.scrollToPosition( 0, this.latestContentOffsetY, true ); + } + }, 50 ); + } } + onKeyboardWillShow={ () => { + this.keyboardWillShowIndicator = true; + } } + onScroll={ ( event ) => { + this.latestContentOffsetY = event.nativeEvent.contentOffset.y; + } } > + <FlatList { ...listProps } /> + </KeyboardAwareScrollView> +); + +KeyboardAwareFlatList.handleCaretVerticalPositionChange = ( scrollView, targetId, caretY, previousCaretY ) => { + if ( previousCaretY ) { //if this is not the first tap + scrollView.props.refreshScrollForField( targetId ); + } +}; + +export default KeyboardAwareFlatList; + diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js new file mode 100644 index 00000000000000..bc550f93de9262 --- /dev/null +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { View, Dimensions } from 'react-native'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +const ReadableContentView = ( { children } ) => ( + <View style={ styles.container } > + <View style={ styles.centeredContent } > + { children } + </View> + </View> +); + +const isContentMaxWidth = () => { + const { width } = Dimensions.get( 'window' ); + return width > styles.centeredContent.maxWidth; +}; + +ReadableContentView.isContentMaxWidth = isContentMaxWidth; + +export default ReadableContentView; diff --git a/packages/components/src/mobile/readable-content-view/style.native.scss b/packages/components/src/mobile/readable-content-view/style.native.scss new file mode 100644 index 00000000000000..611d4054a470fe --- /dev/null +++ b/packages/components/src/mobile/readable-content-view/style.native.scss @@ -0,0 +1,8 @@ +.container { + align-items: center; +} + +.centeredContent { + width: 100%; + max-width: 580; +} From 74b27d288125672bda6762ab31c37eb64e0bb1fc Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 3 Jun 2019 10:01:12 +0100 Subject: [PATCH 241/664] Remove the use of popular plugins in e2e tests (#15940) --- .travis.yml | 24 +++++++++---------- bin/install-wordpress.sh | 5 ---- .../e2e-tests/specs/block-transforms.test.js | 8 +------ 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7ff3c9b0445020..8d1046f2104d1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,39 +81,39 @@ jobs: script: - ./bin/run-wp-unit-tests.sh - - name: E2E tests (Admin with plugins) (1/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= + - name: E2E tests (Admin) (1/4) + env: WP_VERSION=latest SCRIPT_DEBUG=false PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - - name: E2E tests (Admin with plugins) (2/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= + - name: E2E tests (Admin) (2/4) + env: WP_VERSION=latest SCRIPT_DEBUG=false PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - - name: E2E tests (Admin with plugins) (3/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= + - name: E2E tests (Admin) (3/4) + env: WP_VERSION=latest SCRIPT_DEBUG=false PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - - name: E2E tests (Admin with plugins) (4/4) - env: WP_VERSION=latest SCRIPT_DEBUG=false POPULAR_PLUGINS=true PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= + - name: E2E tests (Admin) (4/4) + env: WP_VERSION=latest SCRIPT_DEBUG=false PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh script: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) - - name: E2E tests (Author without plugins) (1/4) + - name: E2E tests (Author) (1/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh @@ -121,7 +121,7 @@ jobs: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 0' < ~/.jest-e2e-tests ) - - name: E2E tests (Author without plugins) (2/4) + - name: E2E tests (Author) (2/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh @@ -129,7 +129,7 @@ jobs: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 1' < ~/.jest-e2e-tests ) - - name: E2E tests (Author without plugins) (3/4) + - name: E2E tests (Author) (3/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh @@ -137,7 +137,7 @@ jobs: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --listTests > ~/.jest-e2e-tests - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 2' < ~/.jest-e2e-tests ) - - name: E2E tests (Author without plugins) (4/4) + - name: E2E tests (Author) (4/4) env: WP_VERSION=latest SCRIPT_DEBUG=false E2E_ROLE=author PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= install: - ./bin/setup-travis-e2e-tests.sh diff --git a/bin/install-wordpress.sh b/bin/install-wordpress.sh index 2b7b1ebf84b393..551e8bd0dc77e1 100755 --- a/bin/install-wordpress.sh +++ b/bin/install-wordpress.sh @@ -90,11 +90,6 @@ fi echo -e $(status_message "Activating Gutenberg...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin activate gutenberg --quiet -if [ "$POPULAR_PLUGINS" == "true" ]; then - echo -e $(status_message "Activating popular plugins...") - docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm -u 33 $CLI plugin install advanced-custom-fields jetpack wpforms-lite --activate --quiet -fi - # Install a dummy favicon to avoid 404 errors. echo -e $(status_message "Installing a dummy favicon...") docker-compose $DOCKER_COMPOSE_FILE_OPTIONS run --rm $CONTAINER touch /var/www/html/favicon.ico diff --git a/packages/e2e-tests/specs/block-transforms.test.js b/packages/e2e-tests/specs/block-transforms.test.js index 8a4a2a38461bb4..a16a7a1ec10035 100644 --- a/packages/e2e-tests/specs/block-transforms.test.js +++ b/packages/e2e-tests/specs/block-transforms.test.js @@ -94,13 +94,7 @@ const getTransformResult = async ( blockContent, transformName ) => { return getEditedPostContent(); }; -// Skipping all the tests when plugins are enabled -// makes sure the tests are not executed, and no unused snapshots errors are thrown. -const maybeDescribe = process.env.POPULAR_PLUGINS ? - describe.skip : - describe; - -maybeDescribe( 'Block transforms', () => { +describe( 'Block transforms', () => { const fileBasenames = getAvailableBlockFixturesBasenames(); const transformStructure = {}; From 2f4a6f1ec4d28890442116ebbd9b09dabfe57737 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 3 Jun 2019 11:04:25 +0100 Subject: [PATCH 242/664] Fix: DateTimePicker styles don't load as expected outside WordPress context. (#15389) --- packages/components/src/date-time/style.scss | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 9d2f8ea61240ca..02b2686f4f8821 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -19,6 +19,21 @@ margin-right: $grid-size; margin-top: 0.5em; } + + fieldset { + border: 0; + padding: 0; + margin: 0; + } + + select, + input { + box-sizing: border-box; + height: 28px; + vertical-align: middle; + padding: 0; + @include input-style__neutral(); + } } .components-datetime__date { From 97806141c43af90f597c3ed1196d79a7442166a8 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Mon, 3 Jun 2019 06:12:33 -0400 Subject: [PATCH 243/664] @wordpress/data: Introduce new custom `useDispatch` react hook (#15896) This pull contains the work exposing a new `useDispatch` hook on the @wordpress/data package. Also, `withDispatch` has been refactored internally to use a new (internal only) `useDispatchWithMap` hook. --- packages/data/CHANGELOG.md | 2 + packages/data/README.md | 107 ++++++++++---- .../data/src/components/use-dispatch/index.js | 2 + .../use-dispatch/test/use-dispatch.js | 137 ++++++++++++++++++ .../use-dispatch/use-dispatch-with-map.js | 71 +++++++++ .../components/use-dispatch/use-dispatch.js | 53 +++++++ .../src/components/with-dispatch/index.js | 135 ++++++----------- .../components/with-dispatch/test/index.js | 104 ++++++++----- packages/data/src/index.js | 1 + 9 files changed, 459 insertions(+), 153 deletions(-) create mode 100644 packages/data/src/components/use-dispatch/index.js create mode 100644 packages/data/src/components/use-dispatch/test/use-dispatch.js create mode 100644 packages/data/src/components/use-dispatch/use-dispatch-with-map.js create mode 100644 packages/data/src/components/use-dispatch/use-dispatch.js diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 391d68abe4c463..6851e89823c6ac 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -3,10 +3,12 @@ ### New Feature - Expose `useSelect` hook for usage in functional components. ([#15737](https://github.com/WordPress/gutenberg/pull/15737)) +- Expose `useDispatch` hook for usage in functional components. ([#15896](https://github.com/WordPress/gutenberg/pull/15896)) ### Enhancements - `withSelect` internally uses the new `useSelect` hook. ([#15737](https://github.com/WordPress/gutenberg/pull/15737). **Note:** This _could_ impact performance of code using `withSelect` in edge-cases. To avoid impact, memoize passed in `mapSelectToProps` callbacks or implement `useSelect` directly with dependencies. +- `withDispatch` internally uses a new `useDispatchWithMap` hook (an internal only api) ([#15896](https://github.com/WordPress/gutenberg/pull/15896)) ## 4.5.0 (2019-05-21) diff --git a/packages/data/README.md b/packages/data/README.md index 45a7cdda6f86b9..82a151ebffb08e 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -486,6 +486,52 @@ _Parameters_ - _plugin_ `Object`: Plugin object. +<a name="useDispatch" href="#useDispatch">#</a> **useDispatch** + +A custom react hook returning the current registry dispatch actions creators. + +Note: The component using this hook must be within the context of a +RegistryProvider. + +_Usage_ + +This illustrates a pattern where you may need to retrieve dynamic data from +the server via the `useSelect` hook to use in combination with the dispatch +action. + +```jsx +const { useDispatch, useSelect } = wp.data; +const { useCallback } = wp.element; + +function Button( { onClick, children } ) { + return <button type="button" onClick={ onClick }>{ children }</button> +} + +const SaleButton = ( { children } ) => { + const { stockNumber } = useSelect( + ( select ) => select( 'my-shop' ).getStockNumber() + ); + const { startSale } = useDispatch( 'my-shop' ); + const onClick = useCallback( () => { + const discountPercent = stockNumber > 50 ? 10: 20; + startSale( discountPercent ); + }, [ stockNumber ] ); + return <Button onClick={ onClick }>{ children }</Button> +} + +// Rendered somewhere in the application: +// +// <SaleButton>Start Sale!</SaleButton> +``` + +_Parameters_ + +- _storeName_ `[string]`: Optionally provide the name of the store from which to retrieve action creators. If not provided, the registry.dispatch function is returned instead. + +_Returns_ + +- `Function`: A custom react hook. + <a name="useRegistry" href="#useRegistry">#</a> **useRegistry** A custom react hook exposing the registry context for use. @@ -573,53 +619,64 @@ _Returns_ <a name="withDispatch" href="#withDispatch">#</a> **withDispatch** -Higher-order component used to add dispatch props using registered action creators. +Higher-order component used to add dispatch props using registered action +creators. _Usage_ ```jsx function Button( { onClick, children } ) { - return <button type="button" onClick={ onClick }>{ children }</button>; + return <button type="button" onClick={ onClick }>{ children }</button>; } const { withDispatch } = wp.data; const SaleButton = withDispatch( ( dispatch, ownProps ) => { - const { startSale } = dispatch( 'my-shop' ); - const { discountPercent } = ownProps; - - return { - onClick() { - startSale( discountPercent ); - }, - }; + const { startSale } = dispatch( 'my-shop' ); + const { discountPercent } = ownProps; + + return { + onClick() { + startSale( discountPercent ); + }, + }; } )( Button ); // Rendered in the application: // -// <SaleButton discountPercent="20">Start Sale!</SaleButton> +// <SaleButton discountPercent="20">Start Sale!</SaleButton> ``` -In the majority of cases, it will be sufficient to use only two first params passed to `mapDispatchToProps` as illustrated in the previous example. However, there might be some very advanced use cases where using the `registry` object might be used as a tool to optimize the performance of your component. Using `select` function from the registry might be useful when you need to fetch some dynamic data from the store at the time when the event is fired, but at the same time, you never use it to render your component. In such scenario, you can avoid using the `withSelect` higher order component to compute such prop, which might lead to unnecessary re-renders of your component caused by its frequent value change. Keep in mind, that `mapDispatchToProps` must return an object with functions only. +In the majority of cases, it will be sufficient to use only two first params +passed to `mapDispatchToProps` as illustrated in the previous example. +However, there might be some very advanced use cases where using the +`registry` object might be used as a tool to optimize the performance of +your component. Using `select` function from the registry might be useful +when you need to fetch some dynamic data from the store at the time when the +event is fired, but at the same time, you never use it to render your +component. In such scenario, you can avoid using the `withSelect` higher +order component to compute such prop, which might lead to unnecessary +re-renders of your component caused by its frequent value change. +Keep in mind, that `mapDispatchToProps` must return an object with functions +only. ```jsx function Button( { onClick, children } ) { - return <button type="button" onClick={ onClick }>{ children }</button>; + return <button type="button" onClick={ onClick }>{ children }</button>; } const { withDispatch } = wp.data; const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => { - // Stock number changes frequently. - const { getStockNumber } = select( 'my-shop' ); - const { startSale } = dispatch( 'my-shop' ); - - return { - onClick() { - const dicountPercent = getStockNumber() > 50 ? 10 : 20; - startSale( discountPercent ); - }, - }; + // Stock number changes frequently. + const { getStockNumber } = select( 'my-shop' ); + const { startSale } = dispatch( 'my-shop' ); + return { + onClick() { + const discountPercent = getStockNumber() > 50 ? 10 : 20; + startSale( discountPercent ); + }, + }; } )( Button ); // Rendered in the application: @@ -627,11 +684,9 @@ const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => { // <SaleButton>Start Sale!</SaleButton> ``` -_Note:_ It is important that the `mapDispatchToProps` function always returns an object with the same keys. For example, it should not contain conditions under which a different value would be returned. - _Parameters_ -- _mapDispatchToProps_ `Object`: Object of prop names where value is a dispatch-bound action creator, or a function to be called with the component's props and returning an action creator. +- _mapDispatchToProps_ `Function`: A function of returning an object of prop names where value is a dispatch-bound action creator, or a function to be called with the component's props and returning an action creator. _Returns_ diff --git a/packages/data/src/components/use-dispatch/index.js b/packages/data/src/components/use-dispatch/index.js new file mode 100644 index 00000000000000..f4f4fed37d38ca --- /dev/null +++ b/packages/data/src/components/use-dispatch/index.js @@ -0,0 +1,2 @@ +export { default as useDispatch } from './use-dispatch'; +export { default as useDispatchWithMap } from './use-dispatch-with-map'; diff --git a/packages/data/src/components/use-dispatch/test/use-dispatch.js b/packages/data/src/components/use-dispatch/test/use-dispatch.js new file mode 100644 index 00000000000000..8770d04144b06d --- /dev/null +++ b/packages/data/src/components/use-dispatch/test/use-dispatch.js @@ -0,0 +1,137 @@ +/** + * External dependencies + */ +import TestRenderer, { act } from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import useDispatch from '../use-dispatch'; +import { createRegistry } from '../../../registry'; +import { RegistryProvider } from '../../registry-provider'; + +describe( 'useDispatch', () => { + let registry; + beforeEach( () => { + registry = createRegistry(); + } ); + + it( 'returns dispatch function from store with no store name provided', () => { + registry.registerStore( 'demoStore', { + reducer: ( state ) => state, + actions: { + foo: () => 'bar', + }, + } ); + const TestComponent = () => { + return <div></div>; + }; + const Component = () => { + const dispatch = useDispatch(); + return <TestComponent dispatch={ dispatch } />; + }; + + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <Component /> + </RegistryProvider> + ); + } ); + + const testInstance = testRenderer.root; + + expect( testInstance.findByType( TestComponent ).props.dispatch ) + .toBe( registry.dispatch ); + } ); + it( 'returns expected action creators from store for given storeName', () => { + const noop = () => ( { type: '__INERT__' } ); + const testAction = jest.fn().mockImplementation( noop ); + registry.registerStore( 'demoStore', { + reducer: ( state ) => state, + actions: { + foo: testAction, + }, + } ); + const TestComponent = () => { + const { foo } = useDispatch( 'demoStore' ); + return <button onClick={ foo } />; + }; + + let testRenderer; + + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry } > + <TestComponent /> + </RegistryProvider> + ); + } ); + + const testInstance = testRenderer.root; + + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); + + expect( testAction ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'returns dispatch from correct registry if registries change', () => { + const reducer = ( state ) => state; + const noop = () => ( { type: '__INERT__' } ); + const firstRegistryAction = jest.fn().mockImplementation( noop ); + const secondRegistryAction = jest.fn().mockImplementation( noop ); + + const firstRegistry = registry; + firstRegistry.registerStore( 'demo', { + reducer, + actions: { + noop: firstRegistryAction, + }, + } ); + + const TestComponent = () => { + const dispatch = useDispatch(); + return <button onClick={ () => dispatch( 'demo' ).noop() } />; + }; + + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ firstRegistry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + const testInstance = testRenderer.root; + + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); + + expect( firstRegistryAction ).toHaveBeenCalledTimes( 1 ); + expect( secondRegistryAction ).toHaveBeenCalledTimes( 0 ); + + const secondRegistry = createRegistry(); + secondRegistry.registerStore( 'demo', { + reducer, + actions: { + noop: secondRegistryAction, + }, + } ); + + act( () => { + testRenderer.update( + <RegistryProvider value={ secondRegistry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); + expect( firstRegistryAction ).toHaveBeenCalledTimes( 1 ); + expect( secondRegistryAction ).toHaveBeenCalledTimes( 1 ); + } ); +} ); diff --git a/packages/data/src/components/use-dispatch/use-dispatch-with-map.js b/packages/data/src/components/use-dispatch/use-dispatch-with-map.js new file mode 100644 index 00000000000000..057ddab06191e9 --- /dev/null +++ b/packages/data/src/components/use-dispatch/use-dispatch-with-map.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { mapValues } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useMemo, useRef, useEffect, useLayoutEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useRegistry from '../registry-provider/use-registry'; + +/** + * Favor useLayoutEffect to ensure the store subscription callback always has + * the dispatchMap from the latest render. If a store update happens between + * render and the effect, this could cause missed/stale updates or + * inconsistent state. + * + * Fallback to useEffect for server rendered components because currently React + * throws a warning when using useLayoutEffect in that environment. + */ +const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +/** + * Custom react hook for returning aggregate dispatch actions using the provided + * dispatchMap. + * + * Currently this is an internal api only and is implemented by `withDispatch` + * + * @param {Function} dispatchMap Receives the `registry.dispatch` function as + * the first argument and the `registry` object + * as the second argument. Should return an + * object mapping props to functions. + * @param {Array} deps An array of dependencies for the hook. + * @return {Object} An object mapping props to functions created by the passed + * in dispatchMap. + */ +const useDispatchWithMap = ( dispatchMap, deps ) => { + const registry = useRegistry(); + const currentDispatchMap = useRef( dispatchMap ); + + useIsomorphicLayoutEffect( () => { + currentDispatchMap.current = dispatchMap; + } ); + + return useMemo( () => { + const currentDispatchProps = currentDispatchMap.current( + registry.dispatch, + registry + ); + return mapValues( + currentDispatchProps, + ( dispatcher, propName ) => { + if ( typeof dispatcher !== 'function' ) { + // eslint-disable-next-line no-console + console.warn( + `Property ${ propName } returned from dispatchMap in useDispatchWithMap must be a function.` + ); + } + return ( ...args ) => currentDispatchMap + .current( registry.dispatch, registry )[ propName ]( ...args ); + } + ); + }, [ registry, ...deps ] ); +}; + +export default useDispatchWithMap; diff --git a/packages/data/src/components/use-dispatch/use-dispatch.js b/packages/data/src/components/use-dispatch/use-dispatch.js new file mode 100644 index 00000000000000..985869d8057fb2 --- /dev/null +++ b/packages/data/src/components/use-dispatch/use-dispatch.js @@ -0,0 +1,53 @@ +/** + * Internal dependencies + */ +import useRegistry from '../registry-provider/use-registry'; + +/** + * A custom react hook returning the current registry dispatch actions creators. + * + * Note: The component using this hook must be within the context of a + * RegistryProvider. + * + * @param {string} [storeName] Optionally provide the name of the store from + * which to retrieve action creators. If not + * provided, the registry.dispatch function is + * returned instead. + * + * @example + * This illustrates a pattern where you may need to retrieve dynamic data from + * the server via the `useSelect` hook to use in combination with the dispatch + * action. + * + * ```jsx + * const { useDispatch, useSelect } = wp.data; + * const { useCallback } = wp.element; + * + * function Button( { onClick, children } ) { + * return <button type="button" onClick={ onClick }>{ children }</button> + * } + * + * const SaleButton = ( { children } ) => { + * const { stockNumber } = useSelect( + * ( select ) => select( 'my-shop' ).getStockNumber() + * ); + * const { startSale } = useDispatch( 'my-shop' ); + * const onClick = useCallback( () => { + * const discountPercent = stockNumber > 50 ? 10: 20; + * startSale( discountPercent ); + * }, [ stockNumber ] ); + * return <Button onClick={ onClick }>{ children }</Button> + * } + * + * // Rendered somewhere in the application: + * // + * // <SaleButton>Start Sale!</SaleButton> + * ``` + * @return {Function} A custom react hook. + */ +const useDispatch = ( storeName ) => { + const { dispatch } = useRegistry(); + return storeName === void 0 ? dispatch : dispatch( storeName ); +}; + +export default useDispatch; diff --git a/packages/data/src/components/with-dispatch/index.js b/packages/data/src/components/with-dispatch/index.js index 1da0aa95f3de7a..117edd1f598293 100644 --- a/packages/data/src/components/with-dispatch/index.js +++ b/packages/data/src/components/with-dispatch/index.js @@ -1,138 +1,97 @@ -/** - * External dependencies - */ -import { mapValues } from 'lodash'; - /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ -import { RegistryConsumer } from '../registry-provider'; +import { useDispatchWithMap } from '../use-dispatch'; /** - * Higher-order component used to add dispatch props using registered action creators. + * Higher-order component used to add dispatch props using registered action + * creators. * - * @param {Object} mapDispatchToProps Object of prop names where value is a - * dispatch-bound action creator, or a - * function to be called with the - * component's props and returning an - * action creator. + * @param {Function} mapDispatchToProps A function of returning an object of + * prop names where value is a + * dispatch-bound action creator, or a + * function to be called with the + * component's props and returning an + * action creator. * * @example * ```jsx * function Button( { onClick, children } ) { - * return <button type="button" onClick={ onClick }>{ children }</button>; + * return <button type="button" onClick={ onClick }>{ children }</button>; * } * * const { withDispatch } = wp.data; * * const SaleButton = withDispatch( ( dispatch, ownProps ) => { - * const { startSale } = dispatch( 'my-shop' ); - * const { discountPercent } = ownProps; + * const { startSale } = dispatch( 'my-shop' ); + * const { discountPercent } = ownProps; * - * return { - * onClick() { - * startSale( discountPercent ); - * }, - * }; + * return { + * onClick() { + * startSale( discountPercent ); + * }, + * }; * } )( Button ); * * // Rendered in the application: * // - * // <SaleButton discountPercent="20">Start Sale!</SaleButton> + * // <SaleButton discountPercent="20">Start Sale!</SaleButton> * ``` * * @example - * In the majority of cases, it will be sufficient to use only two first params passed to `mapDispatchToProps` as illustrated in the previous example. However, there might be some very advanced use cases where using the `registry` object might be used as a tool to optimize the performance of your component. Using `select` function from the registry might be useful when you need to fetch some dynamic data from the store at the time when the event is fired, but at the same time, you never use it to render your component. In such scenario, you can avoid using the `withSelect` higher order component to compute such prop, which might lead to unnecessary re-renders of your component caused by its frequent value change. Keep in mind, that `mapDispatchToProps` must return an object with functions only. + * In the majority of cases, it will be sufficient to use only two first params + * passed to `mapDispatchToProps` as illustrated in the previous example. + * However, there might be some very advanced use cases where using the + * `registry` object might be used as a tool to optimize the performance of + * your component. Using `select` function from the registry might be useful + * when you need to fetch some dynamic data from the store at the time when the + * event is fired, but at the same time, you never use it to render your + * component. In such scenario, you can avoid using the `withSelect` higher + * order component to compute such prop, which might lead to unnecessary + * re-renders of your component caused by its frequent value change. + * Keep in mind, that `mapDispatchToProps` must return an object with functions + * only. * * ```jsx * function Button( { onClick, children } ) { - * return <button type="button" onClick={ onClick }>{ children }</button>; + * return <button type="button" onClick={ onClick }>{ children }</button>; * } * * const { withDispatch } = wp.data; * * const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => { - * // Stock number changes frequently. - * const { getStockNumber } = select( 'my-shop' ); - * const { startSale } = dispatch( 'my-shop' ); - * - * return { - * onClick() { - * const dicountPercent = getStockNumber() > 50 ? 10 : 20; - * startSale( discountPercent ); - * }, - * }; + * // Stock number changes frequently. + * const { getStockNumber } = select( 'my-shop' ); + * const { startSale } = dispatch( 'my-shop' ); + * return { + * onClick() { + * const discountPercent = getStockNumber() > 50 ? 10 : 20; + * startSale( discountPercent ); + * }, + * }; * } )( Button ); * * // Rendered in the application: * // * // <SaleButton>Start Sale!</SaleButton> * ``` - * _Note:_ It is important that the `mapDispatchToProps` function always returns an object with the same keys. For example, it should not contain conditions under which a different value would be returned. * * @return {Component} Enhanced component with merged dispatcher props. */ const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent( - ( WrappedComponent ) => { - class ComponentWithDispatch extends Component { - constructor( props ) { - super( ...arguments ); - - this.proxyProps = {}; - - this.setProxyProps( props ); - } - - proxyDispatch( propName, ...args ) { - // Original dispatcher is a pre-bound (dispatching) action creator. - mapDispatchToProps( this.props.registry.dispatch, this.props.ownProps, this.props.registry )[ propName ]( ...args ); - } - - setProxyProps( props ) { - // Assign as instance property so that in subsequent render - // reconciliation, the prop values are referentially equal. - // Importantly, note that while `mapDispatchToProps` is - // called, it is done only to determine the keys for which - // proxy functions should be created. The actual registry - // dispatch does not occur until the function is called. - const propsToDispatchers = mapDispatchToProps( this.props.registry.dispatch, props.ownProps, this.props.registry ); - this.proxyProps = mapValues( propsToDispatchers, ( dispatcher, propName ) => { - if ( typeof dispatcher !== 'function' ) { - // eslint-disable-next-line no-console - console.warn( `Property ${ propName } returned from mapDispatchToProps in withDispatch must be a function.` ); - } - // Prebind with prop name so we have reference to the original - // dispatcher to invoke. Track between re-renders to avoid - // creating new function references every render. - if ( this.proxyProps.hasOwnProperty( propName ) ) { - return this.proxyProps[ propName ]; - } - - return this.proxyDispatch.bind( this, propName ); - } ); - } - - render() { - return <WrappedComponent { ...this.props.ownProps } { ...this.proxyProps } />; - } - } - - return ( ownProps ) => ( - <RegistryConsumer> - { ( registry ) => ( - <ComponentWithDispatch - ownProps={ ownProps } - registry={ registry } - /> - ) } - </RegistryConsumer> + ( WrappedComponent ) => ( ownProps ) => { + const mapDispatch = ( dispatch, registry ) => mapDispatchToProps( + dispatch, + ownProps, + registry ); + const dispatchProps = useDispatchWithMap( mapDispatch, [] ); + return <WrappedComponent { ...ownProps } { ...dispatchProps } />; }, 'withDispatch' ); diff --git a/packages/data/src/components/with-dispatch/test/index.js b/packages/data/src/components/with-dispatch/test/index.js index 4bde9b30810ac1..66fba4028e84f8 100644 --- a/packages/data/src/components/with-dispatch/test/index.js +++ b/packages/data/src/components/with-dispatch/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import TestRenderer from 'react-test-renderer'; +import TestRenderer, { act } from 'react-test-renderer'; /** * Internal dependencies @@ -45,27 +45,34 @@ describe( 'withDispatch', () => { }; } )( ( props ) => <button onClick={ props.increment } /> ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <Component count={ 0 } /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <Component count={ 0 } /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; const incrementBeforeSetProps = testInstance.findByType( 'button' ).props.onClick; // Verify that dispatch respects props at the time of being invoked by // changing props after the initial mount. - testRenderer.update( - <RegistryProvider value={ registry }> - <Component count={ 2 } /> - </RegistryProvider> - ); + act( () => { + testRenderer.update( + <RegistryProvider value={ registry }> + <Component count={ 2 } /> + </RegistryProvider> + ); + } ); // Function value reference should not have changed in props update. expect( testInstance.findByType( 'button' ).props.onClick ).toBe( incrementBeforeSetProps ); - incrementBeforeSetProps(); + act( () => { + incrementBeforeSetProps(); + } ); expect( store.getState() ).toBe( 2 ); } ); @@ -95,14 +102,19 @@ describe( 'withDispatch', () => { }; } )( ( props ) => <button onClick={ props.noop } /> ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ firstRegistry }> - <Component /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ firstRegistry }> + <Component /> + </RegistryProvider> + ); + } ); const testInstance = testRenderer.root; - testInstance.findByType( 'button' ).props.onClick(); + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); expect( firstRegistryAction ).toHaveBeenCalledTimes( 2 ); expect( secondRegistryAction ).toHaveBeenCalledTimes( 0 ); @@ -114,13 +126,16 @@ describe( 'withDispatch', () => { }, } ); - testRenderer.update( - <RegistryProvider value={ secondRegistry }> - <Component /> - </RegistryProvider> - ); - - testInstance.findByType( 'button' ).props.onClick(); + act( () => { + testRenderer.update( + <RegistryProvider value={ secondRegistry }> + <Component /> + </RegistryProvider> + ); + } ); + act( () => { + testInstance.findByType( 'button' ).props.onClick(); + } ); expect( firstRegistryAction ).toHaveBeenCalledTimes( 2 ); expect( secondRegistryAction ).toHaveBeenCalledTimes( 2 ); } ); @@ -159,21 +174,30 @@ describe( 'withDispatch', () => { }; } )( ( props ) => <button onClick={ props.update } /> ); - const testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <Component /> - </RegistryProvider> - ); + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <Component /> + </RegistryProvider> + ); + } ); const counterUpdateHandler = testRenderer.root.findByType( 'button' ).props.onClick; - counterUpdateHandler(); + act( () => { + counterUpdateHandler(); + } ); expect( store.getState() ).toBe( 1 ); - counterUpdateHandler(); + act( () => { + counterUpdateHandler(); + } ); expect( store.getState() ).toBe( 2 ); - counterUpdateHandler(); + act( () => { + counterUpdateHandler(); + } ); expect( store.getState() ).toBe( 3 ); } ); @@ -184,13 +208,15 @@ describe( 'withDispatch', () => { }; } )( () => null ); - TestRenderer.create( - <RegistryProvider value={ registry }> - <Component /> - </RegistryProvider> - ); + act( () => { + TestRenderer.create( + <RegistryProvider value={ registry }> + <Component /> + </RegistryProvider> + ); + } ); expect( console ).toHaveWarnedWith( - 'Property count returned from mapDispatchToProps in withDispatch must be a function.' + 'Property count returned from dispatchMap in useDispatchWithMap must be a function.' ); } ); } ); diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 00f1e367eb794d..da00faec053d41 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -18,6 +18,7 @@ export { useRegistry, } from './components/registry-provider'; export { default as useSelect } from './components/use-select'; +export { useDispatch } from './components/use-dispatch'; export { AsyncModeProvider as __experimentalAsyncModeProvider, } from './components/async-mode-provider'; From b710cc5619e3ba22bc2c81c1fc39aa1e207a9ad7 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 3 Jun 2019 11:30:52 +0100 Subject: [PATCH 244/664] Extract generic WordPress mediaUtils package. (#15521) This PR extracts the mediaUpload function part of @wordpress/editor to an independent package and now calls it uploadMedia. The MediaUpload component used to control WordPress media library is also part of this new package. The package is used to implement media upload in the widgets screen. --- docs/manifest-devhub.json | 6 +++ docs/manifest.json | 8 +++- package-lock.json | 12 +++++ package.json | 1 + .../edit-post/src/hooks/components/index.js | 8 +--- .../src/components/widget-area/index.js | 33 +++++++++++++- .../src/hooks/components/index.js | 13 ++++++ packages/edit-widgets/src/hooks/index.js | 4 ++ packages/edit-widgets/src/index.js | 1 + packages/editor/package.json | 1 + .../editor/src/utils/media-upload/index.js | 8 +--- packages/media-utils/.npmrc | 1 + packages/media-utils/CHANGELOG.md | 5 +++ packages/media-utils/README.md | 45 +++++++++++++++++++ packages/media-utils/package.json | 35 +++++++++++++++ packages/media-utils/src/components/index.js | 1 + .../src}/components/media-upload/index.js | 0 packages/media-utils/src/index.js | 2 + packages/media-utils/src/utils/index.js | 1 + .../src/utils/test/upload-media.test.js} | 22 ++++----- .../src/utils/upload-media.js} | 2 +- 21 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 packages/edit-widgets/src/hooks/components/index.js create mode 100644 packages/edit-widgets/src/hooks/index.js create mode 100644 packages/media-utils/.npmrc create mode 100644 packages/media-utils/CHANGELOG.md create mode 100644 packages/media-utils/README.md create mode 100644 packages/media-utils/package.json create mode 100644 packages/media-utils/src/components/index.js rename packages/{edit-post/src/hooks => media-utils/src}/components/media-upload/index.js (100%) create mode 100644 packages/media-utils/src/index.js create mode 100644 packages/media-utils/src/utils/index.js rename packages/{editor/src/utils/media-upload/test/media-upload.js => media-utils/src/utils/test/upload-media.test.js} (95%) rename packages/{editor/src/utils/media-upload/media-upload.js => media-utils/src/utils/upload-media.js} (99%) diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index f762d1422c454d..14b29c43ddb989 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -1301,6 +1301,12 @@ "markdown_source": "../packages/list-reusable-blocks/README.md", "parent": "packages" }, + { + "title": "@wordpress/media-utils", + "slug": "packages-media-utils", + "markdown_source": "../packages/media-utils/README.md", + "parent": "packages" + }, { "title": "@wordpress/notices", "slug": "packages-notices", diff --git a/docs/manifest.json b/docs/manifest.json index 6fbe894c39d69e..6cee0109f47498 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -449,6 +449,12 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/list-reusable-blocks/README.md", "parent": "packages" }, + { + "title": "@wordpress/media-utils", + "slug": "packages-media-utils", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/packages/media-utils/README.md", + "parent": "packages" + }, { "title": "@wordpress/notices", "slug": "packages-notices", @@ -1337,4 +1343,4 @@ "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/contributors/outreach.md", "parent": "contributors" } -] \ No newline at end of file +] diff --git a/package-lock.json b/package-lock.json index 00d2e2b1810a07..accdd4ccea7fc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3584,6 +3584,7 @@ "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", "@wordpress/nux": "file:packages/nux", "@wordpress/url": "file:packages/url", @@ -3725,6 +3726,17 @@ "lodash": "^4.17.11" } }, + "@wordpress/media-utils": { + "version": "file:packages/media-utils", + "requires": { + "@babel/runtime": "^7.4.4", + "@wordpress/api-fetch": "file:packages/api-fetch", + "@wordpress/blob": "file:packages/blob", + "@wordpress/element": "file:packages/element", + "@wordpress/i18n": "file:packages/i18n", + "lodash": "^4.17.11" + } + }, "@wordpress/notices": { "version": "file:packages/notices", "requires": { diff --git a/package.json b/package.json index 54a3034abafc94..7b5d744f8d347c 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/list-reusable-blocks": "file:packages/list-reusable-blocks", + "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", "@wordpress/nux": "file:packages/nux", "@wordpress/plugins": "file:packages/plugins", diff --git a/packages/edit-post/src/hooks/components/index.js b/packages/edit-post/src/hooks/components/index.js index 20f16a7461def2..709d89d013ed5a 100644 --- a/packages/edit-post/src/hooks/components/index.js +++ b/packages/edit-post/src/hooks/components/index.js @@ -2,16 +2,12 @@ * WordPress dependencies */ import { addFilter } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import MediaUpload from './media-upload'; +import { MediaUpload } from '@wordpress/media-utils'; const replaceMediaUpload = () => MediaUpload; addFilter( 'editor.MediaUpload', - 'core/edit-post/components/media-upload/replace-media-upload', + 'core/edit-post/replace-media-upload', replaceMediaUpload ); diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 4825a45cefc6e4..0397a77c02957f 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -1,6 +1,13 @@ +/** + * External dependencies + */ +import { defaultTo } from 'lodash'; + /** * WordPress dependencies */ +import { useMemo } from '@wordpress/element'; +import { uploadMedia } from '@wordpress/media-utils'; import { compose } from '@wordpress/compose'; import { Panel, PanelBody } from '@wordpress/components'; import { @@ -9,13 +16,35 @@ import { } from '@wordpress/block-editor'; import { withDispatch, withSelect } from '@wordpress/data'; +function getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ) { + if ( ! hasUploadPermissions ) { + return blockEditorSettings; + } + const mediaUploadBlockEditor = ( { onError, ...argumentsObject } ) => { + uploadMedia( { + wpAllowedMimeTypes: blockEditorSettings.allowedMimeTypes, + onError: ( { message } ) => onError( message ), + ...argumentsObject, + } ); + }; + return { + ...blockEditorSettings, + __experimentalMediaUpload: mediaUploadBlockEditor, + }; +} + function WidgetArea( { blockEditorSettings, blocks, initialOpen, updateBlocks, widgetAreaName, + hasUploadPermissions, } ) { + const settings = useMemo( + () => getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ), + [ blockEditorSettings, hasUploadPermissions ] + ); return ( <Panel className="edit-widgets-widget-area"> <PanelBody @@ -26,7 +55,7 @@ function WidgetArea( { value={ blocks } onInput={ updateBlocks } onChange={ updateBlocks } - settings={ blockEditorSettings } + settings={ settings } > <BlockList /> </BlockEditorProvider> @@ -41,11 +70,13 @@ export default compose( [ getBlocksFromWidgetArea, getWidgetArea, } = select( 'core/edit-widgets' ); + const { canUser } = select( 'core' ); const blocks = getBlocksFromWidgetArea( id ); const widgetAreaName = ( getWidgetArea( id ) || {} ).name; return { blocks, widgetAreaName, + hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), }; } ), withDispatch( ( dispatch, { id } ) => { diff --git a/packages/edit-widgets/src/hooks/components/index.js b/packages/edit-widgets/src/hooks/components/index.js new file mode 100644 index 00000000000000..4c24e365661fae --- /dev/null +++ b/packages/edit-widgets/src/hooks/components/index.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; +import { MediaUpload } from '@wordpress/media-utils'; + +const replaceMediaUpload = () => MediaUpload; + +addFilter( + 'editor.MediaUpload', + 'core/edit-widgets/replace-media-upload', + replaceMediaUpload +); diff --git a/packages/edit-widgets/src/hooks/index.js b/packages/edit-widgets/src/hooks/index.js new file mode 100644 index 00000000000000..c6cbc1d173e861 --- /dev/null +++ b/packages/edit-widgets/src/hooks/index.js @@ -0,0 +1,4 @@ +/** + * Internal dependencies + */ +import './components'; diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index c61d4c2f9bb781..4480c5b0a06ec0 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -7,6 +7,7 @@ import { registerCoreBlocks } from '@wordpress/block-library'; /** * Internal dependencies */ +import './hooks'; import './store'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; diff --git a/packages/editor/package.json b/packages/editor/package.json index 74384ff0ce3900..9578fdf41caf10 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -38,6 +38,7 @@ "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", "@wordpress/nux": "file:../nux", "@wordpress/url": "file:../url", diff --git a/packages/editor/src/utils/media-upload/index.js b/packages/editor/src/utils/media-upload/index.js index f6572e29c6b0c4..dc79320b703be8 100644 --- a/packages/editor/src/utils/media-upload/index.js +++ b/packages/editor/src/utils/media-upload/index.js @@ -7,11 +7,7 @@ import { noop } from 'lodash'; * WordPress dependencies */ import { select } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { mediaUpload } from './media-upload'; +import { uploadMedia } from '@wordpress/media-utils'; /** * Upload a media file when the file upload button is activated. @@ -37,7 +33,7 @@ export default function( { const wpAllowedMimeTypes = getEditorSettings().allowedMimeTypes; maxUploadFileSize = maxUploadFileSize || getEditorSettings().maxUploadFileSize; - mediaUpload( { + uploadMedia( { allowedTypes, filesList, onFileChange, diff --git a/packages/media-utils/.npmrc b/packages/media-utils/.npmrc new file mode 100644 index 00000000000000..43c97e719a5a82 --- /dev/null +++ b/packages/media-utils/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/media-utils/CHANGELOG.md b/packages/media-utils/CHANGELOG.md new file mode 100644 index 00000000000000..08666d6e2cf0c1 --- /dev/null +++ b/packages/media-utils/CHANGELOG.md @@ -0,0 +1,5 @@ +## 0.1.0 (2019-01-03) + +### New Features + +- Implemented first version of the package. diff --git a/packages/media-utils/README.md b/packages/media-utils/README.md new file mode 100644 index 00000000000000..489214c5446765 --- /dev/null +++ b/packages/media-utils/README.md @@ -0,0 +1,45 @@ +# Media Utils + +The media utils package provides a set of artifacts to abstract media functionality that may be useful in situations where there is a need to deal with media uploads or with the media library, e.g., artifacts that extend or implement a block-editor. +This package is meant to be used by the WordPress core. It may not work as expected outside WordPress usages. + +## Installation + +Install the module + +```bash +npm install @wordpress/media-utils --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +## Usage + +### uploadMedia + +Media upload util is a function that allows the invokers to upload files to the WordPress media library. +As an example, provided that `myFiles` is an array of file objects, `onFileChange` on onFileChange is a function that receives an array of objects containing the description of WordPress media items and `handleFileError` is a function that receives an object describing a possible error, the following code uploads a file to the WordPress media library: +```js +wp.mediaUtils.utils.uploadMedia( { + filesList: myFiles, + onFileChange: handleFileChange, + onError: handleFileError +} ); +``` + +The following code uploads a file named foo.txt with foo as content to the media library and alerts its URL: +```js +wp.mediaUtils.utils.uploadMedia( { + filesList: [ new File( ["foo"], "foo.txt", { type: "text/plain"} ) ], + onFileChange: ( [ fileObj] ) => alert( fileObj.url ), + onError: console.error, +} ); +``` + +Beware that first onFileChange is called with temporary blob URLs and then with the final URL's this allows to show the result in an optimistic UI as if the upload was already completed. E.g.: when uploading an image, one can show the image right away in the UI even before the upload is complete. + + +### MediaUpload + +Media upload component provides a UI button that allows users to open the WordPress media library. It is normally used in conjunction with the filter `editor.MediaUpload`. +The component follows the interface specified in https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/media-upload/README.md, and more details regarding its usage can be checked there. diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json new file mode 100644 index 00000000000000..b56795b5d65975 --- /dev/null +++ b/packages/media-utils/package.json @@ -0,0 +1,35 @@ +{ + "name": "@wordpress/media-utils", + "version": "0.1.0", + "description": "WordPress Media Upload Utils.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "media", + "upload", + "media-upload" + ], + "homepage": "https://github.com/WordPress/gutenberg/master/packages/media-utils/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/url" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/blob": "file:../blob", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "lodash": "^4.17.11" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/media-utils/src/components/index.js b/packages/media-utils/src/components/index.js new file mode 100644 index 00000000000000..82a89c0d58014b --- /dev/null +++ b/packages/media-utils/src/components/index.js @@ -0,0 +1 @@ +export { default as MediaUpload } from './media-upload'; diff --git a/packages/edit-post/src/hooks/components/media-upload/index.js b/packages/media-utils/src/components/media-upload/index.js similarity index 100% rename from packages/edit-post/src/hooks/components/media-upload/index.js rename to packages/media-utils/src/components/media-upload/index.js diff --git a/packages/media-utils/src/index.js b/packages/media-utils/src/index.js new file mode 100644 index 00000000000000..590a7f4c9d188d --- /dev/null +++ b/packages/media-utils/src/index.js @@ -0,0 +1,2 @@ +export * from './components'; +export * from './utils'; diff --git a/packages/media-utils/src/utils/index.js b/packages/media-utils/src/utils/index.js new file mode 100644 index 00000000000000..509b62f1e88648 --- /dev/null +++ b/packages/media-utils/src/utils/index.js @@ -0,0 +1 @@ +export { uploadMedia } from './upload-media'; diff --git a/packages/editor/src/utils/media-upload/test/media-upload.js b/packages/media-utils/src/utils/test/upload-media.test.js similarity index 95% rename from packages/editor/src/utils/media-upload/test/media-upload.js rename to packages/media-utils/src/utils/test/upload-media.test.js index d9e18cf7bf280b..857c139625c027 100644 --- a/packages/editor/src/utils/media-upload/test/media-upload.js +++ b/packages/media-utils/src/utils/test/upload-media.test.js @@ -7,7 +7,7 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { mediaUpload, getMimeTypesArray } from '../media-upload'; +import { uploadMedia, getMimeTypesArray } from '../upload-media'; jest.mock( '@wordpress/blob', () => ( { createBlobURL: jest.fn(), @@ -18,11 +18,11 @@ jest.mock( '@wordpress/api-fetch', () => jest.fn() ); const xmlFile = new window.File( [ 'fake_file' ], 'test.xml', { type: 'text/xml' } ); const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', { type: 'image/jpeg' } ); -describe( 'mediaUpload', () => { +describe( 'uploadMedia', () => { it( 'should do nothing on no files', async () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { filesList: [], onError, onFileChange, @@ -35,7 +35,7 @@ describe( 'mediaUpload', () => { it( 'should error if allowedTypes contains a partial mime type and the validation fails', async () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'image' ], filesList: [ xmlFile ], onError, @@ -51,7 +51,7 @@ describe( 'mediaUpload', () => { it( 'should error if allowedTypes contains a complete mime type and the validation fails', async () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'image/gif' ], filesList: [ imageFile ], onError, @@ -70,7 +70,7 @@ describe( 'mediaUpload', () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'image/jpeg' ], filesList: [ imageFile ], onError, @@ -84,7 +84,7 @@ describe( 'mediaUpload', () => { it( 'should error if allowedTypes contains multiple types and the validation fails', async () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'video', 'image' ], filesList: [ xmlFile ], onError, @@ -103,7 +103,7 @@ describe( 'mediaUpload', () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'video', 'image' ], filesList: [ imageFile ], onError, @@ -120,7 +120,7 @@ describe( 'mediaUpload', () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'image' ], filesList: [ imageFile, xmlFile ], onError, @@ -137,7 +137,7 @@ describe( 'mediaUpload', () => { it( 'should error if the file size is greater than the maximum', async () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'image' ], filesList: [ imageFile ], maxUploadFileSize: 1, @@ -154,7 +154,7 @@ describe( 'mediaUpload', () => { it( 'should call error handler with the correct error object if file type is not allowed for user', async () => { const onError = jest.fn(); const onFileChange = jest.fn(); - await mediaUpload( { + await uploadMedia( { allowedTypes: [ 'image' ], filesList: [ imageFile ], onError, diff --git a/packages/editor/src/utils/media-upload/media-upload.js b/packages/media-utils/src/utils/upload-media.js similarity index 99% rename from packages/editor/src/utils/media-upload/media-upload.js rename to packages/media-utils/src/utils/upload-media.js index 3aed464f5c4c6e..553584d010ace3 100644 --- a/packages/editor/src/utils/media-upload/media-upload.js +++ b/packages/media-utils/src/utils/upload-media.js @@ -61,7 +61,7 @@ export function getMimeTypesArray( wpMimeTypesObject ) { * @param {Function} $0.onFileChange Function called each time a file or a temporary representation of the file is available. * @param {?Object} $0.wpAllowedMimeTypes List of allowed mime types and file extensions. */ -export async function mediaUpload( { +export async function uploadMedia( { allowedTypes, additionalData = {}, filesList, From ffacdf1db6e1bfb1b2bfacc6fc0b8a7f1fc04d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Mon, 3 Jun 2019 12:09:27 +0100 Subject: [PATCH 245/664] Make sure onEnter is called on the right prop. (#15944) --- packages/editor/src/components/post-title/index.native.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 661c66a90a403a..0bb3591fa52ecc 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -100,7 +100,8 @@ class PostTitle extends Component { } } placeholder={ decodedPlaceholder } value={ title } - onSplit={ this.props.onEnterPress } + onSplit={ () => { } } + onReplace={ this.props.onEnterPress } disableEditingMenu={ true } setRef={ ( ref ) => { this.titleViewRef = ref; From bd76217166e0d75793ca20b63ad5d4c89119b321 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 3 Jun 2019 09:19:25 -0400 Subject: [PATCH 246/664] Modify reduce-motion mixin so that it works for transitions too. (#15850) * Add `transition` to the reduce motion mixin. * Add reduce motion mixin everywhere we're using transitions. * Modify the reduce-motion mixin to support transition and/or animation. * Specify whether to inclue animation or transition properties for the reduce-motion mixin instances. * Remove unnecessary !importants --- assets/stylesheets/_animations.scss | 2 +- assets/stylesheets/_mixins.scss | 24 ++++++++++++++++--- .../src/components/block-list/style.scss | 6 ++++- .../src/components/block-switcher/style.scss | 1 + .../src/components/block-toolbar/style.scss | 3 ++- .../default-block-appender/style.scss | 2 ++ .../components/inserter-list-item/style.scss | 3 +++ .../src/components/inserter/style.scss | 1 + .../src/components/url-input/style.scss | 1 + .../block-library/src/classic/editor.scss | 1 + packages/components/src/animate/style.scss | 4 ++-- packages/components/src/button/style.scss | 1 + .../components/src/color-palette/style.scss | 2 ++ .../components/src/color-picker/style.scss | 1 + packages/components/src/drop-zone/style.scss | 3 +++ .../components/src/form-toggle/style.scss | 2 ++ .../src/form-token-field/style.scss | 2 ++ packages/components/src/modal/style.scss | 2 +- packages/components/src/panel/style.scss | 3 +++ .../components/src/resizable-box/style.scss | 5 ++-- .../src/components/fullscreen-mode/style.scss | 2 +- .../src/components/layout/style.scss | 2 +- .../sidebar/settings-header/style.scss | 1 + .../src/components/sidebar/style.scss | 1 + .../components/post-featured-image/style.scss | 1 + .../src/components/post-title/style.scss | 2 ++ 26 files changed, 65 insertions(+), 13 deletions(-) diff --git a/assets/stylesheets/_animations.scss b/assets/stylesheets/_animations.scss index f856c0bf812d60..9466001700e036 100644 --- a/assets/stylesheets/_animations.scss +++ b/assets/stylesheets/_animations.scss @@ -5,5 +5,5 @@ @mixin edit-post__fade-in-animation($speed: 0.2s, $delay: 0s) { animation: edit-post__fade-in-animation $speed ease-out $delay; animation-fill-mode: forwards; - @include reduce-motion; + @include reduce-motion("animation"); } diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 8551c934d6edb9..d72c0b5b3ecbfc 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -176,6 +176,7 @@ transition: box-shadow 0.1s linear; border-radius: $radius-round-rectangle; border: $border-width solid $dark-gray-150; + @include reduce-motion("transition"); } @mixin input-style__focus() { @@ -337,10 +338,27 @@ * Allows users to opt-out of animations via OS-level preferences. */ -@mixin reduce-motion { - @media (prefers-reduced-motion: reduce) { - animation-duration: 1ms !important; +@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; + } + } + } /** diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 216fac5253cf3a..4aaf4e3f2c4490 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -109,8 +109,9 @@ border: $border-width solid transparent; border-left: none; box-shadow: none; - transition: border-color 0.1s linear, box-shadow 0.1s linear; pointer-events: none; + transition: border-color 0.1s linear, box-shadow 0.1s linear; + @include reduce-motion("transition"); // Include a transparent outline for Windows High Contrast mode. outline: $border-width solid transparent; @@ -160,6 +161,7 @@ &.is-focus-mode:not(.is-multi-selected) { opacity: 0.5; transition: opacity 0.1s linear; + @include reduce-motion("transition"); &:not(.is-focused) .block-editor-block-list__block, &.is-focused { @@ -752,6 +754,7 @@ // Hide both the button until hovered. opacity: 0; transition: opacity 0.1s linear; + @include reduce-motion("transition"); &:hover, &.is-visible { @@ -815,6 +818,7 @@ font-size: $text-editor-font-size; line-height: 150%; transition: padding 0.2s linear; + @include reduce-motion("transition"); &:focus { box-shadow: none; diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 9431b74fe1f31f..88b0e984fb7319 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -59,6 +59,7 @@ display: flex; align-items: center; transition: all 0.1s cubic-bezier(0.165, 0.84, 0.44, 1); + @include reduce-motion("transition"); } // Add a dropdown arrow indicator. diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index e0fbc6601ee369..3d741214e98799 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -4,8 +4,9 @@ width: 100%; overflow: auto; // Allow horizontal scrolling on mobile. position: relative; - transition: border-color 0.1s linear, box-shadow 0.1s linear; border-left: $border-width solid $light-gray-800; + transition: border-color 0.1s linear, box-shadow 0.1s linear; + @include reduce-motion("transition"); @include break-small() { // Allow overflow on desktop. diff --git a/packages/block-editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/style.scss index 565d12230d0be5..831b844627062b 100644 --- a/packages/block-editor/src/components/default-block-appender/style.scss +++ b/packages/block-editor/src/components/default-block-appender/style.scss @@ -12,6 +12,7 @@ width: 100%; outline: $border-width solid transparent; transition: 0.2s outline; + @include reduce-motion("transition"); resize: none; margin-top: $default-block-margin; margin-bottom: $default-block-margin; @@ -32,6 +33,7 @@ .block-editor-inserter__toggle:not([aria-expanded="true"]) { opacity: 0; transition: opacity 0.2s; + @include reduce-motion("transition"); will-change: opacity; } diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index 073c815dc2909e..d9051da5c839aa 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -20,6 +20,7 @@ border-radius: $radius-round-rectangle; border: $border-width solid transparent; transition: all 0.05s ease-in-out; + @include reduce-motion("transition"); position: relative; &:disabled { @@ -71,6 +72,7 @@ border-radius: $radius-round-rectangle; color: $dark-gray-500; transition: all 0.05s ease-in-out; + @include reduce-motion("transition"); .block-editor-block-icon { margin-left: auto; @@ -79,6 +81,7 @@ svg { transition: all 0.15s ease-out; + @include reduce-motion("transition"); } } diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 41f53d25e00e38..87b9a79f12fa40 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -32,6 +32,7 @@ $block-inserter-search-height: 38px; border: none; outline: none; transition: color 0.2s ease; + @include reduce-motion("transition"); } .block-editor-inserter__menu { diff --git a/packages/block-editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss index de6d71b9facea3..54c1128281b72b 100644 --- a/packages/block-editor/src/components/url-input/style.scss +++ b/packages/block-editor/src/components/url-input/style.scss @@ -43,6 +43,7 @@ $input-size: 300px; .block-editor-url-input__suggestions { max-height: 200px; transition: all 0.15s ease-in-out; + @include reduce-motion("transition"); padding: 4px 0; // To match the url-input width: input width + padding + 2 buttons. width: $input-size + 2; diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 4313096afe14d8..3d4965146dcf2e 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -242,6 +242,7 @@ div[data-type="core/freeform"] { .block-editor-block-list__block-edit::before { transition: border-color 0.1s linear, box-shadow 0.1s linear; + @include reduce-motion("transition"); border: $border-width solid $light-gray-500; // Windows High Contrast mode will show this outline. diff --git a/packages/components/src/animate/style.scss b/packages/components/src/animate/style.scss index 1fa4e95d0cf59a..814e1bc3701122 100644 --- a/packages/components/src/animate/style.scss +++ b/packages/components/src/animate/style.scss @@ -1,7 +1,7 @@ .components-animate__appear { animation: components-animate__appear-animation 0.1s cubic-bezier(0, 0, 0.2, 1) 0s; animation-fill-mode: forwards; - @include reduce-motion; + @include reduce-motion("animation"); &.is-from-top, &.is-from-top.is-from-left { @@ -31,7 +31,7 @@ .components-animate__slide-in { animation: components-animate__slide-in-animation 0.1s cubic-bezier(0, 0, 0.2, 1); animation-fill-mode: forwards; - @include reduce-motion; + @include reduce-motion("animation"); &.is-from-left { transform: translateX(+100%); diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 1d44f3bb9cfcb2..69776322ede4ee 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -144,6 +144,7 @@ transition-property: border, background, color; transition-duration: 0.05s; transition-timing-function: ease-in-out; + @include reduce-motion("transition"); &:hover, &:active { diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss index 030b3e220c375c..97f7502a53e5d9 100644 --- a/packages/components/src/color-palette/style.scss +++ b/packages/components/src/color-palette/style.scss @@ -21,6 +21,7 @@ $color-palette-circle-spacing: 14px; vertical-align: top; transform: scale(1); transition: 100ms transform ease; + @include reduce-motion("transition"); &:hover { transform: scale(1.2); } @@ -42,6 +43,7 @@ $color-palette-circle-spacing: 14px; background: transparent; box-shadow: inset 0 0 0 ($color-palette-circle-size / 2); transition: 100ms box-shadow ease; + @include reduce-motion("transition"); cursor: pointer; &.is-active { diff --git a/packages/components/src/color-picker/style.scss b/packages/components/src/color-picker/style.scss index 8abda3ac275896..576e3d25ff7683 100644 --- a/packages/components/src/color-picker/style.scss +++ b/packages/components/src/color-picker/style.scss @@ -167,6 +167,7 @@ .components-color-picker__hue-pointer, .components-color-picker__saturation-pointer { transition: box-shadow 0.1s linear; + @include reduce-motion("transition"); } .components-color-picker__saturation-pointer:focus { diff --git a/packages/components/src/drop-zone/style.scss b/packages/components/src/drop-zone/style.scss index 4b36a7fb9a2bc3..414918b3a44a54 100644 --- a/packages/components/src/drop-zone/style.scss +++ b/packages/components/src/drop-zone/style.scss @@ -8,6 +8,7 @@ visibility: hidden; opacity: 0; transition: 0.3s opacity, 0.3s background-color, 0s visibility 0.3s; + @include reduce-motion("transition"); border: 2px solid $blue-dark-900; border-radius: 2px; @@ -15,6 +16,7 @@ opacity: 1; visibility: visible; transition: 0.3s opacity, 0.3s background-color; + @include reduce-motion("transition"); } &.is-dragging-over-element { @@ -33,6 +35,7 @@ text-align: center; color: $white; transition: transform 0.2s ease-in-out; + @include reduce-motion("transition"); } .components-drop-zone.is-dragging-over-element .components-drop-zone__content { diff --git a/packages/components/src/form-toggle/style.scss b/packages/components/src/form-toggle/style.scss index 51280da0ac8da9..ebe43dc70441a6 100644 --- a/packages/components/src/form-toggle/style.scss +++ b/packages/components/src/form-toggle/style.scss @@ -36,6 +36,7 @@ $toggle-border-width: 2px; height: $toggle-height; border-radius: $toggle-height / 2; transition: 0.2s background ease; + @include reduce-motion("transition"); } .components-form-toggle__thumb { @@ -48,6 +49,7 @@ $toggle-border-width: 2px; height: $toggle-height - ($toggle-border-width * 4); border-radius: 50%; transition: 0.1s transform ease; + @include reduce-motion("transition"); background-color: $dark-gray-300; border: 5px solid $dark-gray-300; // Has explicit border to act as a fill in Windows High Contrast Mode. } diff --git a/packages/components/src/form-token-field/style.scss b/packages/components/src/form-token-field/style.scss index 5480cdef635409..35f9569f268b11 100644 --- a/packages/components/src/form-token-field/style.scss +++ b/packages/components/src/form-token-field/style.scss @@ -134,6 +134,7 @@ line-height: 24px; background: $light-gray-500; transition: all 0.2s cubic-bezier(0.4, 1, 0.4, 1); + @include reduce-motion; } .components-form-token-field__token-text { @@ -164,6 +165,7 @@ max-height: 9em; overflow-y: scroll; transition: all 0.15s ease-in-out; + @include reduce-motion("transition"); list-style: none; border-top: $border-width solid $dark-gray-300; margin: $grid-size-small (-$grid-size-small) (-$grid-size-small); diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index 5746f8047a03cd..d877fd2bf16dc4 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -42,7 +42,7 @@ // Animate the modal frame/contents appearing on the page. animation: components-modal__appear-animation 0.1s ease-out; animation-fill-mode: forwards; - @include reduce-motion; + @include reduce-motion("animation"); } } diff --git a/packages/components/src/panel/style.scss b/packages/components/src/panel/style.scss index 5b10bbdf8e358e..828af037a724f4 100644 --- a/packages/components/src/panel/style.scss +++ b/packages/components/src/panel/style.scss @@ -64,6 +64,7 @@ margin-top: 0; margin-bottom: 0; transition: 0.1s background ease-in-out; + @include reduce-motion("transition"); } .components-panel__body.is-opened > .components-panel__body-title { margin: -1 * $panel-padding; @@ -86,6 +87,7 @@ color: $dark-gray-900; @include menu-style__neutral; transition: 0.1s background ease-in-out; + @include reduce-motion("transition"); &:focus:not(:disabled):not([aria-disabled="true"]) { @include menu-style__focus; @@ -99,6 +101,7 @@ color: $dark-gray-900; fill: currentColor; transition: 0.1s color ease-in-out; + @include reduce-motion("transition"); } // mirror the arrow horizontally in RTL languages diff --git a/packages/components/src/resizable-box/style.scss b/packages/components/src/resizable-box/style.scss index f8f2e40cdbde6e..4297038291124a 100644 --- a/packages/components/src/resizable-box/style.scss +++ b/packages/components/src/resizable-box/style.scss @@ -39,6 +39,7 @@ top: calc(50% - 4px); right: calc(50% - 4px); transition: transform 0.1s ease-in; + @include reduce-motion("transition"); opacity: 0; } @@ -87,7 +88,7 @@ .components-resizable-box__side-handle.components-resizable-box__handle-bottom:active::before { animation: components-resizable-box__top-bottom-animation 0.1s ease-out 0s; animation-fill-mode: forwards; - @include reduce-motion; + @include reduce-motion("animation"); } .components-resizable-box__side-handle.components-resizable-box__handle-left:hover::before, @@ -96,7 +97,7 @@ .components-resizable-box__side-handle.components-resizable-box__handle-right:active::before { animation: components-resizable-box__left-right-animation 0.1s ease-out 0s; animation-fill-mode: forwards; - @include reduce-motion; + @include reduce-motion("animation"); } @keyframes components-resizable-box__top-bottom-animation { diff --git a/packages/edit-post/src/components/fullscreen-mode/style.scss b/packages/edit-post/src/components/fullscreen-mode/style.scss index 9d51282381eb0e..05ce2a356b351c 100644 --- a/packages/edit-post/src/components/fullscreen-mode/style.scss +++ b/packages/edit-post/src/components/fullscreen-mode/style.scss @@ -26,7 +26,7 @@ body.js.is-fullscreen-mode { .edit-post-header { transform: translateY(-100%); animation: edit-post-fullscreen-mode__slide-in-animation 0.1s forwards; - @include reduce-motion; + @include reduce-motion("animation"); } } } diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 4c6458afe2f051..66307b74c0101c 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -160,7 +160,7 @@ border-left: $border-width solid $light-gray-500; transform: translateX(+100%); animation: edit-post-post-publish-panel__slide-in-animation 0.1s forwards; - @include reduce-motion; + @include reduce-motion("animation"); body.is-fullscreen-mode & { top: 0; diff --git a/packages/edit-post/src/components/sidebar/settings-header/style.scss b/packages/edit-post/src/components/sidebar/settings-header/style.scss index 457f7e0361b21e..73d9431661c267 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/style.scss +++ b/packages/edit-post/src/components/sidebar/settings-header/style.scss @@ -26,6 +26,7 @@ color: $dark-gray-900; @include square-style__neutral; transition: box-shadow 0.1s linear; + @include reduce-motion("transition"); // This pseudo-element "duplicates" the tab label and sets the text to bold. // This ensures that the tab doesn't change width when selected. diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index 62936b0f0a7ff6..740355cf636f75 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -164,6 +164,7 @@ font-weight: 400; @include square-style__neutral; transition: box-shadow 0.1s linear; + @include reduce-motion("transition"); &.is-active { box-shadow: inset 0 -3px theme(outlines); diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss index ccdbafe2e34e35..71af23d863de3a 100644 --- a/packages/editor/src/components/post-featured-image/style.scss +++ b/packages/editor/src/components/post-featured-image/style.scss @@ -25,6 +25,7 @@ width: 100%; padding: 0; transition: all 0.1s ease-out; + @include reduce-motion("transition"); box-shadow: 0 0 0 0 $blue-medium-500; } diff --git a/packages/editor/src/components/post-title/style.scss b/packages/editor/src/components/post-title/style.scss index f0dc790b1fdd00..feff1a077f8964 100644 --- a/packages/editor/src/components/post-title/style.scss +++ b/packages/editor/src/components/post-title/style.scss @@ -17,6 +17,7 @@ line-height: $default-line-height; color: $dark-gray-900; transition: border 0.1s ease-out, box-shadow 0.1s linear; + @include reduce-motion("transition"); padding: #{ $block-padding + 5px } $block-padding; word-break: keep-all; @@ -94,6 +95,7 @@ &.is-focus-mode .editor-post-title__input { opacity: 0.5; transition: opacity 0.1s linear; + @include reduce-motion("transition"); &:focus { opacity: 1; From d98230259df7e485c38f297ad0ac42169ebc0393 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 3 Jun 2019 11:11:25 -0400 Subject: [PATCH 247/664] Framework: Update link to Docker Desktop (#15950) --- bin/install-docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/install-docker.sh b/bin/install-docker.sh index d6a10c498f0420..f485753b9cc4c6 100755 --- a/bin/install-docker.sh +++ b/bin/install-docker.sh @@ -10,7 +10,7 @@ set -e # Check that Docker is installed. if ! command_exists "docker"; then - echo -e $(error_message "Docker doesn't seem to be installed. Please head on over to the Docker site to download it: $(action_format "https://www.docker.com/community-edition#/download")") + echo -e $(error_message "Docker doesn't seem to be installed. Please head on over to the Docker site to download it: $(action_format "https://www.docker.com/products/docker-desktop")") exit 1 fi From 392bf02030ff4b25c478972911ca51ea21eef975 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 3 Jun 2019 16:28:30 +0100 Subject: [PATCH 248/664] Add media query hooks and animation to snackbar notices (#15908) --- bin/setup-travis-e2e-tests.sh | 3 +- package-lock.json | 10 ++++ packages/components/package.json | 1 + packages/components/src/snackbar/index.js | 9 ++-- packages/components/src/snackbar/list.js | 47 +++++++++++++++---- packages/components/src/snackbar/style.scss | 7 ++- packages/compose/CHANGELOG.md | 6 +++ packages/compose/README.md | 22 ++++++++- .../{ => higher-order}/if-condition/README.md | 0 .../{ => higher-order}/if-condition/index.js | 2 +- .../src/{ => higher-order}/pure/index.js | 2 +- .../src/{ => higher-order}/pure/test/index.js | 0 .../with-global-events/README.md | 0 .../with-global-events/index.js | 2 +- .../with-global-events/listener.js | 0 .../with-global-events/test/index.js | 0 .../with-global-events/test/listener.js | 0 .../with-instance-id/README.md | 0 .../with-instance-id/index.js | 2 +- .../with-instance-id/test/index.js | 0 .../with-safe-timeout/README.md | 0 .../with-safe-timeout/index.js | 2 +- .../{ => higher-order}/with-state/README.md | 0 .../{ => higher-order}/with-state/index.js | 2 +- .../with-state/test/index.js | 0 .../src/hooks/use-media-query/index.js | 25 ++++++++++ .../src/hooks/use-reduced-motion/index.js | 16 +++++++ packages/compose/src/index.js | 21 ++++++--- .../create-higher-order-component/index.js | 0 .../test/index.js | 0 webpack.config.js | 1 + 31 files changed, 151 insertions(+), 29 deletions(-) rename packages/compose/src/{ => higher-order}/if-condition/README.md (100%) rename packages/compose/src/{ => higher-order}/if-condition/index.js (86%) rename packages/compose/src/{ => higher-order}/pure/index.js (93%) rename packages/compose/src/{ => higher-order}/pure/test/index.js (100%) rename packages/compose/src/{ => higher-order}/with-global-events/README.md (100%) rename packages/compose/src/{ => higher-order}/with-global-events/index.js (96%) rename packages/compose/src/{ => higher-order}/with-global-events/listener.js (100%) rename packages/compose/src/{ => higher-order}/with-global-events/test/index.js (100%) rename packages/compose/src/{ => higher-order}/with-global-events/test/listener.js (100%) rename packages/compose/src/{ => higher-order}/with-instance-id/README.md (100%) rename packages/compose/src/{ => higher-order}/with-instance-id/index.js (88%) rename packages/compose/src/{ => higher-order}/with-instance-id/test/index.js (100%) rename packages/compose/src/{ => higher-order}/with-safe-timeout/README.md (100%) rename packages/compose/src/{ => higher-order}/with-safe-timeout/index.js (94%) rename packages/compose/src/{ => higher-order}/with-state/README.md (100%) rename packages/compose/src/{ => higher-order}/with-state/index.js (90%) rename packages/compose/src/{ => higher-order}/with-state/test/index.js (100%) create mode 100644 packages/compose/src/hooks/use-media-query/index.js create mode 100644 packages/compose/src/hooks/use-reduced-motion/index.js rename packages/compose/src/{ => utils}/create-higher-order-component/index.js (100%) rename packages/compose/src/{ => utils}/create-higher-order-component/test/index.js (100%) diff --git a/bin/setup-travis-e2e-tests.sh b/bin/setup-travis-e2e-tests.sh index 0ebc8bc1e6dff3..0594f4724b2e58 100755 --- a/bin/setup-travis-e2e-tests.sh +++ b/bin/setup-travis-e2e-tests.sh @@ -5,7 +5,8 @@ set -e npm ci -npm run build +# Force reduced motion in e2e tests +FORCE_REDUCED_MOTION=true npm run build # Set up environment variables . "$(dirname "$0")/bootstrap-env.sh" diff --git a/package-lock.json b/package-lock.json index accdd4ccea7fc3..f2ec975d616b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3398,6 +3398,7 @@ "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" @@ -18521,6 +18522,15 @@ "prop-types": "^15.5.8" } }, + "react-spring": { + "version": "8.0.20", + "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.20.tgz", + "integrity": "sha512-40ZUQ5uI5YHsoQWLPchWNcEUh6zQ6qvcVDeTI2vW10ldoCN3PvDsII9wBH2xEbMl+BQvYmHzGdfLTQxPxJWGnQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "prop-types": "^15.5.8" + } + }, "react-test-renderer": { "version": "16.8.4", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.8.4.tgz", diff --git a/packages/components/package.json b/packages/components/package.json index 1dafbbe3e018df..d2aa5957d36a83 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -44,6 +44,7 @@ "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" diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js index c5d5674ff408da..66dd09afa48791 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useEffect } from '@wordpress/element'; +import { useEffect, forwardRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** @@ -22,12 +22,12 @@ function Snackbar( { children, actions = [], onRemove = noop, -} ) { +}, ref ) { useEffect( () => { // This rule doesn't account yet for React Hooks // eslint-disable-next-line @wordpress/react-no-unsafe-timeout const timeoutHandle = setTimeout( () => { - onRemove(); + // onRemove(); }, NOTICE_TIMEOUT ); return () => clearTimeout( timeoutHandle ); @@ -37,6 +37,7 @@ function Snackbar( { return ( <div + ref={ ref } className={ classes } onClick={ onRemove } tabIndex="0" @@ -83,4 +84,4 @@ function Snackbar( { ); } -export default Snackbar; +export default forwardRef( Snackbar ); diff --git a/packages/components/src/snackbar/list.js b/packages/components/src/snackbar/list.js index c7cff210a138a3..b44aa8d24c93a3 100644 --- a/packages/components/src/snackbar/list.js +++ b/packages/components/src/snackbar/list.js @@ -3,6 +3,13 @@ */ import classnames from 'classnames'; import { omit, noop } from 'lodash'; +import { useTransition, animated } from 'react-spring'; + +/** + * WordPress dependencies + */ +import { useReducedMotion } from '@wordpress/compose'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -20,20 +27,42 @@ import Snackbar from './'; * @return {Object} The rendered notices list. */ function SnackbarList( { notices, className, children, onRemove = noop } ) { + const isReducedMotion = useReducedMotion(); + const [ refMap ] = useState( () => new WeakMap() ); + const transitions = useTransition( + notices, + ( notice ) => notice.id, + { + from: { opacity: 0, height: 0 }, + enter: ( item ) => async ( next ) => await next( { opacity: 1, height: refMap.get( item ).offsetHeight } ), + leave: () => async ( next ) => { + await next( { opacity: 0 } ); + await next( { height: 0 } ); + }, + immediate: isReducedMotion, + } + ); + className = classnames( 'components-snackbar-list', className ); - const removeNotice = ( id ) => () => onRemove( id ); + const removeNotice = ( notice ) => () => onRemove( notice.id ); return ( <div className={ className }> { children } - { notices.map( ( notice ) => ( - <Snackbar - { ...omit( notice, [ 'content' ] ) } - key={ notice.id } - onRemove={ removeNotice( notice.id ) } - > - { notice.content } - </Snackbar> + { transitions.map( ( { item: notice, key, props: style } ) => ( + <animated.div key={ key } style={ style }> + <div + className="components-snackbar-list__notice-container" + ref={ ( ref ) => ref && refMap.set( notice, ref ) } + > + <Snackbar + { ...omit( notice, [ 'content' ] ) } + onRemove={ removeNotice( notice ) } + > + { notice.content } + </Snackbar> + </div> + </animated.div> ) ) } </div> ); diff --git a/packages/components/src/snackbar/style.scss b/packages/components/src/snackbar/style.scss index 40e8f75837ae6b..4357685ffa0cbd 100644 --- a/packages/components/src/snackbar/style.scss +++ b/packages/components/src/snackbar/style.scss @@ -8,7 +8,7 @@ padding: 16px 24px; width: 100%; max-width: 600px; - margin: 8px 0 0; + cursor: pointer; @include break-small() { width: fit-content; @@ -55,3 +55,8 @@ z-index: z-index(".components-snackbar-list"); width: 100%; } + +.components-snackbar-list__notice-container { + position: relative; + padding-top: 8px; +} diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 0914387d78744d..30e39e3a50596c 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Features + +- Add the `useMediaQuery` and `useReducedMotion` hooks. + ## 3.0.0 (2018-11-15) ### Breaking Changes diff --git a/packages/compose/README.md b/packages/compose/README.md index b7db41522461c6..dc47f26e995652 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -1,6 +1,6 @@ # Compose -The `compose` package is a collection of handy [Higher Order Components](https://facebook.github.io/react/docs/higher-order-components.html) (HOCs) you can use to wrap your WordPress components and provide some basic features like: state, instance id, pure... +The `compose` package is a collection of handy [Hooks](https://reactjs.org/docs/hooks-intro.html) and [Higher Order Components](https://facebook.github.io/react/docs/higher-order-components.html) (HOCs) you can use to wrap your WordPress components and provide some basic features like: state, instance id, pure... The `compose` function is an alias to [flowRight](https://lodash.com/docs/#flowRight) from Lodash. It comes from functional programming, and allows you to compose any number of functions. You might also think of this as layering functions; `compose` will execute the last function first, then sequentially move back through the previous functions passing the result of each function upward. @@ -119,6 +119,26 @@ _Returns_ - `WPComponent`: Component class with generated display name assigned. +<a name="useMediaQuery" href="#useMediaQuery">#</a> **useMediaQuery** + +Runs a media query and returns its value when it changes. + +_Parameters_ + +- _query_ `string`: Media Query. + +_Returns_ + +- `boolean`: return value of the media query. + +<a name="useReducedMotion" href="#useReducedMotion">#</a> **useReducedMotion** + +Hook returning whether the user has a preference for reduced motion. + +_Returns_ + +- `boolean`: Reduced motion preference value. + <a name="withGlobalEvents" href="#withGlobalEvents">#</a> **withGlobalEvents** Higher-order component creator which, given an object of DOM event types and diff --git a/packages/compose/src/if-condition/README.md b/packages/compose/src/higher-order/if-condition/README.md similarity index 100% rename from packages/compose/src/if-condition/README.md rename to packages/compose/src/higher-order/if-condition/README.md diff --git a/packages/compose/src/if-condition/index.js b/packages/compose/src/higher-order/if-condition/index.js similarity index 86% rename from packages/compose/src/if-condition/index.js rename to packages/compose/src/higher-order/if-condition/index.js index afc45c8e5be2f1..79d23b01ba4218 100644 --- a/packages/compose/src/if-condition/index.js +++ b/packages/compose/src/higher-order/if-condition/index.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import createHigherOrderComponent from '../create-higher-order-component'; +import createHigherOrderComponent from '../../utils/create-higher-order-component'; /** * Higher-order component creator, creating a new component which renders if diff --git a/packages/compose/src/pure/index.js b/packages/compose/src/higher-order/pure/index.js similarity index 93% rename from packages/compose/src/pure/index.js rename to packages/compose/src/higher-order/pure/index.js index 16d0b19e9da705..e721fbb7964796 100644 --- a/packages/compose/src/pure/index.js +++ b/packages/compose/src/higher-order/pure/index.js @@ -7,7 +7,7 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import createHigherOrderComponent from '../create-higher-order-component'; +import createHigherOrderComponent from '../../utils/create-higher-order-component'; /** * Given a component returns the enhanced component augmented with a component diff --git a/packages/compose/src/pure/test/index.js b/packages/compose/src/higher-order/pure/test/index.js similarity index 100% rename from packages/compose/src/pure/test/index.js rename to packages/compose/src/higher-order/pure/test/index.js diff --git a/packages/compose/src/with-global-events/README.md b/packages/compose/src/higher-order/with-global-events/README.md similarity index 100% rename from packages/compose/src/with-global-events/README.md rename to packages/compose/src/higher-order/with-global-events/README.md diff --git a/packages/compose/src/with-global-events/index.js b/packages/compose/src/higher-order/with-global-events/index.js similarity index 96% rename from packages/compose/src/with-global-events/index.js rename to packages/compose/src/higher-order/with-global-events/index.js index bf32f27743232d..ac8aabd4639f6a 100644 --- a/packages/compose/src/with-global-events/index.js +++ b/packages/compose/src/higher-order/with-global-events/index.js @@ -11,7 +11,7 @@ import { Component, forwardRef } from '@wordpress/element'; /** * Internal dependencies */ -import createHigherOrderComponent from '../create-higher-order-component'; +import createHigherOrderComponent from '../../utils/create-higher-order-component'; import Listener from './listener'; /** diff --git a/packages/compose/src/with-global-events/listener.js b/packages/compose/src/higher-order/with-global-events/listener.js similarity index 100% rename from packages/compose/src/with-global-events/listener.js rename to packages/compose/src/higher-order/with-global-events/listener.js diff --git a/packages/compose/src/with-global-events/test/index.js b/packages/compose/src/higher-order/with-global-events/test/index.js similarity index 100% rename from packages/compose/src/with-global-events/test/index.js rename to packages/compose/src/higher-order/with-global-events/test/index.js diff --git a/packages/compose/src/with-global-events/test/listener.js b/packages/compose/src/higher-order/with-global-events/test/listener.js similarity index 100% rename from packages/compose/src/with-global-events/test/listener.js rename to packages/compose/src/higher-order/with-global-events/test/listener.js diff --git a/packages/compose/src/with-instance-id/README.md b/packages/compose/src/higher-order/with-instance-id/README.md similarity index 100% rename from packages/compose/src/with-instance-id/README.md rename to packages/compose/src/higher-order/with-instance-id/README.md diff --git a/packages/compose/src/with-instance-id/index.js b/packages/compose/src/higher-order/with-instance-id/index.js similarity index 88% rename from packages/compose/src/with-instance-id/index.js rename to packages/compose/src/higher-order/with-instance-id/index.js index 8bc363ddacba4d..53c09ec56f8882 100644 --- a/packages/compose/src/with-instance-id/index.js +++ b/packages/compose/src/higher-order/with-instance-id/index.js @@ -6,7 +6,7 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import createHigherOrderComponent from '../create-higher-order-component'; +import createHigherOrderComponent from '../../utils/create-higher-order-component'; /** * A Higher Order Component used to be provide a unique instance ID by diff --git a/packages/compose/src/with-instance-id/test/index.js b/packages/compose/src/higher-order/with-instance-id/test/index.js similarity index 100% rename from packages/compose/src/with-instance-id/test/index.js rename to packages/compose/src/higher-order/with-instance-id/test/index.js diff --git a/packages/compose/src/with-safe-timeout/README.md b/packages/compose/src/higher-order/with-safe-timeout/README.md similarity index 100% rename from packages/compose/src/with-safe-timeout/README.md rename to packages/compose/src/higher-order/with-safe-timeout/README.md diff --git a/packages/compose/src/with-safe-timeout/index.js b/packages/compose/src/higher-order/with-safe-timeout/index.js similarity index 94% rename from packages/compose/src/with-safe-timeout/index.js rename to packages/compose/src/higher-order/with-safe-timeout/index.js index 5fff43e7bb5dd9..910dd94fddc19d 100644 --- a/packages/compose/src/with-safe-timeout/index.js +++ b/packages/compose/src/higher-order/with-safe-timeout/index.js @@ -11,7 +11,7 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import createHigherOrderComponent from '../create-higher-order-component'; +import createHigherOrderComponent from '../../utils/create-higher-order-component'; /** * A higher-order component used to provide and manage delayed function calls diff --git a/packages/compose/src/with-state/README.md b/packages/compose/src/higher-order/with-state/README.md similarity index 100% rename from packages/compose/src/with-state/README.md rename to packages/compose/src/higher-order/with-state/README.md diff --git a/packages/compose/src/with-state/index.js b/packages/compose/src/higher-order/with-state/index.js similarity index 90% rename from packages/compose/src/with-state/index.js rename to packages/compose/src/higher-order/with-state/index.js index e1957482d8c8ee..19e639043bd28b 100644 --- a/packages/compose/src/with-state/index.js +++ b/packages/compose/src/higher-order/with-state/index.js @@ -6,7 +6,7 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import createHigherOrderComponent from '../create-higher-order-component'; +import createHigherOrderComponent from '../../utils/create-higher-order-component'; /** * A Higher Order Component used to provide and manage internal component state diff --git a/packages/compose/src/with-state/test/index.js b/packages/compose/src/higher-order/with-state/test/index.js similarity index 100% rename from packages/compose/src/with-state/test/index.js rename to packages/compose/src/higher-order/with-state/test/index.js diff --git a/packages/compose/src/hooks/use-media-query/index.js b/packages/compose/src/hooks/use-media-query/index.js new file mode 100644 index 00000000000000..2571da9e23deae --- /dev/null +++ b/packages/compose/src/hooks/use-media-query/index.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; + +/** + * Runs a media query and returns its value when it changes. + * + * @param {string} query Media Query. + * @return {boolean} return value of the media query. + */ +export default function useMediaQuery( query ) { + const [ match, setMatch ] = useState( false ); + useEffect( () => { + const updateMatch = () => setMatch( window.matchMedia( query ).matches ); + updateMatch(); + const list = window.matchMedia( query ); + list.addListener( updateMatch ); + return () => { + list.removeListener( updateMatch ); + }; + }, [ query ] ); + + return match; +} diff --git a/packages/compose/src/hooks/use-reduced-motion/index.js b/packages/compose/src/hooks/use-reduced-motion/index.js new file mode 100644 index 00000000000000..7b1e22792d37c5 --- /dev/null +++ b/packages/compose/src/hooks/use-reduced-motion/index.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import useMediaQuery from '../use-media-query'; + +/** + * Hook returning whether the user has a preference for reduced motion. + * + * @return {boolean} Reduced motion preference value. + */ +const useReducedMotion = + process.env.FORCE_REDUCED_MOTION ? + () => true : + () => useMediaQuery( '(prefers-reduced-motion: reduce)' ); + +export default useReducedMotion; diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 379ed6a4ab54de..9ea31b2d351aba 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -3,13 +3,8 @@ */ import { flowRight } from 'lodash'; -export { default as createHigherOrderComponent } from './create-higher-order-component'; -export { default as ifCondition } from './if-condition'; -export { default as pure } from './pure'; -export { default as withGlobalEvents } from './with-global-events'; -export { default as withInstanceId } from './with-instance-id'; -export { default as withSafeTimeout } from './with-safe-timeout'; -export { default as withState } from './with-state'; +// Utils +export { default as createHigherOrderComponent } from './utils/create-higher-order-component'; /** * Composes multiple higher-order components into a single higher-order component. Performs right-to-left function @@ -20,3 +15,15 @@ export { default as withState } from './with-state'; * @return {Function} Returns the new composite function. */ export { flowRight as compose }; + +// Higher-order components +export { default as ifCondition } from './higher-order/if-condition'; +export { default as pure } from './higher-order/pure'; +export { default as withGlobalEvents } from './higher-order/with-global-events'; +export { default as withInstanceId } from './higher-order/with-instance-id'; +export { default as withSafeTimeout } from './higher-order/with-safe-timeout'; +export { default as withState } from './higher-order/with-state'; + +// Hooks +export { default as useMediaQuery } from './hooks/use-media-query'; +export { default as useReducedMotion } from './hooks/use-reduced-motion'; diff --git a/packages/compose/src/create-higher-order-component/index.js b/packages/compose/src/utils/create-higher-order-component/index.js similarity index 100% rename from packages/compose/src/create-higher-order-component/index.js rename to packages/compose/src/utils/create-higher-order-component/index.js diff --git a/packages/compose/src/create-higher-order-component/test/index.js b/packages/compose/src/utils/create-higher-order-component/test/index.js similarity index 100% rename from packages/compose/src/create-higher-order-component/test/index.js rename to packages/compose/src/utils/create-higher-order-component/test/index.js diff --git a/webpack.config.js b/webpack.config.js index 3de56a819e0de8..0c0748fb684062 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -58,6 +58,7 @@ module.exports = { // Inject the `GUTENBERG_PHASE` global, used for feature flagging. // eslint-disable-next-line @wordpress/gutenberg-phase 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), + 'process.env.FORCE_REDUCED_MOTION': JSON.stringify( process.env.FORCE_REDUCED_MOTION ), } ), new CustomTemplatedPathPlugin( { basename( path, data ) { From f7cf606c133434a2b105a6cf457b83208f5ab6fc Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 3 Jun 2019 22:53:54 +0100 Subject: [PATCH 249/664] Revert inadvertently commented snackbar timeout --- packages/components/src/snackbar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js index 66dd09afa48791..afca0dd162d8df 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.js @@ -27,7 +27,7 @@ function Snackbar( { // This rule doesn't account yet for React Hooks // eslint-disable-next-line @wordpress/react-no-unsafe-timeout const timeoutHandle = setTimeout( () => { - // onRemove(); + onRemove(); }, NOTICE_TIMEOUT ); return () => clearTimeout( timeoutHandle ); From 6d3269ea055f8219d6d792f914a8f9580a6e6b87 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Tue, 4 Jun 2019 16:27:55 +1000 Subject: [PATCH 250/664] Adds check for attributes in block settings (#15959) * Adds check for attributes in block settings * Adds unit test * Addresses PR feedback --- packages/block-editor/src/hooks/anchor.js | 6 +++++- packages/block-editor/src/hooks/test/anchor.js | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 2b9e7adfd9dd1c..3f21ed3ef3b0a2 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { assign } from 'lodash'; +import { assign, has } from 'lodash'; /** * WordPress dependencies @@ -33,6 +33,10 @@ const ANCHOR_REGEX = /[\s#]/g; * @return {Object} Filtered block settings. */ export function addAttribute( settings ) { + // allow blocks to specify their own attribute definition with default values if needed. + if ( has( settings.attributes, [ 'anchor', 'type' ] ) ) { + return settings; + } if ( hasBlockSupport( settings, 'anchor' ) ) { // Use Lodash's assign to gracefully handle if attributes are undefined settings.attributes = assign( settings.attributes, { diff --git a/packages/block-editor/src/hooks/test/anchor.js b/packages/block-editor/src/hooks/test/anchor.js index 21a5c65be83cb0..d55a54fff332e2 100644 --- a/packages/block-editor/src/hooks/test/anchor.js +++ b/packages/block-editor/src/hooks/test/anchor.js @@ -39,6 +39,23 @@ describe( 'anchor', () => { expect( settings.attributes ).toHaveProperty( 'anchor' ); } ); + + it( 'should not override attributes defined in settings', () => { + const settings = registerBlockType( { + ...blockSettings, + supports: { + anchor: true, + }, + attributes: { + anchor: { + type: 'string', + default: 'testAnchor', + }, + }, + } ); + + expect( settings.attributes.anchor ).toEqual( { type: 'string', default: 'testAnchor' } ); + } ); } ); describe( 'addSaveProps', () => { From f8e2bcc9c8710808710bd1c9fe1d425ddde7af0d Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Tue, 4 Jun 2019 17:19:32 +0800 Subject: [PATCH 251/664] Fix spacer deselection after dragging (#15884) * Fix spacer deselection after dragging * Add e2e tests * Move drag utility to e2e-test-utils package * Update docs * Fix typo * Update docs Co-Authored-By: Robert Anderson <robert@noisysocks.com> --- packages/block-library/src/spacer/edit.js | 6 +-- packages/e2e-test-utils/README.md | 15 ++++++++ .../e2e-test-utils/src/drag-and-resize.js | 26 +++++++++++++ packages/e2e-test-utils/src/index.js | 1 + .../blocks/__snapshots__/spacer.test.js.snap | 13 +++++++ .../e2e-tests/specs/blocks/spacer.test.js | 38 +++++++++++++++++++ 6 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 packages/e2e-test-utils/src/drag-and-resize.js create mode 100644 packages/e2e-tests/specs/blocks/__snapshots__/spacer.test.js.snap create mode 100644 packages/e2e-tests/specs/blocks/spacer.test.js diff --git a/packages/block-library/src/spacer/edit.js b/packages/block-library/src/spacer/edit.js index fdb1abcf9d2395..f6a3091eb57e26 100644 --- a/packages/block-library/src/spacer/edit.js +++ b/packages/block-library/src/spacer/edit.js @@ -12,7 +12,7 @@ import { InspectorControls } from '@wordpress/block-editor'; import { BaseControl, PanelBody, ResizableBox } from '@wordpress/components'; import { withInstanceId } from '@wordpress/compose'; -const SpacerEdit = ( { attributes, isSelected, setAttributes, toggleSelection, instanceId } ) => { +const SpacerEdit = ( { attributes, isSelected, setAttributes, instanceId } ) => { const { height } = attributes; const id = `block-spacer-height-input-${ instanceId }`; const [ inputHeightValue, setInputHeightValue ] = useState( height ); @@ -44,10 +44,6 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, toggleSelection, i height: spacerHeight, } ); setInputHeightValue( spacerHeight ); - toggleSelection( true ); - } } - onResizeStart={ () => { - toggleSelection( false ); } } /> <InspectorControls> diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 7cb439d46fc4eb..2a3954c47e4b9b 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -141,6 +141,21 @@ _Parameters_ Disables Pre-publish checks. +<a name="dragAndResize" href="#dragAndResize">#</a> **dragAndResize** + +Clicks an element, drags a particular distance and releases the mouse button. + +_Parameters_ + +- _element_ `Object`: The puppeteer element handle. +- _delta_ `Object`: Object containing movement distances. +- _delta.x_ `number`: Horizontal distance to drag. +- _delta.y_ `number`: Vertical distance to drag. + +_Returns_ + +- `Promise`: Promise resolving when drag completes. + <a name="enablePageDialogAccept" href="#enablePageDialogAccept">#</a> **enablePageDialogAccept** Enables even listener which accepts a page dialog which diff --git a/packages/e2e-test-utils/src/drag-and-resize.js b/packages/e2e-test-utils/src/drag-and-resize.js new file mode 100644 index 00000000000000..f8f0b3e996b261 --- /dev/null +++ b/packages/e2e-test-utils/src/drag-and-resize.js @@ -0,0 +1,26 @@ +/** + * Clicks an element, drags a particular distance and releases the mouse button. + * + * @param {Object} element The puppeteer element handle. + * @param {Object} delta Object containing movement distances. + * @param {number} delta.x Horizontal distance to drag. + * @param {number} delta.y Vertical distance to drag. + * + * @return {Promise} Promise resolving when drag completes. + */ +export async function dragAndResize( element, delta ) { + const { + x: elementX, + y: elementY, + width: elementWidth, + height: elementHeight, + } = await element.boundingBox(); + + const originX = elementX + ( elementWidth / 2 ); + const originY = elementY + ( elementHeight / 2 ); + + await page.mouse.move( originX, originY ); + await page.mouse.down(); + await page.mouse.move( originX + delta.x, originY + delta.y ); + await page.mouse.up(); +} diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index e984658a99f6df..64e0a22a585b1e 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -10,6 +10,7 @@ export { createNewPost } from './create-new-post'; export { createURL } from './create-url'; export { deactivatePlugin } from './deactivate-plugin'; export { disablePrePublishChecks } from './disable-pre-publish-checks'; +export { dragAndResize } from './drag-and-resize'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; export { enablePrePublishChecks } from './enable-pre-publish-checks'; export { ensureSidebarOpened } from './ensure-sidebar-opened'; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/spacer.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/spacer.test.js.snap new file mode 100644 index 00000000000000..cfd7c9149d512e --- /dev/null +++ b/packages/e2e-tests/specs/blocks/__snapshots__/spacer.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Spacer can be created by typing "/spacer" 1`] = ` +"<!-- wp:spacer --> +<div style=\\"height:100px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div> +<!-- /wp:spacer -->" +`; + +exports[`Spacer can be resized using the drag handle and remains selected after being dragged 1`] = ` +"<!-- wp:spacer {\\"height\\":150} --> +<div style=\\"height:150px\\" aria-hidden=\\"true\\" class=\\"wp-block-spacer\\"></div> +<!-- /wp:spacer -->" +`; diff --git a/packages/e2e-tests/specs/blocks/spacer.test.js b/packages/e2e-tests/specs/blocks/spacer.test.js new file mode 100644 index 00000000000000..bd02e7a8505599 --- /dev/null +++ b/packages/e2e-tests/specs/blocks/spacer.test.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { + clickBlockAppender, + getEditedPostContent, + createNewPost, + dragAndResize, +} from '@wordpress/e2e-test-utils'; + +describe( 'Spacer', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'can be created by typing "/spacer"', async () => { + // Create a spacer with the slash block shortcut. + await clickBlockAppender(); + await page.keyboard.type( '/spacer' ); + await page.keyboard.press( 'Enter' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'can be resized using the drag handle and remains selected after being dragged', async () => { + // Create a spacer with the slash block shortcut. + await clickBlockAppender(); + await page.keyboard.type( '/spacer' ); + await page.keyboard.press( 'Enter' ); + + const resizableHandle = await page.$( '.block-library-spacer__resize-container .components-resizable-box__handle' ); + await dragAndResize( resizableHandle, { x: 0, y: 50 } ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + + const selectedSpacer = await page.$( '[data-type="core/spacer"].is-selected' ); + expect( selectedSpacer ).not.toBe( null ); + } ); +} ); From 1a293b3ccf242b3a5793bbd6c291aedd08a3d3c3 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Tue, 4 Jun 2019 14:39:16 +0200 Subject: [PATCH 252/664] [RNMobile] Moving native tests to gutenberg repo (#15589) * Moving paragraph native test to gutenberg repo * Rename native tests folder from __native_tests__ to test To be able to use the same structure already in place. * Ignore .native.js files on unit tests * Move native code block test to gutenberg repo. * Moved native link modal tests to gutenberg repo * Moved mobile media progress tests to gutenberg repo. * Moved mobile media upload tests to gutenberg repo * Renamed mobile paragraph block tests to be the same as the file it tests * [Mobile] Add unit tests for willTrimSpaces function in RichText (#15552) * Add unit test for willTrimSpaces. * Fix willTripSpaces to report false for no outer spaces on styled text. * Ignore gutenberg-mobile unit tests in gutenberg test pipeline * Revert "Fix willTripSpaces to report false for no outer spaces on styled text." This reverts commit 0731689a0cff89b1896454d9d7349ec8b14c454b. Upstream changes make this implementation no longer necessary. * Fix lint issues * Import RichText without native extension * Remove stubs from rich-text __mocks__ * Update native paragraph test * Update mobile link tests --- .../media-upload/test/index.native.js | 115 +++++++++++++ .../components/rich-text/test/index.native.js | 27 +++ .../src/code/test/edit.native.js | 26 +++ .../test/media-upload-progress.native.js | 162 ++++++++++++++++++ .../src/paragraph/test/edit.native.js | 32 ++++ .../src/link/test/modal.native.js | 66 +++++++ test/unit/jest.config.js | 1 + 7 files changed, 429 insertions(+) create mode 100644 packages/block-editor/src/components/media-upload/test/index.native.js create mode 100644 packages/block-editor/src/components/rich-text/test/index.native.js create mode 100644 packages/block-library/src/code/test/edit.native.js create mode 100644 packages/block-library/src/image/test/media-upload-progress.native.js create mode 100644 packages/block-library/src/paragraph/test/edit.native.js create mode 100644 packages/format-library/src/link/test/modal.native.js diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js new file mode 100644 index 00000000000000..76acd6132f4dbb --- /dev/null +++ b/packages/block-editor/src/components/media-upload/test/index.native.js @@ -0,0 +1,115 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; +import { TouchableWithoutFeedback } from 'react-native'; +import { + requestMediaPickFromMediaLibrary, + requestMediaPickFromDeviceLibrary, + requestMediaPickFromDeviceCamera, +} from 'react-native-gutenberg-bridge'; + +/** + * Internal dependencies + */ +import { + MediaUpload, + MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, + MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, + MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, + MEDIA_TYPE_IMAGE, + MEDIA_TYPE_VIDEO, + OPTION_TAKE_VIDEO, + OPTION_TAKE_PHOTO, +} from '../index'; + +jest.mock( 'react-native-gutenberg-bridge', () => ( + { + requestMediaPickFromMediaLibrary: jest.fn(), + requestMediaPickFromDeviceLibrary: jest.fn(), + requestMediaPickFromDeviceCamera: jest.fn(), + } +) ); + +const MEDIA_URL = 'http://host.media.type'; +const MEDIA_ID = 123; + +describe( 'MediaUpload component', () => { + it( 'renders without crashing', () => { + const wrapper = shallow( + <MediaUpload render={ () => {} } /> + ); + expect( wrapper ).toBeTruthy(); + } ); + + it( 'opens media options picker', () => { + const wrapper = shallow( + <MediaUpload render={ ( { open, getMediaOptions } ) => { + return ( + <TouchableWithoutFeedback onPress={ open }> + { getMediaOptions() } + </TouchableWithoutFeedback> + ); + } } /> + ); + expect( wrapper.find( 'Picker' ).length ).toEqual( 1 ); + } ); + + it( 'shows right media capture option for media type', () => { + const expectOptionForMediaType = ( mediaType, expectedOption ) => { + const wrapper = shallow( + <MediaUpload + mediaType={ mediaType } + render={ ( { open, getMediaOptions } ) => { + return ( + <TouchableWithoutFeedback onPress={ open }> + { getMediaOptions() } + </TouchableWithoutFeedback> + ); + } } /> + ); + expect( wrapper.find( 'Picker' ).props().options.filter( ( item ) => item.label === expectedOption ).length ).toEqual( 1 ); + }; + expectOptionForMediaType( MEDIA_TYPE_IMAGE, OPTION_TAKE_PHOTO ); + expectOptionForMediaType( MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO ); + } ); + + const expectMediaPickerForOption = ( option, requestFunction ) => { + requestFunction.mockImplementation( ( mediaTypes, callback ) => { + expect( mediaTypes[ 0 ] ).toEqual( MEDIA_TYPE_VIDEO ); + callback( MEDIA_ID, MEDIA_URL ); + } ); + + const onSelectURL = jest.fn(); + + const wrapper = shallow( + <MediaUpload + mediaType={ MEDIA_TYPE_VIDEO } + onSelectURL={ onSelectURL } + render={ ( { open, getMediaOptions } ) => { + return ( + <TouchableWithoutFeedback onPress={ open }> + { getMediaOptions() } + </TouchableWithoutFeedback> + ); + } } /> + ); + wrapper.find( 'Picker' ).simulate( 'change', option ); + expect( requestFunction ).toHaveBeenCalledTimes( 1 ); + + expect( onSelectURL ).toHaveBeenCalledTimes( 1 ); + expect( onSelectURL ).toHaveBeenCalledWith( MEDIA_ID, MEDIA_URL ); + }; + + it( 'can select media from device library', () => { + expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, requestMediaPickFromDeviceLibrary ); + } ); + + it( 'can select media from WP media library', () => { + expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, requestMediaPickFromMediaLibrary ); + } ); + + it( 'can select media by capturig', () => { + expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, requestMediaPickFromDeviceCamera ); + } ); +} ); diff --git a/packages/block-editor/src/components/rich-text/test/index.native.js b/packages/block-editor/src/components/rich-text/test/index.native.js new file mode 100644 index 00000000000000..22ee6b118bb2d2 --- /dev/null +++ b/packages/block-editor/src/components/rich-text/test/index.native.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +import { RichText } from '../index'; + +describe( 'RichText Native', () => { + let richText; + + beforeEach( () => { + richText = new RichText( { multiline: false } ); + } ); + + describe( 'willTrimSpaces', () => { + it( 'exists', () => { + expect( richText ).toHaveProperty( 'willTrimSpaces' ); + } ); + + it( 'is a function', () => { + expect( richText.willTrimSpaces ).toBeInstanceOf( Function ); + } ); + + it( 'reports false for styled text with no outer spaces', () => { + const html = '<p><b>Hello</b> <strong>Hello</strong> WorldWorld!</p>'; + expect( richText.willTrimSpaces( html ) ).toBe( false ); + } ); + } ); +} ); diff --git a/packages/block-library/src/code/test/edit.native.js b/packages/block-library/src/code/test/edit.native.js new file mode 100644 index 00000000000000..62e9fa9adb48cc --- /dev/null +++ b/packages/block-library/src/code/test/edit.native.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import renderer from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import Code from '../edit'; +import { TextInput } from 'react-native'; + +describe( 'Code', () => { + it( 'renders without crashing', () => { + const component = renderer.create( <Code attributes={ { content: '' } } /> ); + const rendered = component.toJSON(); + expect( rendered ).toBeTruthy(); + } ); + + it( 'renders given text without crashing', () => { + const component = renderer.create( <Code attributes={ { content: 'sample text' } } /> ); + const testInstance = component.root; + const textInput = testInstance.findByType( TextInput ); + expect( textInput ).toBeTruthy(); + expect( textInput.props.value ).toBe( 'sample text' ); + } ); +} ); diff --git a/packages/block-library/src/image/test/media-upload-progress.native.js b/packages/block-library/src/image/test/media-upload-progress.native.js new file mode 100644 index 00000000000000..042f571aeefe58 --- /dev/null +++ b/packages/block-library/src/image/test/media-upload-progress.native.js @@ -0,0 +1,162 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; +import { + sendMediaUpload, +} from 'react-native-gutenberg-bridge'; + +/** + * Internal dependencies + */ +import { + MediaUploadProgress, + MEDIA_UPLOAD_STATE_UPLOADING, + MEDIA_UPLOAD_STATE_SUCCEEDED, + MEDIA_UPLOAD_STATE_FAILED, + MEDIA_UPLOAD_STATE_RESET, +} from '../media-upload-progress'; + +jest.mock( 'react-native-gutenberg-bridge', () => { + const callUploadCallback = ( payload ) => { + this.uploadCallBack( payload ); + }; + const subscribeMediaUpload = ( callback ) => { + this.uploadCallBack = callback; + }; + return { + subscribeMediaUpload, + sendMediaUpload: callUploadCallback, + }; +} ); + +const MEDIA_ID = 123; + +describe( 'MediaUploadProgress component', () => { + it( 'renders without crashing', () => { + const wrapper = shallow( + <MediaUploadProgress renderContent={ () => {} } /> + ); + expect( wrapper ).toBeTruthy(); + } ); + + it( 'listens media upload progress', () => { + const progress = 10; + const payload = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + + const onUpdateMediaProgress = jest.fn(); + + const wrapper = shallow( + <MediaUploadProgress + onUpdateMediaProgress={ onUpdateMediaProgress } + mediaId={ MEDIA_ID } + renderContent={ () => {} } + /> + ); + + sendMediaUpload( payload ); + + expect( wrapper.instance().state.progress ).toEqual( progress ); + expect( wrapper.instance().state.isUploadInProgress ).toEqual( true ); + expect( wrapper.instance().state.isUploadFailed ).toEqual( false ); + expect( onUpdateMediaProgress ).toHaveBeenCalledTimes( 1 ); + expect( onUpdateMediaProgress ).toHaveBeenCalledWith( payload ); + } ); + + it( 'does not get affected by unrelated media uploads', () => { + const payload = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: 1, progress: 20 }; + const onUpdateMediaProgress = jest.fn(); + const wrapper = shallow( + <MediaUploadProgress + onUpdateMediaProgress={ onUpdateMediaProgress } + mediaId={ MEDIA_ID } + renderContent={ () => {} } + /> + ); + + sendMediaUpload( payload ); + + expect( wrapper.instance().state.progress ).toEqual( 0 ); + expect( onUpdateMediaProgress ).toHaveBeenCalledTimes( 0 ); + } ); + + it( 'listens media upload success', () => { + const progress = 10; + const payloadSuccess = { state: MEDIA_UPLOAD_STATE_SUCCEEDED, mediaId: MEDIA_ID }; + const payloadUploading = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + + const onFinishMediaUploadWithSuccess = jest.fn(); + + const wrapper = shallow( + <MediaUploadProgress + onFinishMediaUploadWithSuccess={ onFinishMediaUploadWithSuccess } + mediaId={ MEDIA_ID } + renderContent={ () => {} } + /> + ); + + sendMediaUpload( payloadUploading ); + + expect( wrapper.instance().state.progress ).toEqual( progress ); + + sendMediaUpload( payloadSuccess ); + + expect( wrapper.instance().state.isUploadInProgress ).toEqual( false ); + expect( onFinishMediaUploadWithSuccess ).toHaveBeenCalledTimes( 1 ); + expect( onFinishMediaUploadWithSuccess ).toHaveBeenCalledWith( payloadSuccess ); + } ); + + it( 'listens media upload fail', () => { + const progress = 10; + const payloadFail = { state: MEDIA_UPLOAD_STATE_FAILED, mediaId: MEDIA_ID }; + const payloadUploading = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + + const onFinishMediaUploadWithFailure = jest.fn(); + + const wrapper = shallow( + <MediaUploadProgress + onFinishMediaUploadWithFailure={ onFinishMediaUploadWithFailure } + mediaId={ MEDIA_ID } + renderContent={ () => {} } + /> + ); + + sendMediaUpload( payloadUploading ); + + expect( wrapper.instance().state.progress ).toEqual( progress ); + + sendMediaUpload( payloadFail ); + + expect( wrapper.instance().state.isUploadInProgress ).toEqual( false ); + expect( wrapper.instance().state.isUploadFailed ).toEqual( true ); + expect( onFinishMediaUploadWithFailure ).toHaveBeenCalledTimes( 1 ); + expect( onFinishMediaUploadWithFailure ).toHaveBeenCalledWith( payloadFail ); + } ); + + it( 'listens media upload reset', () => { + const progress = 10; + const payloadReset = { state: MEDIA_UPLOAD_STATE_RESET, mediaId: MEDIA_ID }; + const payloadUploading = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + + const onMediaUploadStateReset = jest.fn(); + + const wrapper = shallow( + <MediaUploadProgress + onMediaUploadStateReset={ onMediaUploadStateReset } + mediaId={ MEDIA_ID } + renderContent={ () => {} } + /> + ); + + sendMediaUpload( payloadUploading ); + + expect( wrapper.instance().state.progress ).toEqual( progress ); + + sendMediaUpload( payloadReset ); + + expect( wrapper.instance().state.isUploadInProgress ).toEqual( false ); + expect( wrapper.instance().state.isUploadFailed ).toEqual( false ); + expect( onMediaUploadStateReset ).toHaveBeenCalledTimes( 1 ); + expect( onMediaUploadStateReset ).toHaveBeenCalledWith( payloadReset ); + } ); +} ); diff --git a/packages/block-library/src/paragraph/test/edit.native.js b/packages/block-library/src/paragraph/test/edit.native.js new file mode 100644 index 00000000000000..3a7fea4e28e35d --- /dev/null +++ b/packages/block-library/src/paragraph/test/edit.native.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import Paragraph from '../edit'; + +/** + * WordPress dependencies + */ +jest.mock( '@wordpress/blocks' ); + +const getTestComponentWithContent = ( content ) => { + return shallow( + <Paragraph + attributes={ { content } } + setAttributes={ jest.fn() } + onReplace={ jest.fn() } + insertBlocksAfter={ jest.fn() } + /> + ); +}; + +describe( 'Paragraph block', () => { + it( 'renders without crashing', () => { + const component = getTestComponentWithContent( '' ); + expect( component.exists() ).toBe( true ); + } ); +} ); diff --git a/packages/format-library/src/link/test/modal.native.js b/packages/format-library/src/link/test/modal.native.js new file mode 100644 index 00000000000000..03594a4ef1c380 --- /dev/null +++ b/packages/format-library/src/link/test/modal.native.js @@ -0,0 +1,66 @@ +/** + * Internal dependencies + */ +import ModalLinkUI from '../modal'; +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +describe( 'LinksUI', () => { + it( 'LinksUI renders', () => { + const wrapper = shallow( + <ModalLinkUI /> + ); + expect( wrapper ).toBeTruthy(); + } ); + + it( 'Links are removed when no text is in the URL field', () => { + // Given + const onRemove = jest.fn(); + const wrapper = shallow( + <ModalLinkUI + onRemove={ onRemove } + onClose={ jest.fn() } + /> + ).dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI + + // When + + // Close the BottomSheet + const bottomSheet = wrapper.find( 'BottomSheet' ).first(); + bottomSheet.simulate( 'close' ); + + // Then + + expect( onRemove ).toHaveBeenCalledTimes( 1 ); + } ); + + it( 'Links are saved when URL field has content', () => { + // Given + const onRemove = jest.fn(); + const wrapper = shallow( + <ModalLinkUI + onRemove={ onRemove } + onClose={ jest.fn() } + /> + ).dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI + + // Mock `submitLink` for simplicity (we don't want to test submitLink itself here) + wrapper.instance().submitLink = jest.fn(); + + // When + + // Simulate user typing on the URL Cell. + const bottomSheet = wrapper.find( 'BottomSheet' ).first(); + const cell = bottomSheet.find( 'BottomSheetCell' ).first(); + cell.simulate( 'changeValue', 'wordpress.com' ); + + // Close the BottomSheet + bottomSheet.simulate( 'close' ); + + // Then + expect( onRemove ).toHaveBeenCalledTimes( 0 ); + expect( wrapper.instance().submitLink ).toHaveBeenCalledTimes( 1 ); + } ); +} ); diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js index 597dd2735c33f1..4b1f0974325e99 100644 --- a/test/unit/jest.config.js +++ b/test/unit/jest.config.js @@ -23,5 +23,6 @@ module.exports = { '/packages/e2e-tests', '<rootDir>/.*/build/', '<rootDir>/.*/build-module/', + '<rootDir>/.+\.native\.js$', ], }; From fc08af8dd3555a3d287a9011aafd39580d8f8d37 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Tue, 4 Jun 2019 18:41:28 +0200 Subject: [PATCH 253/664] Fix inline docs and add tests for color utils (#15861) * Correct inline docs for colors utils * Adds some tests for the color utils --- packages/block-editor/README.md | 6 +- .../src/components/colors/test/utils.js | 83 +++++++++++++++++++ .../src/components/colors/utils.js | 9 +- 3 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 packages/block-editor/src/components/colors/test/utils.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 072bec27f51b9e..6467b59284706f 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -208,7 +208,7 @@ _Parameters_ _Returns_ -- `string`: String with the class corresponding to the color in the provided context. +- `?string`: String with the class corresponding to the color in the provided context. Returns undefined if either colorContextName or colorSlug are not provided. <a name="getColorObjectByAttributeValues" href="#getColorObjectByAttributeValues">#</a> **getColorObjectByAttributeValues** @@ -223,7 +223,7 @@ _Parameters_ _Returns_ -- `?string`: If definedColor is passed and the name is found in colors, the color object exactly as set by the theme or editor defaults is returned. Otherwise, an object that just sets the color is defined. +- `?Object`: If definedColor is passed and the name is found in colors, the color object exactly as set by the theme or editor defaults is returned. Otherwise, an object that just sets the color is defined. <a name="getColorObjectByColorValue" href="#getColorObjectByColorValue">#</a> **getColorObjectByColorValue** @@ -236,7 +236,7 @@ _Parameters_ _Returns_ -- `?string`: Returns the color object included in the colors array whose color property equals colorValue. Returns undefined if no color object matches this requirement. +- `?Object`: Color object included in the colors array whose color property equals colorValue. Returns undefined if no color object matches this requirement. <a name="getFontSize" href="#getFontSize">#</a> **getFontSize** diff --git a/packages/block-editor/src/components/colors/test/utils.js b/packages/block-editor/src/components/colors/test/utils.js new file mode 100644 index 00000000000000..8eb370394aae83 --- /dev/null +++ b/packages/block-editor/src/components/colors/test/utils.js @@ -0,0 +1,83 @@ +/** + * Internal dependencies + */ +import { + getColorObjectByAttributeValues, + getColorObjectByColorValue, + getColorClassName, +} from '../utils'; + +describe( 'color utils', () => { + describe( 'getColorObjectByAttributeValues', () => { + it( 'should return the custom color object when there is no definedColor', () => { + const colors = [ + { slug: 'red' }, + { slug: 'green' }, + { slug: 'blue' }, + ]; + const customColor = '#ffffff'; + + expect( getColorObjectByAttributeValues( colors, undefined, customColor ) ).toEqual( { color: customColor } ); + } ); + + it( 'should return the custom color object when definedColor was not found', () => { + const colors = [ + { slug: 'red' }, + { slug: 'green' }, + { slug: 'blue' }, + ]; + const definedColor = 'purple'; + const customColor = '#ffffff'; + + expect( getColorObjectByAttributeValues( colors, definedColor, customColor ) ).toEqual( { color: customColor } ); + } ); + + it( 'should return the found color object', () => { + const colors = [ + { slug: 'red' }, + { slug: 'green' }, + { slug: 'blue' }, + ]; + const definedColor = 'blue'; + const customColor = '#ffffff'; + + expect( getColorObjectByAttributeValues( colors, definedColor, customColor ) ).toEqual( { slug: 'blue' } ); + } ); + } ); + + describe( 'getColorObjectByColorValue', () => { + it( 'should return undefined if the given color was not found', () => { + const colors = [ + { slug: 'red', color: '#ff0000' }, + { slug: 'green', color: '#00ff00' }, + { slug: 'blue', color: '#0000ff' }, + ]; + + expect( getColorObjectByColorValue( colors, '#ffffff' ) ).toBeUndefined(); + } ); + + it( 'should return a color object for the given color value', () => { + const colors = [ + { slug: 'red', color: '#ff0000' }, + { slug: 'green', color: '#00ff00' }, + { slug: 'blue', color: '#0000ff' }, + ]; + + expect( getColorObjectByColorValue( colors, '#00ff00' ) ).toEqual( { slug: 'green', color: '#00ff00' } ); + } ); + } ); + + describe( 'getColorClassName', () => { + it( 'should return undefined if colorContextName is missing', () => { + expect( getColorClassName( undefined, 'Light Purple' ) ).toBeUndefined(); + } ); + + it( 'should return undefined if colorSlug is missing', () => { + expect( getColorClassName( 'background', undefined ) ).toBeUndefined(); + } ); + + it( 'should return a class name with the color slug in kebab case', () => { + expect( getColorClassName( 'background', 'Light Purple' ) ).toBe( 'has-light-purple-background' ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/components/colors/utils.js b/packages/block-editor/src/components/colors/utils.js index 29ab15f30e731b..67be0eed5a2517 100644 --- a/packages/block-editor/src/components/colors/utils.js +++ b/packages/block-editor/src/components/colors/utils.js @@ -12,7 +12,7 @@ import tinycolor from 'tinycolor2'; * @param {?string} definedColor A string containing the color slug. * @param {?string} customColor A string containing the customColor value. * - * @return {?string} If definedColor is passed and the name is found in colors, + * @return {?Object} If definedColor is passed and the name is found in colors, * the color object exactly as set by the theme or editor defaults is returned. * Otherwise, an object that just sets the color is defined. */ @@ -35,7 +35,7 @@ export const getColorObjectByAttributeValues = ( colors, definedColor, customCol * @param {Array} colors Array of color objects as set by the theme or by the editor defaults. * @param {?string} colorValue A string containing the color value. * -* @return {?string} Returns the color object included in the colors array whose color property equals colorValue. +* @return {?Object} Color object included in the colors array whose color property equals colorValue. * Returns undefined if no color object matches this requirement. */ export const getColorObjectByColorValue = ( colors, colorValue ) => { @@ -48,11 +48,12 @@ export const getColorObjectByColorValue = ( colors, colorValue ) => { * @param {string} colorContextName Context/place where color is being used e.g: background, text etc... * @param {string} colorSlug Slug of the color. * - * @return {string} String with the class corresponding to the color in the provided context. + * @return {?string} String with the class corresponding to the color in the provided context. + * Returns undefined if either colorContextName or colorSlug are not provided. */ export function getColorClassName( colorContextName, colorSlug ) { if ( ! colorContextName || ! colorSlug ) { - return; + return undefined; } return `has-${ kebabCase( colorSlug ) }-${ colorContextName }`; From 0d60f1bb51391ac12243ff573253733c07479686 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Wed, 5 Jun 2019 03:07:09 +1000 Subject: [PATCH 254/664] Redesign Table Block Placeholder (#15903) * Adds placeholder with BlockIcon * Adds column orientation for placeholder layout * Styling improvements and copy * Fixes failing e2e tests * Adds documentation to Placeholder * CSS adjustments as per design feedback * Addresses PR feedback --- packages/block-library/src/table/edit.js | 46 ++++++++++++------- packages/block-library/src/table/editor.scss | 12 +++++ packages/components/src/placeholder/README.md | 9 ++++ packages/components/src/placeholder/index.js | 6 +-- .../components/src/placeholder/style.scss | 5 ++ packages/e2e-tests/specs/blocks/table.test.js | 8 ++-- 6 files changed, 63 insertions(+), 23 deletions(-) diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 79af531cd2929b..bdc6cfc253cebd 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -13,6 +13,7 @@ import { RichText, PanelColorSettings, createCustomColorsHOC, + BlockIcon, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { @@ -22,6 +23,7 @@ import { Button, Toolbar, DropdownMenu, + Placeholder, } from '@wordpress/components'; /** @@ -36,6 +38,7 @@ import { deleteColumn, toggleSection, } from './state'; +import icon from './icon'; const BACKGROUND_COLORS = [ { @@ -418,23 +421,32 @@ export class TableEdit extends Component { if ( isEmpty ) { return ( - <form onSubmit={ this.onCreateTable }> - <TextControl - type="number" - label={ __( 'Column Count' ) } - value={ initialColumnCount } - onChange={ this.onChangeInitialColumnCount } - min="1" - /> - <TextControl - type="number" - label={ __( 'Row Count' ) } - value={ initialRowCount } - onChange={ this.onChangeInitialRowCount } - min="1" - /> - <Button isPrimary type="submit">{ __( 'Create' ) }</Button> - </form> + <Placeholder + label={ __( 'Table' ) } + icon={ <BlockIcon icon={ icon } showColors /> } + instructions={ __( 'Insert a table for sharing data.' ) } + isColumnLayout + > + <form className="wp-block-table__placeholder-form" onSubmit={ this.onCreateTable }> + <TextControl + type="number" + label={ __( 'Column Count' ) } + value={ initialColumnCount } + onChange={ this.onChangeInitialColumnCount } + min="1" + className="wp-block-table__placeholder-input" + /> + <TextControl + type="number" + label={ __( 'Row Count' ) } + value={ initialRowCount } + onChange={ this.onChangeInitialRowCount } + min="1" + className="wp-block-table__placeholder-input" + /> + <Button className="wp-block-table__placeholder-button" isDefault type="submit">{ __( 'Create Table' ) }</Button> + </form> + </Placeholder> ); } diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index 52f6a60e545d15..46ae863c31a0d9 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -40,4 +40,16 @@ &__cell-content { padding: 0.5em; } + // Extra specificity to override default Placeholder styles. + &__placeholder-form.wp-block-table__placeholder-form { + text-align: left; + align-items: center; + } + &__placeholder-input { + width: 100px; + } + &__placeholder-button { + min-width: 100px; + justify-content: center; + } } diff --git a/packages/components/src/placeholder/README.md b/packages/components/src/placeholder/README.md index 5d4c64a7f64f19..2a12363e7fde01 100644 --- a/packages/components/src/placeholder/README.md +++ b/packages/components/src/placeholder/README.md @@ -11,3 +11,12 @@ const MyPlaceholder = () => ( /> ); ``` + +### Props + +Name | Type | Default | Description +--- | --- | --- | --- +`icon` | `string, ReactElement` | `undefined` | If provided, renders an icon next to the label. +`label` | `string` | `undefined` | Renders a label for the placeholder. +`instructions` | `string` | `undefined` | Renders instruction text below label. +`isColumnLayout` | `bool` | `false` | Changes placeholder children layout from flex-row to flex-column. diff --git a/packages/components/src/placeholder/index.js b/packages/components/src/placeholder/index.js index e5a5b323f60c7d..8e49b70d5226ec 100644 --- a/packages/components/src/placeholder/index.js +++ b/packages/components/src/placeholder/index.js @@ -15,9 +15,9 @@ import Dashicon from '../dashicon'; * @param {Object} props The component props. * @return {Object} The rendered placeholder. */ -function Placeholder( { icon, children, label, instructions, className, notices, preview, ...additionalProps } ) { +function Placeholder( { icon, children, label, instructions, className, notices, preview, isColumnLayout, ...additionalProps } ) { const classes = classnames( 'components-placeholder', className ); - + const fieldsetClasses = classnames( 'components-placeholder__fieldset', { 'is-column-layout': isColumnLayout } ); return ( <div { ...additionalProps } className={ classes }> { notices } @@ -31,7 +31,7 @@ function Placeholder( { icon, children, label, instructions, className, notices, { label } </div> { !! instructions && <div className="components-placeholder__instructions">{ instructions }</div> } - <div className="components-placeholder__fieldset"> + <div className={ fieldsetClasses }> { children } </div> </div> diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index 63938e69498e95..50e1da3d93af56 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -54,6 +54,11 @@ } } +.components-placeholder__fieldset.is-column-layout, +.components-placeholder__fieldset.is-column-layout form { + flex-direction: column; +} + .components-placeholder__input { margin-right: 8px; flex: 1 1 auto; diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js index 3a61f5e5eda594..0e3dd0bf5cad42 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -3,6 +3,8 @@ */ import { createNewPost, insertBlock, getEditedPostContent } from '@wordpress/e2e-test-utils'; +const createButtonSelector = "//div[@data-type='core/table']//button[text()='Create Table']"; + describe( 'Table', () => { beforeEach( async () => { await createNewPost(); @@ -34,7 +36,7 @@ describe( 'Table', () => { await page.keyboard.type( '10' ); // // Create the table. - const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + const createButton = await page.$x( createButtonSelector ); await createButton[ 0 ].click(); // // Expect the post content to have a correctly sized table. @@ -45,7 +47,7 @@ describe( 'Table', () => { await insertBlock( 'Table' ); // Create the table. - const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + const createButton = await page.$x( createButtonSelector ); await createButton[ 0 ].click(); // Click the first cell and add some text. @@ -81,7 +83,7 @@ describe( 'Table', () => { expect( footerSwitch ).toHaveLength( 0 ); // Create the table. - const createButton = await page.$x( "//div[@data-type='core/table']//button[text()='Create']" ); + const createButton = await page.$x( createButtonSelector ); await createButton[ 0 ].click(); // Expect the header and footer switches to be present now that the table has been created. From 672599a74c5a373cfba7c83a0616e7d9e3af8023 Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Tue, 4 Jun 2019 15:58:42 -0700 Subject: [PATCH 255/664] Fixes #15870. Removes the from the editor.scss file allowing it to break at the word. (#15871) --- packages/block-library/src/media-text/editor.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/media-text/editor.scss b/packages/block-library/src/media-text/editor.scss index fe6a552526971b..3d0ad0361bd947 100644 --- a/packages/block-library/src/media-text/editor.scss +++ b/packages/block-library/src/media-text/editor.scss @@ -3,7 +3,6 @@ "media-text-media media-text-content" "resizer resizer"; align-items: center; - word-break: break-all; } .wp-block-media-text.has-media-on-the-right { From c85e28f7b38e65f5bb6e01757ff890f3024a1036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 5 Jun 2019 10:39:41 +0200 Subject: [PATCH 256/664] Scripts: Ignore linting files located in build folders by default (#15977) * Scripts: Ignore linting files located in build folders by default * Add CHANGELOG entry --- packages/scripts/CHANGELOG.md | 6 ++++++ packages/scripts/README.md | 7 +++++++ packages/scripts/config/.eslintignore | 2 ++ packages/scripts/config/.npmpackagejsonlintignore | 3 +++ packages/scripts/config/.stylelintignore | 3 +++ packages/scripts/scripts/lint-js.js | 11 ++++++++++- packages/scripts/scripts/lint-pkg-json.js | 11 ++++++++++- packages/scripts/scripts/lint-style.js | 15 ++++++++++++--- 8 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 packages/scripts/config/.eslintignore create mode 100644 packages/scripts/config/.npmpackagejsonlintignore create mode 100644 packages/scripts/config/.stylelintignore diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 30aed0c902b09b..e7e408e8b0166f 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Features + +- The `lint-js`, `lint-pkg-json` and `lint-style` commands ignore now files located in `build` and `node_modules` folders by default ([15977](https://github.com/WordPress/gutenberg/pull/15977)). + ## 3.2.0 (2019-05-21) ### New Feature diff --git a/packages/scripts/README.md b/packages/scripts/README.md index b21c7078a61d56..0e394ad71f23a2 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -123,6 +123,9 @@ This is how you execute the script with presented setup: * `npm run lint:js` - lints JavaScript files in the entire project's directories. + +By default, files located in `build` and `node_modules` folders are ignored. + #### Advanced information It uses [eslint](https://eslint.org/) with the set of recommended rules defined in [@wordpress/eslint-plugin](https://www.npmjs.com/package/@wordpress/eslint-plugin) npm package. You can override default rules with your own as described in [eslint docs](https://eslint.org/docs/rules/). Learn more in the [Advanced Usage](#advanced-usage) section. @@ -145,6 +148,8 @@ This is how you execute those scripts using the presented setup: * `npm run lint:pkg-json` - lints `package.json` file in the project's root folder. +By default, files located in `build` and `node_modules` folders are ignored. + #### Advanced information It uses [npm-package-json-lint](https://www.npmjs.com/package/npm-package-json-lint) with the set of recommended rules defined in [@wordpress/npm-package-json-lint-config](https://www.npmjs.com/package/@wordpress/npm-package-json-lint-config) npm package. You can override default rules with your own as described in [npm-package-json-lint wiki](https://github.com/tclindner/npm-package-json-lint/wiki). Learn more in the [Advanced Usage](#advanced-usage) section. @@ -167,6 +172,8 @@ This is how you execute the script with presented setup: * `npm run lint:css` - lints CSS files in the whole project's directory. +By default, files located in `build` and `node_modules` folders are ignored. + #### Advanced information It uses [stylelint](https://github.com/stylelint/stylelint) with the [stylelint-config-wordpress](https://github.com/WordPress-Coding-Standards/stylelint-config-wordpress) configuration per the [WordPress CSS Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/css/). You can override them with your own rules as described in [stylelint user guide](https://github.com/stylelint/stylelint/docs/user-guide.md). Learn more in the [Advanced Usage](#advanced-usage) section. diff --git a/packages/scripts/config/.eslintignore b/packages/scripts/config/.eslintignore new file mode 100644 index 00000000000000..e3fbd98336eafe --- /dev/null +++ b/packages/scripts/config/.eslintignore @@ -0,0 +1,2 @@ +build +node_modules diff --git a/packages/scripts/config/.npmpackagejsonlintignore b/packages/scripts/config/.npmpackagejsonlintignore new file mode 100644 index 00000000000000..ae6cdbfb623bb1 --- /dev/null +++ b/packages/scripts/config/.npmpackagejsonlintignore @@ -0,0 +1,3 @@ +# By default, all `node_modules` are ignored. + +build diff --git a/packages/scripts/config/.stylelintignore b/packages/scripts/config/.stylelintignore new file mode 100644 index 00000000000000..ae6cdbfb623bb1 --- /dev/null +++ b/packages/scripts/config/.stylelintignore @@ -0,0 +1,3 @@ +# By default, all `node_modules` are ignored. + +build diff --git a/packages/scripts/scripts/lint-js.js b/packages/scripts/scripts/lint-js.js index c7346fb76ad88c..33bcdf83e5f897 100644 --- a/packages/scripts/scripts/lint-js.js +++ b/packages/scripts/scripts/lint-js.js @@ -17,6 +17,7 @@ const { const args = getCliArgs(); +// See: https://eslint.org/docs/user-guide/configuring#using-configuration-files-1. const hasLintConfig = hasCliArg( '-c' ) || hasCliArg( '--config' ) || hasProjectFile( '.eslintrc.js' ) || @@ -33,9 +34,17 @@ const config = ! hasLintConfig ? [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc.js' ) ] : []; +// See: https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories. +const hasIgnoredFiles = hasCliArg( '--ignore-path' ) || + hasProjectFile( '.eslintignore' ); + +const defaultIgnoreArgs = ! hasIgnoredFiles ? + [ '--ignore-path', fromConfigRoot( '.eslintignore' ) ] : + []; + const result = spawn( resolveBin( 'eslint' ), - [ ...config, ...args ], + [ ...config, ...defaultIgnoreArgs, ...args ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-pkg-json.js b/packages/scripts/scripts/lint-pkg-json.js index b37e0a07bc75bf..c3cbd4f429c680 100644 --- a/packages/scripts/scripts/lint-pkg-json.js +++ b/packages/scripts/scripts/lint-pkg-json.js @@ -17,6 +17,7 @@ const { const args = getCliArgs(); +// See: https://github.com/tclindner/npm-package-json-lint/wiki/configuration#configuration. const hasLintConfig = hasCliArg( '-c' ) || hasCliArg( '--configFile' ) || hasProjectFile( '.npmpackagejsonlintrc.json' ) || @@ -27,9 +28,17 @@ const config = ! hasLintConfig ? [ '--configFile', fromConfigRoot( 'npmpackagejsonlint.json' ) ] : []; +// See: https://github.com/tclindner/npm-package-json-lint/#cli-commands-and-configuration. +const hasIgnoredFiles = hasCliArg( '--ignorePath' ) || + hasProjectFile( '.npmpackagejsonlintignore' ); + +const defaultIgnoreArgs = ! hasIgnoredFiles ? + [ '--ignorePath', fromConfigRoot( '.npmpackagejsonlintignore' ) ] : + []; + const result = spawn( resolveBin( 'npm-package-json-lint', { executable: 'npmPkgJsonLint' } ), - [ ...config, ...args ], + [ ...config, ...defaultIgnoreArgs, ...args ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js index d72a877fa93702..32783a97f4eb2b 100644 --- a/packages/scripts/scripts/lint-style.js +++ b/packages/scripts/scripts/lint-style.js @@ -17,7 +17,8 @@ const { const args = getCliArgs(); -const hasStylelintConfig = hasCliArg( '--config' ) || +// See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#loading-the-configuration-object. +const hasLintConfig = hasCliArg( '--config' ) || hasProjectFile( '.stylelintrc' ) || hasProjectFile( '.stylelintrc.js' ) || hasProjectFile( '.stylelintrc.json' ) || @@ -26,13 +27,21 @@ const hasStylelintConfig = hasCliArg( '--config' ) || hasProjectFile( '.stylelint.config.js' ) || hasPackageProp( 'stylelint' ); -const config = ! hasStylelintConfig ? +const config = ! hasLintConfig ? [ '--config', fromConfigRoot( '.stylelintrc.json' ) ] : []; +// See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#stylelintignore. +const hasIgnoredFiles = hasCliArg( '--ignore-path' ) || + hasProjectFile( '.stylelintignore' ); + +const defaultIgnoreArgs = ! hasIgnoredFiles ? + [ '--ignore-path', fromConfigRoot( '.stylelintignore' ) ] : + []; + const result = spawn( resolveBin( 'stylelint' ), - [ ...config, ...args ], + [ ...config, ...defaultIgnoreArgs, ...args ], { stdio: 'inherit' } ); From 4d5998d7b8558c94c6030d167111c11f92c5b46a Mon Sep 17 00:00:00 2001 From: andrei draganescu <andrei.draganescu@automattic.com> Date: Wed, 5 Jun 2019 12:51:20 +0300 Subject: [PATCH 257/664] adds selection persistence on sidebar tab switch (#15583) * adds selection persistence on sidebar tab switch * autogen doc for the new restoreSelectedBlock action * removed useless spread operator and used lodash omit for a more declarative construction * wip: updating the test for better readability * small updates to make the tests pass * fixed the failing session persistence test * adds a destructive way to clear selection * adds destructive selection clearer and clears on visual edititng toggle and manual deselection * autogen docs * removes unused documentation * Docs: Rengenerate after API docs has changed * better docs and unit tests for new actions * updated the selection persistence test for better readability --- .../developers/data/data-core-block-editor.md | 21 +++++++- .../block-selection-clearer/index.js | 8 +-- packages/block-editor/src/store/actions.js | 27 +++++++++- packages/block-editor/src/store/reducer.js | 15 +++++- .../block-editor/src/store/test/actions.js | 18 +++++++ .../block-editor/src/store/test/reducer.js | 1 + packages/e2e-tests/specs/a11y.test.js | 51 +++++++++++++++++++ .../sidebar/settings-header/index.js | 4 +- packages/edit-post/src/store/effects.js | 2 +- 9 files changed, 138 insertions(+), 9 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 6e496446d6be20..f765454e948878 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -824,7 +824,9 @@ _Returns_ <a name="clearSelectedBlock" href="#clearSelectedBlock">#</a> **clearSelectedBlock** -Returns an action object used in signalling that the block selection is cleared. +Returns an action object used in signaling that the block selection is cleared. +This will save the current selection in a state called `previousSelection` and +`restoreSelectedBlock` will be able to restore the selection. _Returns_ @@ -1039,6 +1041,14 @@ _Returns_ - `Object`: Action object. +<a name="restoreSelectedBlock" href="#restoreSelectedBlock">#</a> **restoreSelectedBlock** + +Returns an action object used in restoring the previously cleared selected blocks. + +_Returns_ + +- `Object`: Action object. + <a name="selectBlock" href="#selectBlock">#</a> **selectBlock** Returns an action object used in signalling that the block with the @@ -1225,4 +1235,13 @@ _Returns_ Undocumented declaration. +<a name="wipeSelectedBlock" href="#wipeSelectedBlock">#</a> **wipeSelectedBlock** + +Returns an action object used in signaling that the block selection is wiped. +This will remove block selection so that `restoreSelectedBlock` will have no effect. + +_Returns_ + +- `Object`: Action object. + <!-- END TOKEN(Autogenerated actions) --> diff --git a/packages/block-editor/src/components/block-selection-clearer/index.js b/packages/block-editor/src/components/block-selection-clearer/index.js index be39e5df9e7de5..cc2e27944e06bd 100644 --- a/packages/block-editor/src/components/block-selection-clearer/index.js +++ b/packages/block-editor/src/components/block-selection-clearer/index.js @@ -33,12 +33,12 @@ class BlockSelectionClearer extends Component { const { hasSelectedBlock, hasMultiSelection, - clearSelectedBlock, + wipeSelectedBlock, } = this.props; const hasSelection = ( hasSelectedBlock || hasMultiSelection ); if ( event.target === this.container && hasSelection ) { - clearSelectedBlock(); + wipeSelectedBlock(); } } @@ -68,7 +68,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { clearSelectedBlock } = dispatch( 'core/block-editor' ); - return { clearSelectedBlock }; + const { wipeSelectedBlock } = dispatch( 'core/block-editor' ); + return { wipeSelectedBlock }; } ), ] )( BlockSelectionClearer ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 4dafaa291fcf31..e1e4c1f69ed040 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -193,7 +193,21 @@ export function multiSelect( start, end ) { } /** - * Returns an action object used in signalling that the block selection is cleared. + * Returns an action object used in signaling that the block selection is wiped. + * This will remove block selection so that `restoreSelectedBlock` will have no effect. + * + * @return {Object} Action object. + */ +export function wipeSelectedBlock() { + return { + type: 'WIPE_SELECTED_BLOCK', + }; +} + +/** + * Returns an action object used in signaling that the block selection is cleared. + * This will save the current selection in a state called `previousSelection` and + * `restoreSelectedBlock` will be able to restore the selection. * * @return {Object} Action object. */ @@ -203,6 +217,17 @@ export function clearSelectedBlock() { }; } +/** + * Returns an action object used in restoring the previously cleared selected blocks. + * + * @return {Object} Action object. + */ +export function restoreSelectedBlock() { + return { + type: 'RESTORE_SELECTED_BLOCK', + }; +} + /** * Returns an action object that enables or disables block selection. * diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index d9d0ecbdd28b64..e62734d0412e2c 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -717,8 +717,21 @@ const BLOCK_SELECTION_INITIAL_STATE = { */ export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action ) { switch ( action.type ) { - case 'CLEAR_SELECTED_BLOCK': + case 'WIPE_SELECTED_BLOCK': return BLOCK_SELECTION_INITIAL_STATE; + case 'CLEAR_SELECTED_BLOCK': + if ( isEqual( state, BLOCK_SELECTION_INITIAL_STATE ) ) { + return BLOCK_SELECTION_INITIAL_STATE; + } + return { + ...BLOCK_SELECTION_INITIAL_STATE, + previousSelection: omit( state, [ 'previousSelection' ] ), + }; + case 'RESTORE_SELECTED_BLOCK': + return { + ...BLOCK_SELECTION_INITIAL_STATE, + ...state.previousSelection, + }; case 'START_MULTI_SELECT': if ( state.isMultiSelecting ) { return state; diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 1fb97cd38d9bde..93123c17bf987b 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -3,6 +3,8 @@ */ import { clearSelectedBlock, + wipeSelectedBlock, + restoreSelectedBlock, enterFormattedText, exitFormattedText, hideInsertionPoint, @@ -116,6 +118,22 @@ describe( 'actions', () => { } ); } ); + describe( 'wipeSelectedBlock', () => { + it( 'should return WIPE_SELECTED_BLOCK action', () => { + expect( wipeSelectedBlock() ).toEqual( { + type: 'WIPE_SELECTED_BLOCK', + } ); + } ); + } ); + + describe( 'restoreSelectedBlock', () => { + it( 'should return RESTORE_SELECTED_BLOCK action', () => { + expect( restoreSelectedBlock() ).toEqual( { + type: 'RESTORE_SELECTED_BLOCK', + } ); + } ); + } ); + describe( 'replaceBlock', () => { it( 'should yield the REPLACE_BLOCKS action if the new block can be inserted in the destination root block', () => { const block = { diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 7543fdad57dd8b..b0d79d8911292e 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -1740,6 +1740,7 @@ describe( 'state', () => { initialPosition: null, isMultiSelecting: false, isEnabled: true, + previousSelection: original, } ); } ); diff --git a/packages/e2e-tests/specs/a11y.test.js b/packages/e2e-tests/specs/a11y.test.js index 6f6cc960f204aa..784ee4dc556743 100644 --- a/packages/e2e-tests/specs/a11y.test.js +++ b/packages/e2e-tests/specs/a11y.test.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { + clickBlockAppender, createNewPost, pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; @@ -29,6 +30,56 @@ describe( 'a11y', () => { expect( isFocusedToggle ).toBe( true ); } ); + it( 'checks persistent selection', async () => { + await clickBlockAppender(); + + // adding one Paragraph block which contains a focusable RichText + await page.keyboard.type( 'Testing editor selection persistence' ); + + let isFocusedRichText = await page.$eval( ':focus', ( focusedElement ) => { + return focusedElement.classList.contains( 'block-editor-rich-text__editable' ); + } ); + + expect( isFocusedRichText ).toBe( true ); + + // moving focus backwards using keyboard shortcuts + // twice to get to the inspector tabs + await pressKeyWithModifier( 'ctrlShift', '`' ); + await pressKeyWithModifier( 'ctrlShift', '`' ); + + await page.keyboard.press( 'Tab' ); + + const isFocusedInspectorDocumentTab = await page.$eval( ':focus', ( focusedElement ) => { + return focusedElement.getAttribute( 'data-label' ); + } ); + + expect( isFocusedInspectorDocumentTab ).toEqual( 'Document' ); + + await page.keyboard.press( 'Space' ); + + isFocusedRichText = await page.$eval( ':focus', ( focusedElement ) => { + return focusedElement.classList.contains( 'block-editor-rich-text__editable' ); + } ); + + expect( isFocusedRichText ).toBe( false ); + + await page.keyboard.press( 'Tab' ); + + const isFocusedInspectorBlockTab = await page.$eval( ':focus', ( focusedElement ) => { + return focusedElement.getAttribute( 'data-label' ); + } ); + + expect( isFocusedInspectorBlockTab ).toEqual( 'Block' ); + + await page.keyboard.press( 'Space' ); + + isFocusedRichText = await page.$eval( ':focus', ( focusedElement ) => { + return focusedElement.classList.contains( 'block-editor-rich-text__editable' ); + } ); + + expect( isFocusedRichText ).toBe( true ); + } ); + it( 'constrains focus to a modal when tabbing', async () => { // Open keyboard help modal. await pressKeyWithModifier( 'access', 'h' ); diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index eeb95a872166f5..47d00bce72048a 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -57,7 +57,8 @@ const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName export default withDispatch( ( dispatch ) => { const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - const { clearSelectedBlock } = dispatch( 'core/block-editor' ); + const { clearSelectedBlock, restoreSelectedBlock } = dispatch( 'core/block-editor' ); + return { openDocumentSettings() { openGeneralSidebar( 'edit-post/document' ); @@ -65,6 +66,7 @@ export default withDispatch( ( dispatch ) => { }, openBlockSettings() { openGeneralSidebar( 'edit-post/block' ); + restoreSelectedBlock(); }, }; } )( SettingsHeader ); diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index 54bfe07fbd67b5..36d3c2b501369c 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -109,7 +109,7 @@ const effects = { SWITCH_MODE( action ) { // Unselect blocks when we switch to the code editor. if ( action.mode !== 'visual' ) { - dispatch( 'core/block-editor' ).clearSelectedBlock(); + dispatch( 'core/block-editor' ).wipeSelectedBlock(); } const message = action.mode === 'visual' ? __( 'Visual editor selected' ) : __( 'Code editor selected' ); From a48892022f2f471faaa17d8769d28db70b3bd877 Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Wed, 5 Jun 2019 11:51:02 +0100 Subject: [PATCH 258/664] Enables Block Grouping/UnGrouping using core/group (#14908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Experiment with rudity mentory mechanic to allow Block Grouping Tried a few approaches via `insertBlock/removeBlocks` and even `replaceBlocks` but nothing preserved the undo history apart from this rather brute force method. * Migrate to `core/group` due to renaming of container Block from `core/section` * Adds conditionals to hide Group btn if selection is only a single `core/group` Block * Adds transform and updates Group button implementation to mirror Adds a `from` transform to the `core/group` Block. Currently this only works for `core/paragraph` but will need to work for all Block types. Updates the convert button to utilise `switchToBlockType` which invokes the same functionality as used in Block transform thereby unifiying the two methods of grouping. * Adds and applies Group icon As provided here https://github.com/WordPress/gutenberg/pull/14908#issuecomment-482196334 * Adds editor shortcut for Grouping Blocks * Add test to check for blocks before attempting replace * Adds wildcard Block transforms A major update to the transforms logic to enable wildcard Block transforms. * Pass Block names into transform callback function - enables dynamic Block creation in wildcard transforms (see `core/group`) * Add edge cases to Block transformation logic to account for specifying `*` (all) Block types as valid for a transformation * Remove unwanted test that checks all Blocks are of the same type before allowing a transform on a Multi Block selection * Reinstate missing createBlock dep after rebase * Fix to avoid allowing single Group Block to be Grouped * Update to use more appropriate logical negation operator for comparison Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r279828282 * Extracts key transform test logic into dedicated methods Previously hard coded values and difficult to follow logic were making the `switchToBlockType` overly complex. Extracted to well name methods to improve readability and allow to further refactoring. * Extract method to test for wildcard block transforms DRYs up code by extracting a function to test for the presence of a wildcard block transform * Moves logic to allow for wildcard transform into central transform checking method Previously test to allow wildcard transform to be valid were manually added as edge cases in conditions in predicate functions. This update centralises all logic to test whether a given transform is possible but including the logic that allows wildcard transforms within the main method `isPossibleTransformForSource` which determines whether a given transform is possible. * Adds UnGrouping mechanic * Adds UnGroup Icon * Adds e2e test to cover basic grouping for single and multiple blocks * Fix edge case with test to detect a single group container Block * Adds UnGroup keyboard shortcut * Adds more e2e test coverage Includes testing grouping via transforms, options menu and keyboard shortcuts * Adds check for group block availability before displaying grouping UI Also adds e2e tests to cover this. * Updates misnamned components Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r280851925 * Updates to preserve widest width alignment of child block on group container Previously if one of the child blocks being grouped had a width alignment (eg: wide or full) then the group block did not respect this. This meant that layouts weren’t preserved when grouping. Adds functionality and tests to ensure that when a group is created the widest alignment setting of the child blocks is set on the group block. * Updates to DRY out tests * Updates to simplify test setup Previously API calls were cleaning up blocks. This can be removed because all posts are auto removed before each test is run. Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r281445117 * Updates to simplify test assertion Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r281452179 * Combines test cases to simplify and reduce number of test required Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r281450978 * Updates to combine test for grouping and ungrouping via options menu Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r281450690 * Adds keyboard shortcut to global keyboard shortcuts modal * Ensure correct case for group shortcut (ie: lower) * Add shortcut to Block settings menu dropdown items * Adds translation context to Group actions in menus Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r283683456 * Update Block Transforms fixtures to show all Blocks can transform to Group Block * Updates Keyboard Shortcut test snapshot to include Group/UnGroup * Fix to ensure Group block accounted for * Fix Block deletion test failure via keyboard workaround Due to the addition of the “Group” item into the Block Options toolbar the “Remove Block” item had been removed outside the viewable portion of the Popover (not the popover has a restricted height and allows scrolling to see the additional items if there is insufficient space to display them all). Pupeteer isn’t able to click on items that are not visible. The simplest work around is to us the keyboard to select the “Remove Block” menu item rather. Trying to scroll a div within Pupeteer is fraught with problems and inconsistencies. * Fixes Remove Block button helper to be more resilient to change Previously we relied on the number of tab stops to locate the correct button in the menu. This change uses the actual text of the button to identify it and uses an assertion to ensure that the correct button is explicitly required. * Rename function to better convey intent Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r286106077 * Fixes to catch transforms which are invalid for blocks of different types A check was accidentally removed in `b2af0f2611e2a2bc66c62959dbf483abcbe103a9` which allowed multiple blocks of different types to be considered valid even if they were not. Adds conditional to ensure that unless the transform is a wildcard then we test that all the blocks are of the same type as the first block’s type before considering the transform to be valid. * Removes redundant snapshots * Fixes Transforms test to not over-test Group transforms Previously we tested every block transforming into the Group Block. This was unwanted overhead. Fixed to only test 1 single Block transforming into Group. Removed redundant snapshots as a result of removing it. * Fixes e2e test by reinstating helper lost during rebase * Fix to make Group transform snapshot tests more resilient to change Now explicity selects paragraph and image blocks to test the Group transform rather than simply testing the first block that transform into a Group. This ensures that if the order of transforms changes in the fixtures the test won’t be accidentally invalidated. Resolves: https://github.com/WordPress/gutenberg/pull/14908/#discussion_r287315363 * Updates to DRY out test and reduce test redundancy and performance Through use of smarter filtering we can get away with a single `it.each()` block which improves perf and removes redundant tests. Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r287342432 * Adds unit tests to cover new conditionals added for wildcard blocks transforms * Fixes capitalisation of UnGroup to Ungroup Resolves https://github.com/WordPress/gutenberg/pull/14908#discussion_r287415190 * Updates doc block to reflect possible nullable return type Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r287416745 * Updates Readme with doc update * Updates to remove unwanted comments * Updates capitalisatoin of “wildcard” Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r287435420 * Update comment with more context for future maintainers Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r287436023 * Updates to remove unwanted level of context on the translation Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> * Adds tests to cover isWildcardBlockTransform * Adds tests for isContainerGroupBlock function Note these test will need updating when we land https://github.com/WordPress/gutenberg/pull/15774 * Fixes logic around multi blocks of same type and adds tests Prevously we had 1 function attempting to test for multiple block selection and checking that the selection was all of the same block type. This caused bugs within `switchToBlockType` because the logic was confusing. For example, if a selection isn’t a multi block then we don’t need to test that all the blocks are the same. Separated the two functions and updated conditions in switchToBlockType to reflect this. Added unit tests to cover two new functions. * Adds new generator based API signature to Block transforms Previously the transforms function was pased two arguments 1. attributes 2. innerblocks This wasn’t extensible and more advanced transformations require more information about the block (eg: name). To avoid bloating the signature, a progressive enhancement approach is applied whereby if a generator function is passed as the transform then we pass the entire block object to the generator. This is opt-in only and is backwards compatible with all existing transform functions. * Adds new apply option method to transform API Previously we were modifying the existing transform function to conform to the requirements of a new API (ie: receiving the entire block object rather than the individual arguments). It was decided that introducing a new `apply` option and soft deprecating the old transform option would be preferable. The apply option if provided now takes precedence over the transform option. This is fully backwards compatible. See https://wordpress.slack.com/archives/C02QB2JS7/p1559567845087000 * Updates Block Reg docs to cover wildcard transforms * Updates changelog to document wildcards and transform apply option * Fix linting error introduce in rebase * Fixes test util to avoid infinite loops Previously if the button wasn’t found then the loop would continue forever looking for the button. This would have caused timeouts. Limits the loop to the number of buttons in the document. Also bails out immediately having found the button. Resolves https://github.com/WordPress/gutenberg/pull/14908#discussion_r290022464 * Renames apply to convert to avoid confusion with Func.apply To avoid potential confusion and overlap with Function.apply, rename to `convert`. Slack discussion: https://wordpress.slack.com/archives/C02QB2JS7/p1559593099150300?thread_ts=1559571243.134500&cid=C02QB2JS7 MDN Function apply docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply Resolves: https://github.com/WordPress/gutenberg/pull/14908#discussion_r290021073 * Fixes unecessary additional var introduced during debugging * Fix convert API to match established patterns for consistency Previously `convert` always passed an array of block objects even if there was only a single block. This is inconsistent with the implementation of the existing `transform` method which passes only a block’s attributes/innerBlocks pair when it is not a multi block. To retain consistency with the existing `isMultiBlock` paradiagm this updates the API of `convert` to pass a single block object when not in multiblock mode. * Fixes error in docs Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> * Fixes doc blocks to match coding style Resolves https://github.com/WordPress/gutenberg/pull/14908#discussion_r290024877 * Fixes icon size and color in dropdown for Group/Ungroup * Updates to remove keyboard shortcuts Following a discussion it was not possible to achieve a consensus on which shortcuts was most suitable (or indeed whether keyboard shortcuts for this were even a good idea). As a result this has been descoped from this PR and will be addressed elsewhere. Once merged I will push a new placeholder PR with the foundation for shortcuts in place and others can then amend it. * Updates snapshot to account for removing keyboard shortcuts * Removes e2e tests covering keyboard shortcuts * Fixes unwanted check introduced during debugging Test for existence of transform is not required and was introduced during debugging. Can be removed. * Updates to collapse spaces in doc blocks Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> * Fixes isWildcardBlockTransform to test for Array-ness Resolves https://github.com/WordPress/gutenberg/pull/14908#discussion_r290459410 * Fixes incorrect capitalisation of “UnGroup” to “Ungroup” Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r290461528 * Updates to remove redundant isMultiBlockSelection util function Addresses https://github.com/WordPress/gutenberg/pull/14908#discussion_r290463266 * Reinstate missing Ungroup icon Accidentally got lost during renaming of “UnGroup” to “Ungroup”! --- .../block-api/block-registration.md | 34 +++ .../src/components/block-actions/index.js | 40 ++- packages/block-library/src/group/index.js | 40 +++ packages/blocks/CHANGELOG.md | 2 + packages/blocks/README.md | 2 +- packages/blocks/src/api/factory.js | 104 +++++-- packages/blocks/src/api/test/factory.js | 271 +++++++++++++++++- .../e2e-tests/fixtures/block-transforms.js | 138 +++++++-- .../__snapshots__/block-grouping.test.js.snap | 99 +++++++ .../block-transforms.test.js.snap | 16 ++ .../e2e-tests/specs/block-deletion.test.js | 39 ++- .../e2e-tests/specs/block-grouping.test.js | 176 ++++++++++++ .../e2e-tests/specs/block-switcher.test.js | 3 + .../e2e-tests/specs/block-transforms.test.js | 7 +- .../convert-button.js | 127 ++++++++ .../convert-to-group-buttons/icons.js | 19 ++ .../convert-to-group-buttons/index.js | 33 +++ .../editor/src/components/provider/index.js | 2 + 18 files changed, 1097 insertions(+), 55 deletions(-) create mode 100644 packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap create mode 100644 packages/e2e-tests/specs/block-grouping.test.js create mode 100644 packages/editor/src/components/convert-to-group-buttons/convert-button.js create mode 100644 packages/editor/src/components/convert-to-group-buttons/icons.js create mode 100644 packages/editor/src/components/convert-to-group-buttons/index.js diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index 8a2d6c26a7dabf..7986be28bee4bc 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -311,6 +311,40 @@ transforms: { ``` {% end %} +In addition to accepting an array of known block types, the `blocks` option also accepts a "wildcard" (`"*"`). This allows for transformations which apply to _all_ block types (eg: all blocks can transform into `core/group`): + +{% codetabs %} +{% ES5 %} +```js +transforms: { + from: [ + { + type: 'block', + blocks: [ '*' ], // wildcard - match any block + transform: function( attributes, innerBlocks ) { + // transform logic here + }, + }, + ], +}, +``` +{% ESNext %} +```js +transforms: { + from: [ + { + type: 'block', + blocks: [ '*' ], // wildcard - match any block + transform: ( attributes, innerBlocks ) => { + // transform logic here + }, + }, + ], +}, +``` +{% end %} + + A block with innerBlocks can also be transformed from and to another block with innerBlocks. {% codetabs %} diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index 3b3f8032432e8b..c10d03a5c060f0 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -8,13 +8,15 @@ import { castArray, first, last, every } from 'lodash'; */ import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; -import { cloneBlock, hasBlockSupport } from '@wordpress/blocks'; +import { cloneBlock, hasBlockSupport, switchToBlockType } from '@wordpress/blocks'; function BlockActions( { onDuplicate, onRemove, onInsertBefore, onInsertAfter, + onGroup, + onUngroup, isLocked, canDuplicate, children, @@ -24,6 +26,8 @@ function BlockActions( { onRemove, onInsertAfter, onInsertBefore, + onGroup, + onUngroup, isLocked, canDuplicate, } ); @@ -65,6 +69,7 @@ export default compose( [ multiSelect, removeBlocks, insertDefaultBlock, + replaceBlocks, } = dispatch( 'core/block-editor' ); return { @@ -107,6 +112,39 @@ export default compose( [ insertDefaultBlock( {}, rootClientId, lastSelectedIndex + 1 ); } }, + onGroup() { + if ( ! blocks.length ) { + return; + } + + // Activate the `transform` on `core/group` which does the conversion + const newBlocks = switchToBlockType( blocks, 'core/group' ); + + if ( ! newBlocks ) { + return; + } + replaceBlocks( + clientIds, + newBlocks + ); + }, + + onUngroup() { + if ( ! blocks.length ) { + return; + } + + const innerBlocks = blocks[ 0 ].innerBlocks; + + if ( ! innerBlocks.length ) { + return; + } + + replaceBlocks( + clientIds, + innerBlocks + ); + }, }; } ), ] )( BlockActions ); diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index 9bab779f5863b5..386ab20a80955c 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -25,6 +26,45 @@ export const settings = { anchor: true, html: false, }, + + transforms: { + from: [ + { + type: 'block', + isMultiBlock: true, + blocks: [ '*' ], + convert( blocks ) { + // Avoid transforming a single `core/group` Block + if ( blocks.length === 1 && blocks[ 0 ].name === 'core/group' ) { + return; + } + + const alignments = [ 'wide', 'full' ]; + + // Determine the widest setting of all the blocks to be grouped + const widestAlignment = blocks.reduce( ( result, block ) => { + const { align } = block.attributes; + return alignments.indexOf( align ) > alignments.indexOf( result ) ? align : result; + }, undefined ); + + // Clone the Blocks to be Grouped + // Failing to create new block references causes the original blocks + // to be replaced in the switchToBlockType call thereby meaning they + // are removed both from their original location and within the + // new group block. + const groupInnerBlocks = blocks.map( ( block ) => { + return createBlock( block.name, block.attributes, block.innerBlocks ); + } ); + + return createBlock( 'core/group', { + align: widestAlignment, + }, groupInnerBlocks ); + }, + }, + + ], + }, + edit, save, }; diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index b13c58601e1e99..df5b183e688d45 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -3,6 +3,8 @@ ### New Feature - Added a default implementation for `save` setting in `registerBlockType` which saves no markup in the post content. +- Added wildcard block transforms which allows for transforming all/any blocks in another block. +- Added `convert()` method option to `transforms` definition. It receives complete block object(s) as it's argument(s). It is now preferred over the older `transform()` (note that `transform()` is still fully supported). ## 6.1.0 (2019-03-06) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 9bc199a54452f2..b7a8e5fd70c9dd 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -679,7 +679,7 @@ _Parameters_ _Returns_ -- `Array`: Array of blocks. +- `?Array`: Array of blocks or null. <a name="synchronizeBlocksWithTemplate" href="#synchronizeBlocksWithTemplate">#</a> **synchronizeBlocksWithTemplate** diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index c7bd01f2c699c4..304329d0be657f 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -11,6 +11,7 @@ import { filter, first, flatMap, + has, uniq, isFunction, isEmpty, @@ -117,26 +118,41 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { return false; } - // If multiple blocks are selected, only multi block transforms are allowed. + // If multiple blocks are selected, only multi block transforms + // or wildcard transforms are allowed. const isMultiBlock = blocks.length > 1; - const isValidForMultiBlocks = ! isMultiBlock || transform.isMultiBlock; + const firstBlockName = first( blocks ).name; + const isValidForMultiBlocks = isWildcardBlockTransform( transform ) || ! isMultiBlock || transform.isMultiBlock; if ( ! isValidForMultiBlocks ) { return false; } + // Check non-wildcard transforms to ensure that transform is valid + // for a block selection of multiple blocks of different types + if ( ! isWildcardBlockTransform( transform ) && ! every( blocks, { name: firstBlockName } ) ) { + return false; + } + // Only consider 'block' type transforms as valid. const isBlockType = transform.type === 'block'; if ( ! isBlockType ) { return false; } - // Check if the transform's block name matches the source block only if this is a transform 'from'. + // Check if the transform's block name matches the source block (or is a wildcard) + // only if this is a transform 'from'. const sourceBlock = first( blocks ); - const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1; + const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1 || isWildcardBlockTransform( transform ); if ( ! hasMatchingName ) { return false; } + // Don't allow single 'core/group' blocks to be transformed into + // a 'core/group' block. + if ( ! isMultiBlock && isContainerGroupBlock( sourceBlock.name ) && isContainerGroupBlock( transform.blockName ) ) { + return false; + } + // If the transform has a `isMatch` function specified, check that it returns true. if ( isFunction( transform.isMatch ) ) { const attributes = transform.isMultiBlock ? blocks.map( ( block ) => block.attributes ) : sourceBlock.attributes; @@ -171,7 +187,9 @@ const getBlockTypesForPossibleFromTransforms = ( blocks ) => { return !! findTransform( fromTransforms, - ( transform ) => isPossibleTransformForSource( transform, 'from', blocks ) + ( transform ) => { + return isPossibleTransformForSource( transform, 'from', blocks ); + } ); }, ); @@ -199,7 +217,9 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => { // filter all 'to' transforms to find those that are possible. const possibleTransforms = filter( transformsTo, - ( transform ) => isPossibleTransformForSource( transform, 'to', blocks ) + ( transform ) => { + return transform && isPossibleTransformForSource( transform, 'to', blocks ); + } ); // Build a list of block names using the possible 'to' transforms. @@ -212,6 +232,45 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => { return blockNames.map( ( name ) => getBlockType( name ) ); }; +/** + * Determines whether transform is a "block" type + * and if so whether it is a "wildcard" transform + * ie: targets "any" block type + * + * @param {Object} t the Block transform object + * + * @return {boolean} whether transform is a wildcard transform + */ +export const isWildcardBlockTransform = ( t ) => t && t.type === 'block' && Array.isArray( t.blocks ) && t.blocks.includes( '*' ); + +/** + * Determines whether the given Block is the core Block which + * acts as a container Block for other Blocks as part of the + * Grouping mechanics + * + * @param {string} name the name of the Block to test against + * + * @return {boolean} whether or not the Block is the container Block type + */ +export const isContainerGroupBlock = ( name ) => name === 'core/group'; + +/** + * Determines whether the provided Blocks are of the same type + * (eg: all `core/paragraph`). + * + * @param {Array} blocksArray the Block definitions + * + * @return {boolean} whether or not the given Blocks pass the criteria + */ +export const isBlockSelectionOfSameType = ( blocksArray = [] ) => { + if ( ! blocksArray.length ) { + return false; + } + const sourceName = blocksArray[ 0 ].name; + + return every( blocksArray, [ 'name', sourceName ] ); +}; + /** * Returns an array of block types that the set of blocks received as argument * can be transformed into. @@ -225,12 +284,6 @@ export function getPossibleBlockTransformations( blocks ) { return []; } - const sourceBlock = first( blocks ); - const isMultiBlock = blocks.length > 1; - if ( isMultiBlock && ! every( blocks, { name: sourceBlock.name } ) ) { - return []; - } - const blockTypesForFromTransforms = getBlockTypesForPossibleFromTransforms( blocks ); const blockTypesForToTransforms = getBlockTypesForPossibleToTransforms( blocks ); @@ -313,7 +366,7 @@ export function getBlockTransforms( direction, blockTypeOrName ) { * @param {Array|Object} blocks Blocks array or block object. * @param {string} name Block name. * - * @return {Array} Array of blocks. + * @return {?Array} Array of blocks or null. */ export function switchToBlockType( blocks, name ) { const blocksArray = castArray( blocks ); @@ -321,7 +374,10 @@ export function switchToBlockType( blocks, name ) { const firstBlock = blocksArray[ 0 ]; const sourceName = firstBlock.name; - if ( isMultiBlock && ! every( blocksArray, ( block ) => ( block.name === sourceName ) ) ) { + // Unless it's a `core/group` Block then for multi block selections + // check that all Blocks are of the same type otherwise + // we can't run a conversion + if ( ! isContainerGroupBlock( name ) && isMultiBlock && ! isBlockSelectionOfSameType( blocksArray ) ) { return null; } @@ -329,14 +385,15 @@ export function switchToBlockType( blocks, name ) { // transformation. const transformationsFrom = getBlockTransforms( 'from', name ); const transformationsTo = getBlockTransforms( 'to', sourceName ); + const transformation = findTransform( transformationsTo, - ( t ) => t.type === 'block' && t.blocks.indexOf( name ) !== -1 && ( ! isMultiBlock || t.isMultiBlock ) + ( t ) => t.type === 'block' && ( ( isWildcardBlockTransform( t ) ) || t.blocks.indexOf( name ) !== -1 ) && ( ! isMultiBlock || t.isMultiBlock ) ) || findTransform( transformationsFrom, - ( t ) => t.type === 'block' && t.blocks.indexOf( sourceName ) !== -1 && ( ! isMultiBlock || t.isMultiBlock ) + ( t ) => t.type === 'block' && ( ( isWildcardBlockTransform( t ) ) || t.blocks.indexOf( sourceName ) !== -1 ) && ( ! isMultiBlock || t.isMultiBlock ) ); // Stop if there is no valid transformation. @@ -345,11 +402,18 @@ export function switchToBlockType( blocks, name ) { } let transformationResults; + if ( transformation.isMultiBlock ) { - transformationResults = transformation.transform( - blocksArray.map( ( currentBlock ) => currentBlock.attributes ), - blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ) - ); + if ( has( transformation, 'convert' ) ) { + transformationResults = transformation.convert( blocksArray ); + } else { + transformationResults = transformation.transform( + blocksArray.map( ( currentBlock ) => currentBlock.attributes ), + blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ), + ); + } + } else if ( has( transformation, 'convert' ) ) { + transformationResults = transformation.convert( firstBlock ); } else { transformationResults = transformation.transform( firstBlock.attributes, firstBlock.innerBlocks ); } diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index 11249b08d36ced..3e0d42c103bb9a 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -2,7 +2,7 @@ * External dependencies */ import deepFreeze from 'deep-freeze'; -import { noop } from 'lodash'; +import { noop, times } from 'lodash'; /** * Internal dependencies @@ -14,6 +14,9 @@ import { switchToBlockType, getBlockTransforms, findTransform, + isWildcardBlockTransform, + isContainerGroupBlock, + isBlockSelectionOfSameType, } from '../factory'; import { getBlockType, @@ -776,6 +779,81 @@ describe( 'block factory', () => { expect( isMatch ).toHaveBeenCalledWith( [ { value: 'ribs' }, { value: 'halloumi' } ] ); } ); + + describe( 'wildcard block transforms', () => { + beforeEach( () => { + registerBlockType( 'core/group', { + attributes: { + value: { + type: 'string', + }, + }, + transforms: { + from: [ { + type: 'block', + blocks: [ '*' ], + transform: noop, + } ], + }, + save: noop, + category: 'common', + title: 'A block that groups other blocks.', + } ); + } ); + + it( 'should should show wildcard "from" transformation as available for multiple blocks of the same type', () => { + registerBlockType( 'core/text-block', defaultBlockSettings ); + registerBlockType( 'core/image-block', defaultBlockSettings ); + + const textBlocks = times( 4, ( index ) => { + return createBlock( 'core/text-block', { + value: `textBlock${ index + 1 }`, + } ); + } ); + + const availableBlocks = getPossibleBlockTransformations( textBlocks ); + + expect( availableBlocks ).toHaveLength( 1 ); + expect( availableBlocks[ 0 ].name ).toBe( 'core/group' ); + } ); + + it( 'should should show wildcard "from" transformation as available for multiple blocks of different types', () => { + registerBlockType( 'core/text-block', defaultBlockSettings ); + registerBlockType( 'core/image-block', defaultBlockSettings ); + + const textBlocks = times( 2, ( index ) => { + return createBlock( 'core/text-block', { + value: `textBlock${ index + 1 }`, + } ); + } ); + + const imageBlocks = times( 2, ( index ) => { + return createBlock( 'core/image-block', { + value: `imageBlock${ index + 1 }`, + } ); + } ); + + const availableBlocks = getPossibleBlockTransformations( [ ...textBlocks, ...imageBlocks ] ); + + expect( availableBlocks ).toHaveLength( 1 ); + expect( availableBlocks[ 0 ].name ).toBe( 'core/group' ); + } ); + + it( 'should should show wildcard "from" transformation as available for single blocks', () => { + registerBlockType( 'core/text-block', defaultBlockSettings ); + + const blocks = times( 1, ( index ) => { + return createBlock( 'core/text-block', { + value: `textBlock${ index + 1 }`, + } ); + } ); + + const availableBlocks = getPossibleBlockTransformations( blocks ); + + expect( availableBlocks ).toHaveLength( 1 ); + expect( availableBlocks[ 0 ].name ).toBe( 'core/group' ); + } ); + } ); } ); describe( 'switchToBlockType()', () => { @@ -1222,6 +1300,94 @@ describe( 'block factory', () => { expect( transformedBlocks[ 1 ].innerBlocks ).toHaveLength( 1 ); expect( transformedBlocks[ 1 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after1' ); } ); + + it( 'should pass entire block object(s) to the "convert" method if defined', () => { + registerBlockType( 'core/test-group-block', { + attributes: { + value: { + type: 'string', + }, + }, + transforms: { + from: [ { + type: 'block', + blocks: [ '*' ], + isMultiBlock: true, + convert( blocks ) { + const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { + return createBlock( name, attributes, innerBlocks ); + } ); + + return createBlock( 'core/test-group-block', {}, groupInnerBlocks ); + }, + } ], + }, + save: noop, + category: 'common', + title: 'Test Group Block', + } ); + + registerBlockType( 'core/text-block', defaultBlockSettings ); + + const numOfBlocksToGroup = 4; + const blocks = times( numOfBlocksToGroup, ( index ) => { + return createBlock( 'core/text-block', { + value: `textBlock${ index + 1 }`, + } ); + } ); + + const transformedBlocks = switchToBlockType( blocks, 'core/test-group-block' ); + + expect( transformedBlocks ).toHaveLength( 1 ); + expect( transformedBlocks[ 0 ].name ).toBe( 'core/test-group-block' ); + expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( numOfBlocksToGroup ); + } ); + + it( 'should prefer "convert" method over "transform" method when running a transformation', () => { + const convertSpy = jest.fn( ( blocks ) => { + const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { + return createBlock( name, attributes, innerBlocks ); + } ); + + return createBlock( 'core/test-group-block', {}, groupInnerBlocks ); + } ); + const transformSpy = jest.fn(); + + registerBlockType( 'core/test-group-block', { + attributes: { + value: { + type: 'string', + }, + }, + transforms: { + from: [ { + type: 'block', + blocks: [ '*' ], + isMultiBlock: true, + convert: convertSpy, + transform: transformSpy, + } ], + }, + save: noop, + category: 'common', + title: 'Test Group Block', + } ); + + registerBlockType( 'core/text-block', defaultBlockSettings ); + + const numOfBlocksToGroup = 4; + const blocks = times( numOfBlocksToGroup, ( index ) => { + return createBlock( 'core/text-block', { + value: `textBlock${ index + 1 }`, + } ); + } ); + + const transformedBlocks = switchToBlockType( blocks, 'core/test-group-block' ); + + expect( transformedBlocks ).toHaveLength( 1 ); + expect( convertSpy.mock.calls ).toHaveLength( 1 ); + expect( transformSpy.mock.calls ).toHaveLength( 0 ); + } ); } ); describe( 'getBlockTransforms', () => { @@ -1336,4 +1502,107 @@ describe( 'block factory', () => { expect( transform ).toBe( null ); } ); } ); + + describe( 'isWildcardBlockTransform', () => { + it( 'should return true for transforms with type of block and "*" alias as blocks', () => { + const validWildcardBlockTransform = { + type: 'block', + blocks: [ + 'core/some-other-block-first', // unlikely to happen but... + '*', + ], + blockName: 'core/test-block', + }; + + expect( isWildcardBlockTransform( validWildcardBlockTransform ) ).toBe( true ); + } ); + + it( 'should return false for transforms with a type which is not "block"', () => { + const invalidWildcardBlockTransform = { + type: 'file', + blocks: [ + '*', + ], + blockName: 'core/test-block', + }; + + expect( isWildcardBlockTransform( invalidWildcardBlockTransform ) ).toBe( false ); + } ); + + it( 'should return false for transforms which do not include "*" alias in "block" array', () => { + const invalidWildcardBlockTransform = { + type: 'block', + blocks: [ + 'core/some-block', + 'core/another-block', + ], + blockName: 'core/test-block', + }; + + expect( isWildcardBlockTransform( invalidWildcardBlockTransform ) ).toBe( false ); + } ); + + it( 'should return false for transforms which do not provide an array as the "blocks" option', () => { + const invalidWildcardBlockTransform = { + type: 'block', + blocks: noop, + blockName: 'core/test-block', + }; + + expect( isWildcardBlockTransform( invalidWildcardBlockTransform ) ).toBe( false ); + } ); + } ); + + describe( 'isContainerGroupBlock', () => { + it( 'should return true when passed block name matches "core/group"', () => { + expect( isContainerGroupBlock( 'core/group' ) ).toBe( true ); + } ); + + it( 'should return false when passed block name does not match "core/group"', () => { + expect( isContainerGroupBlock( 'core/some-test-name' ) ).toBe( false ); + } ); + } ); + + describe( 'isBlockSelectionOfSameType', () => { + it( 'should return false when all blocks do not match the name of the first block', () => { + const blocks = [ + { + name: 'core/test-block', + }, + { + name: 'core/test-block', + }, + { + name: 'core/test-block', + }, + { + name: 'core/another-block', + }, + { + name: 'core/test-block', + }, + ]; + + expect( isBlockSelectionOfSameType( blocks ) ).toBe( false ); + } ); + + it( 'should return true when all blocks match the name of the first block', () => { + const blocks = [ + { + name: 'core/test-block', + }, + { + name: 'core/test-block', + }, + { + name: 'core/test-block', + }, + { + name: 'core/test-block', + }, + ]; + + expect( isBlockSelectionOfSameType( blocks ) ).toBe( true ); + } ); + } ); } ); diff --git a/packages/e2e-tests/fixtures/block-transforms.js b/packages/e2e-tests/fixtures/block-transforms.js index 79d4d7bc804566..913953d69d2b3e 100644 --- a/packages/e2e-tests/fixtures/block-transforms.js +++ b/packages/e2e-tests/fixtures/block-transforms.js @@ -1,131 +1,166 @@ export const EXPECTED_TRANSFORMS = { core__archives: { originalBlock: 'Archives', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__archives__showPostCounts: { originalBlock: 'Archives', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__audio: { originalBlock: 'Audio', availableTransforms: [ 'File', + 'Group', ], }, core__button__center: { originalBlock: 'Button', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__calendar: { originalBlock: 'Calendar', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__media-text': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Image', ], }, 'core__media-text__image-alt-no-align': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Image', ], }, 'core__media-text__image-fill-no-focal-point-selected': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Image', ], }, 'core__media-text__image-fill-with-focal-point-selected': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Image', ], }, 'core__media-text__is-stacked-on-mobile': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Video', ], }, 'core__media-text__media-right-custom-width': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Video', ], }, 'core__media-text__vertical-align-bottom': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Image', ], }, 'core__media-text__video': { originalBlock: 'Media & Text', availableTransforms: [ + 'Group', 'Video', ], }, core__categories: { originalBlock: 'Categories', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__code: { originalBlock: 'Code', availableTransforms: [ + 'Group', 'Preformatted', ], }, core__columns: { originalBlock: 'Columns', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__cover: { availableTransforms: [ + 'Group', 'Image', ], originalBlock: 'Cover', }, core__cover__video: { availableTransforms: [ + 'Group', 'Video', ], originalBlock: 'Cover', }, 'core__cover__video-overlay': { availableTransforms: [ + 'Group', 'Video', ], originalBlock: 'Cover', }, core__embed: { originalBlock: 'Embed', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__file__new-window': { originalBlock: 'File', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__file__no-download-button': { originalBlock: 'File', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__file__no-text-link': { originalBlock: 'File', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__gallery: { originalBlock: 'Gallery', availableTransforms: [ + 'Group', 'Image', ], }, core__gallery__columns: { originalBlock: 'Gallery', availableTransforms: [ + 'Group', 'Image', ], }, @@ -137,6 +172,7 @@ export const EXPECTED_TRANSFORMS = { originalBlock: 'Heading', availableTransforms: [ 'Quote', + 'Group', 'Paragraph', ], }, @@ -144,12 +180,15 @@ export const EXPECTED_TRANSFORMS = { originalBlock: 'Heading', availableTransforms: [ 'Quote', + 'Group', 'Paragraph', ], }, core__html: { originalBlock: 'Custom HTML', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__image: { originalBlock: 'Image', @@ -157,6 +196,7 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, @@ -166,6 +206,7 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, @@ -175,6 +216,7 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, @@ -184,6 +226,7 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, @@ -193,6 +236,7 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, @@ -202,6 +246,7 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, @@ -211,43 +256,59 @@ export const EXPECTED_TRANSFORMS = { 'Gallery', 'Cover', 'File', + 'Group', 'Media & Text', ], }, 'core__latest-comments': { originalBlock: 'Latest Comments', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__latest-posts': { originalBlock: 'Latest Posts', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__latest-posts__displayPostDate': { originalBlock: 'Latest Posts', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__legacy-widget': { originalBlock: 'Legacy Widget (Experimental)', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__list__ul: { originalBlock: 'List', availableTransforms: [ + 'Group', 'Paragraph', 'Quote', ], }, core__more: { originalBlock: 'More', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__more__custom-text-teaser': { originalBlock: 'More', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__nextpage: { originalBlock: 'Page Break', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__paragraph__align-right': { originalBlock: 'Paragraph', @@ -255,6 +316,7 @@ export const EXPECTED_TRANSFORMS = { 'Heading', 'List', 'Quote', + 'Group', 'Preformatted', 'Verse', ], @@ -262,6 +324,7 @@ export const EXPECTED_TRANSFORMS = { core__preformatted: { originalBlock: 'Preformatted', availableTransforms: [ + 'Group', 'Paragraph', ], }, @@ -269,18 +332,21 @@ export const EXPECTED_TRANSFORMS = { originalBlock: 'Pullquote', availableTransforms: [ 'Quote', + 'Group', ], }, 'core__pullquote__multi-paragraph': { originalBlock: 'Pullquote', availableTransforms: [ 'Quote', + 'Group', ], }, 'core__quote__style-1': { originalBlock: 'Quote', availableTransforms: [ 'List', + 'Group', 'Paragraph', 'Heading', 'Pullquote', @@ -290,6 +356,7 @@ export const EXPECTED_TRANSFORMS = { originalBlock: 'Quote', availableTransforms: [ 'List', + 'Group', 'Paragraph', 'Heading', 'Pullquote', @@ -297,44 +364,62 @@ export const EXPECTED_TRANSFORMS = { }, core__rss: { originalBlock: 'RSS', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__search: { originalBlock: 'Search', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__search__custom-text': { originalBlock: 'Search', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__separator: { originalBlock: 'Separator', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__shortcode: { originalBlock: 'Shortcode', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__spacer: { originalBlock: 'Spacer', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, core__table: { originalBlock: 'Table', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__tag-cloud': { originalBlock: 'Tag Cloud', - availableTransforms: [], + availableTransforms: [ + 'Group', + ], }, 'core__tag-cloud__showTagCounts': { originalBlock: 'Tag Cloud', availableTransforms: [ + 'Group', ], }, core__verse: { originalBlock: 'Verse', availableTransforms: [ + 'Group', 'Paragraph', ], }, @@ -343,6 +428,7 @@ export const EXPECTED_TRANSFORMS = { availableTransforms: [ 'Cover', 'File', + 'Group', 'Media & Text', ], }, diff --git a/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap new file mode 100644 index 00000000000000..af571b2010ed25 --- /dev/null +++ b/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Block Grouping Group creation creates a group from multiple blocks of different types via block transforms 1`] = ` +"<!-- wp:group --> +<div class=\\"wp-block-group\\"><!-- wp:heading --> +<h2>Group Heading</h2> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Some paragraph</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->" +`; + +exports[`Block Grouping Group creation creates a group from multiple blocks of the same type via block transforms 1`] = ` +"<!-- wp:group --> +<div class=\\"wp-block-group\\"><!-- wp:paragraph --> +<p>First Paragraph</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Second Paragraph</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Third Paragraph</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->" +`; + +exports[`Block Grouping Group creation creates a group from multiple blocks of the same type via options toolbar 1`] = ` +"<!-- wp:group --> +<div class=\\"wp-block-group\\"><!-- wp:paragraph --> +<p>First Paragraph</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Second Paragraph</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Third Paragraph</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->" +`; + +exports[`Block Grouping Group creation groups and ungroups multiple blocks of different types via options toolbar 1`] = ` +"<!-- wp:group --> +<div class=\\"wp-block-group\\"><!-- wp:heading --> +<h2>Group Heading</h2> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Some paragraph</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->" +`; + +exports[`Block Grouping Group creation groups and ungroups multiple blocks of different types via options toolbar 2`] = ` +"<!-- wp:heading --> +<h2>Group Heading</h2> +<!-- /wp:heading --> + +<!-- wp:image --> +<figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Some paragraph</p> +<!-- /wp:paragraph -->" +`; + +exports[`Block Grouping Preserving selected blocks attributes preserves width alignment settings of selected blocks 1`] = ` +"<!-- wp:group {\\"align\\":\\"full\\"} --> +<div class=\\"wp-block-group alignfull\\"><!-- wp:heading --> +<h2>Group Heading</h2> +<!-- /wp:heading --> + +<!-- wp:image {\\"align\\":\\"full\\"} --> +<figure class=\\"wp-block-image alignfull\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:image {\\"align\\":\\"wide\\"} --> +<figure class=\\"wp-block-image alignwide\\"><img alt=\\"\\"/></figure> +<!-- /wp:image --> + +<!-- wp:paragraph --> +<p>Some paragraph</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->" +`; diff --git a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap index ce9dfc0be7e555..25e50d75416a43 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap @@ -96,6 +96,14 @@ exports[`Block transforms correctly transform block Image in fixture core__image <!-- /wp:gallery -->" `; +exports[`Block transforms correctly transform block Image in fixture core__image into the Group block 1`] = ` +"<!-- wp:group --> +<div class=\\"wp-block-group\\"><!-- wp:image --> +<figure class=\\"wp-block-image\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure> +<!-- /wp:image --></div> +<!-- /wp:group -->" +`; + exports[`Block transforms correctly transform block Image in fixture core__image into the Media & Text block 1`] = ` "<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> <div class=\\"wp-block-media-text alignwide\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> @@ -352,6 +360,14 @@ exports[`Block transforms correctly transform block Media & Text in fixture core <!-- /wp:video -->" `; +exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Group block 1`] = ` +"<!-- wp:group --> +<div class=\\"wp-block-group\\"><!-- wp:paragraph {\\"align\\":\\"right\\"} --> +<p style=\\"text-align:right\\">... like this one, which is separate from the above and right aligned.</p> +<!-- /wp:paragraph --></div> +<!-- /wp:group -->" +`; + exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Heading block 1`] = ` "<!-- wp:heading --> <h2>... like this one, which is separate from the above and right aligned.</h2> diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index 194ff847328c18..02cc0a0f5720ee 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -8,6 +8,7 @@ import { createNewPost, isInDefaultBlock, pressKeyWithModifier, + pressKeyTimes, insertBlock, } from '@wordpress/e2e-test-utils'; @@ -22,10 +23,37 @@ const addThreeParagraphsToNewPost = async () => { await page.keyboard.press( 'Enter' ); }; -const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { +/** + * Due to an issue with the Popover component not being scrollable + * under certain conditions, Pupeteer cannot "see" the "Remove Block" + * button. This is a workaround until that issue is resolved. + * + * see: https://github.com/WordPress/gutenberg/pull/14908#discussion_r284725956 + */ +const clickOnBlockSettingsMenuRemoveBlockButton = async () => { await clickBlockToolbarButton( 'More options' ); - const itemButton = ( await page.$x( `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; - await itemButton.click(); + + let isRemoveButton = false; + + let numButtons = await page.$$eval( '.block-editor-block-toolbar button', ( btns ) => btns.length ); + + // Limit by the number of buttons available + while ( --numButtons ) { + await page.keyboard.press( 'Tab' ); + + isRemoveButton = await page.evaluate( () => { + return document.activeElement.innerText.includes( 'Remove Block' ); + } ); + + // Stop looping once we find the button + if ( isRemoveButton ) { + await pressKeyTimes( 'Enter', 1 ); + break; + } + } + + // Makes failures more explicit + await expect( isRemoveButton ).toBe( true ); }; describe( 'block deletion -', () => { @@ -39,7 +67,8 @@ describe( 'block deletion -', () => { // Press Escape to show the block toolbar await page.keyboard.press( 'Escape' ); - await clickOnBlockSettingsMenuItem( 'Remove Block' ); + await clickOnBlockSettingsMenuRemoveBlockButton(); + expect( await getEditedPostContent() ).toMatchSnapshot(); // Type additional text and assert that caret position is correct by comparing to snapshot. @@ -121,7 +150,7 @@ describe( 'deleting all blocks', () => { await page.keyboard.press( 'Escape' ); - await clickOnBlockSettingsMenuItem( 'Remove Block' ); + await clickOnBlockSettingsMenuRemoveBlockButton(); // There is a default block: expect( await page.$$( '.block-editor-block-list__block' ) ).toHaveLength( 1 ); diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/block-grouping.test.js new file mode 100644 index 00000000000000..57300ec2b9de66 --- /dev/null +++ b/packages/e2e-tests/specs/block-grouping.test.js @@ -0,0 +1,176 @@ +/** + * WordPress dependencies + */ +import { + insertBlock, + createNewPost, + clickBlockToolbarButton, + pressKeyWithModifier, + getEditedPostContent, + transformBlockTo, + getAllBlocks, + getAvailableBlockTransforms, +} from '@wordpress/e2e-test-utils'; + +async function insertBlocksOfSameType() { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'First Paragraph' ); + + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Second Paragraph' ); + + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Third Paragraph' ); +} + +async function insertBlocksOfMultipleTypes() { + await insertBlock( 'Heading' ); + await page.keyboard.type( 'Group Heading' ); + + await insertBlock( 'Image' ); + + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Some paragraph' ); +} + +describe( 'Block Grouping', () => { + beforeEach( async () => { + // Posts are auto-removed at the end of each test run + await createNewPost(); + } ); + + describe( 'Group creation', () => { + it( 'creates a group from multiple blocks of the same type via block transforms', async () => { + // Creating test blocks + await insertBlocksOfSameType(); + + // Multiselect via keyboard. + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await transformBlockTo( 'Group' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'creates a group from multiple blocks of different types via block transforms', async () => { + // Creating test blocks + await insertBlocksOfMultipleTypes(); + + // Multiselect via keyboard. + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await transformBlockTo( 'Group' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'creates a group from multiple blocks of the same type via options toolbar', async () => { + // Creating test blocks + await insertBlocksOfSameType(); + + // Multiselect via keyboard. + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await clickBlockToolbarButton( 'More options' ); + + const groupButton = await page.waitForXPath( '//button[text()="Group"]' ); + await groupButton.click(); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'groups and ungroups multiple blocks of different types via options toolbar', async () => { + // Creating test blocks + await insertBlocksOfMultipleTypes(); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + // Group + await clickBlockToolbarButton( 'More options' ); + const groupButton = await page.waitForXPath( '//button[text()="Group"]' ); + await groupButton.click(); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // UnGroup + await clickBlockToolbarButton( 'More options' ); + const unGroupButton = await page.waitForXPath( '//button[text()="Ungroup"]' ); + await unGroupButton.click(); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + } ); + + describe( 'Container Block availability', () => { + beforeEach( async () => { + // Disable the Group block + await page.evaluate( () => { + const { dispatch } = wp.data; + dispatch( 'core/edit-post' ).hideBlockTypes( [ 'core/group' ] ); + } ); + + // Create a Group + await insertBlocksOfMultipleTypes(); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + } ); + + afterAll( async () => { + // Re-enable the Group block + await page.evaluate( () => { + const { dispatch } = wp.data; + dispatch( 'core/edit-post' ).showBlockTypes( [ 'core/group' ] ); + } ); + } ); + + it( 'does not show group transform if container block is disabled', async () => { + const availableTransforms = await getAvailableBlockTransforms(); + + expect( + availableTransforms + ).not.toContain( 'Group' ); + } ); + + it( 'does not show group option in the options toolbar if container block is disabled ', async () => { + await clickBlockToolbarButton( 'More options' ); + + const blockOptionsDropdownHTML = await page.evaluate( () => document.querySelector( '.block-editor-block-settings-menu__content' ).innerHTML ); + + expect( blockOptionsDropdownHTML ).not.toContain( 'Group' ); + } ); + } ); + + describe( 'Preserving selected blocks attributes', () => { + it( 'preserves width alignment settings of selected blocks', async () => { + await insertBlock( 'Heading' ); + await page.keyboard.type( 'Group Heading' ); + + // Full width image + await insertBlock( 'Image' ); + await clickBlockToolbarButton( 'Full width' ); + + // Wide width image) + await insertBlock( 'Image' ); + await clickBlockToolbarButton( 'Wide width' ); + + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Some paragraph' ); + + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await transformBlockTo( 'Group' ); + + const allBlocks = await getAllBlocks(); + + // We expect Group block align setting to match that + // of the widest of it's "child" innerBlocks + expect( allBlocks[ 0 ].attributes.align ).toBe( 'full' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + } ); +} ); diff --git a/packages/e2e-tests/specs/block-switcher.test.js b/packages/e2e-tests/specs/block-switcher.test.js index 0d2fcf2adfe9e3..9282e83dc855f1 100644 --- a/packages/e2e-tests/specs/block-switcher.test.js +++ b/packages/e2e-tests/specs/block-switcher.test.js @@ -27,6 +27,7 @@ describe( 'adding blocks', () => { expect( await getAvailableBlockTransforms() ).toEqual( [ + 'Group', 'Paragraph', 'Quote', ] ); @@ -50,6 +51,7 @@ describe( 'adding blocks', () => { expect( await getAvailableBlockTransforms() ).toEqual( [ + 'Group', 'Paragraph', ] ); } ); @@ -60,6 +62,7 @@ describe( 'adding blocks', () => { ( [ 'core/quote', 'core/paragraph', + 'core/group', ] ).map( ( block ) => wp.blocks.unregisterBlockType( block ) ); } ); diff --git a/packages/e2e-tests/specs/block-transforms.test.js b/packages/e2e-tests/specs/block-transforms.test.js index a16a7a1ec10035..89d14bf524491f 100644 --- a/packages/e2e-tests/specs/block-transforms.test.js +++ b/packages/e2e-tests/specs/block-transforms.test.js @@ -153,7 +153,12 @@ describe( 'Block transforms', () => { ) ); - it.each( testTable )( + // As Group is available as a transform on *all* blocks this would create a lot of + // tests which would impact on the performance of the e2e test suite. + // To avoid this, we remove `core/group` from test table for all but 2 block types. + const testTableWithSomeGroupsFiltered = testTable.filter( ( transform ) => ( transform[ 2 ] !== 'Group' || transform[ 1 ] === 'core__paragraph__align-right' || transform[ 1 ] === 'core__image' ) ); + + it.each( testTableWithSomeGroupsFiltered )( 'block %s in fixture %s into the %s block', async ( originalBlock, fixture, destinationBlock ) => { const { content } = transformStructure[ fixture ]; diff --git a/packages/editor/src/components/convert-to-group-buttons/convert-button.js b/packages/editor/src/components/convert-to-group-buttons/convert-button.js new file mode 100644 index 00000000000000..6a7299ed259c98 --- /dev/null +++ b/packages/editor/src/components/convert-to-group-buttons/convert-button.js @@ -0,0 +1,127 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { MenuItem } from '@wordpress/components'; +import { _x } from '@wordpress/i18n'; +import { switchToBlockType } from '@wordpress/blocks'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { Group, Ungroup } from './icons'; + +export function ConvertToGroupButton( { + onConvertToGroup, + onConvertFromGroup, + isGroupable = false, + isUngroupable = false, +} ) { + return ( + <Fragment> + { isGroupable && ( + <MenuItem + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" + icon={ Group } + onClick={ onConvertToGroup } + > + { _x( 'Group', 'verb' ) } + </MenuItem> + ) } + { isUngroupable && ( + <MenuItem + className="editor-block-settings-menu__control block-editor-block-settings-menu__control" + icon={ Ungroup } + onClick={ onConvertFromGroup } + > + { _x( 'Ungroup', 'Ungrouping blocks from within a Group block back into individual blocks within the Editor ' ) } + </MenuItem> + ) } + </Fragment> + ); +} + +export default compose( [ + withSelect( ( select, { clientIds } ) => { + const { + getBlocksByClientId, + canInsertBlockType, + } = select( 'core/block-editor' ); + + const containerBlockAvailable = canInsertBlockType( 'core/group' ); + + const blocksSelection = getBlocksByClientId( clientIds ); + + const isSingleContainerBlock = blocksSelection.length === 1 && blocksSelection[ 0 ] && blocksSelection[ 0 ].name === 'core/group'; + + // Do we have + // 1. Container block available to be inserted? + // 2. One or more blocks selected + // (we allow single Blocks to become groups unless + // they are a soltiary group block themselves) + const isGroupable = ( + containerBlockAvailable && + blocksSelection.length && + ! isSingleContainerBlock + ); + + // Do we have a single Group Block selected? + const isUngroupable = isSingleContainerBlock; + + return { + isGroupable, + isUngroupable, + blocksSelection, + }; + } ), + withDispatch( ( dispatch, { clientIds, onToggle = noop, blocksSelection = [] } ) => { + const { + replaceBlocks, + } = dispatch( 'core/block-editor' ); + + return { + onConvertToGroup() { + if ( ! blocksSelection.length ) { + return; + } + + // Activate the `transform` on `core/group` which does the conversion + const newBlocks = switchToBlockType( blocksSelection, 'core/group' ); + + if ( newBlocks ) { + replaceBlocks( + clientIds, + newBlocks + ); + } + + onToggle(); + }, + onConvertFromGroup() { + if ( ! blocksSelection.length ) { + return; + } + + const innerBlocks = blocksSelection[ 0 ].innerBlocks; + + if ( ! innerBlocks.length ) { + return; + } + + replaceBlocks( + clientIds, + innerBlocks + ); + + onToggle(); + }, + }; + } ), +] )( ConvertToGroupButton ); diff --git a/packages/editor/src/components/convert-to-group-buttons/icons.js b/packages/editor/src/components/convert-to-group-buttons/icons.js new file mode 100644 index 00000000000000..8ca249c2fa7ee6 --- /dev/null +++ b/packages/editor/src/components/convert-to-group-buttons/icons.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { Icon, SVG, Path } from '@wordpress/components'; + +const GroupSVG = <SVG width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + <Path fillRule="evenodd" clipRule="evenodd" d="M10 4H18C19.1 4 20 4.9 20 6V14C20 15.1 19.1 16 18 16H10C8.9 16 8 15.1 8 14V6C8 4.9 8.9 4 10 4ZM10 14H18V6H10V14Z" /> + <Path d="M6 8C4.9 8 4 8.9 4 10V18C4 19.1 4.9 20 6 20H14C15.1 20 16 19.1 16 18H6V8Z" /> +</SVG>; + +export const Group = <Icon icon={ GroupSVG } />; + +const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> + <Path fillRule="evenodd" clipRule="evenodd" d="M12 2H20C21.1 2 22 2.9 22 4V12C22 13.1 21.1 14 20 14H12C10.9 14 10 13.1 10 12V4C10 2.9 10.9 2 12 2ZM12 12H20V4H12V12Z" /> + <Path d="M4 10H8V12H4V20H12V16H14V20C14 21.1 13.1 22 12 22H4C2.9 22 2 21.1 2 20V12C2 10.9 2.9 10 4 10Z" /> +</SVG>; + +export const Ungroup = <Icon icon={ UngroupSVG } />; + diff --git a/packages/editor/src/components/convert-to-group-buttons/index.js b/packages/editor/src/components/convert-to-group-buttons/index.js new file mode 100644 index 00000000000000..a276ed4434bcab --- /dev/null +++ b/packages/editor/src/components/convert-to-group-buttons/index.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { __experimentalBlockSettingsMenuPluginsExtension } from '@wordpress/block-editor'; +import { withSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import ConvertToGroupButton from './convert-button'; + +function ConvertToGroupButtons( { clientIds } ) { + return ( + <__experimentalBlockSettingsMenuPluginsExtension> + { ( { onClose } ) => ( + <Fragment> + <ConvertToGroupButton + clientIds={ clientIds } + onToggle={ onClose } + /> + </Fragment> + ) } + </__experimentalBlockSettingsMenuPluginsExtension> + ); +} + +export default withSelect( ( select ) => { + const { getSelectedBlockClientIds } = select( 'core/block-editor' ); + return { + clientIds: getSelectedBlockClientIds(), + }; +} )( ConvertToGroupButtons ); diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 601a44c0260cfe..e897931daf6c42 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -21,6 +21,7 @@ import { decodeEntities } from '@wordpress/html-entities'; */ import { mediaUpload } from '../../utils'; import ReusableBlocksButtons from '../reusable-blocks-buttons'; +import ConvertToGroupButtons from '../convert-to-group-buttons'; const fetchLinkSuggestions = async ( search ) => { const posts = await apiFetch( { @@ -159,6 +160,7 @@ class EditorProvider extends Component { > { children } <ReusableBlocksButtons /> + <ConvertToGroupButtons /> </BlockEditorProvider> ); } From 941c6ec085964ce1557f2def23cbf4a37b458037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 5 Jun 2019 13:07:15 +0200 Subject: [PATCH 259/664] Scripts: Add default file patterns for linting scripts (#15890) * Scripts: Add default file patterns for linting scripts * Add CHANGELOG entry --- package-lock.json | 123 ++++------------------ package.json | 10 +- packages/scripts/CHANGELOG.md | 3 + packages/scripts/README.md | 31 ++++-- packages/scripts/package.json | 1 + packages/scripts/scripts/lint-js.js | 7 +- packages/scripts/scripts/lint-pkg-json.js | 7 +- packages/scripts/scripts/lint-style.js | 7 +- packages/scripts/utils/cli.js | 4 + packages/scripts/utils/index.js | 4 +- 10 files changed, 75 insertions(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index f2ec975d616b57..8632bb2c43cde7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,12 +53,6 @@ "minimist": "^1.2.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -937,14 +931,6 @@ "requires": { "exec-sh": "^0.3.2", "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "@iarna/toml": { @@ -3825,6 +3811,7 @@ "eslint": "^5.16.0", "jest": "^24.7.1", "jest-puppeteer": "^4.0.0", + "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", @@ -5479,12 +5466,6 @@ "semver": "^5.0.3" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -5936,7 +5917,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -6827,12 +6809,6 @@ "trim-newlines": "^2.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -6941,12 +6917,6 @@ "trim-newlines": "^2.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -7058,12 +7028,6 @@ "trim-newlines": "^2.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -10224,7 +10188,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -10554,12 +10519,6 @@ "trim-newlines": "^1.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -10670,12 +10629,6 @@ "trim-newlines": "^2.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -10770,12 +10723,6 @@ "trim-newlines": "^2.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -14879,9 +14826,9 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, "minimist-options": { @@ -14985,6 +14932,14 @@ "dev": true, "requires": { "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } } }, "modify-values": { @@ -15444,12 +15399,6 @@ "mime-db": "1.40.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "nan": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", @@ -16100,6 +16049,12 @@ "wordwrap": "~0.0.2" }, "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -16759,12 +16714,6 @@ "minimist": "^1.2.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -17521,12 +17470,6 @@ "minimist": "^1.2.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -18331,14 +18274,6 @@ "buffer-equal": "0.0.1", "minimist": "^1.1.3", "through2": "^2.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "raf": { @@ -19496,12 +19431,6 @@ "pump": "^3.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -20616,14 +20545,6 @@ "duplexer": "^0.1.1", "minimist": "^1.2.0", "through": "^2.3.4" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, "style-search": { diff --git a/package.json b/package.json index 7b5d744f8d347c..790c5db1328d9c 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,18 @@ "name": "gutenberg", "version": "5.8.0", "private": true, - "description": "A new WordPress editor experience", - "repository": "git+https://github.com/WordPress/gutenberg.git", + "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", "keywords": [ "WordPress", "editor" ], + "homepage": "https://github.com/WordPress/gutenberg/", + "repository": "git+https://github.com/WordPress/gutenberg.git", + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, "config": { "GUTENBERG_PHASE": 2 }, @@ -182,7 +186,7 @@ "fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", "fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate", "lint": "concurrently \"npm run lint-js\" \"npm run lint-pkg-json\" \"npm run lint-css\"", - "lint-js": "wp-scripts lint-js .", + "lint-js": "wp-scripts lint-js", "lint-js:fix": "npm run lint-js -- --fix", "lint-php": "docker-compose run --rm composer run-script lint", "lint-pkg-json": "wp-scripts lint-pkg-json ./packages", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index e7e408e8b0166f..1183fa6bd736d4 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -2,6 +2,9 @@ ### New Features +- The `lint-js` command lints now JS files in the entire project's directories by default ([15890](https://github.com/WordPress/gutenberg/pull/15890)). +- The `lint-pkg-json` command lints now `package.json` files in the entire project's directories by default ([15890](https://github.com/WordPress/gutenberg/pull/15890)). +- The `lint-style` command lints now CSS and SCSS files in the entire project's directories by default ([15890](https://github.com/WordPress/gutenberg/pull/15890)). - The `lint-js`, `lint-pkg-json` and `lint-style` commands ignore now files located in `build` and `node_modules` folders by default ([15977](https://github.com/WordPress/gutenberg/pull/15977)). ## 3.2.0 (2019-05-21) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 0e394ad71f23a2..7ee723ce817047 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -25,10 +25,10 @@ _Example:_ "scripts": { "build": "wp-scripts build", "check-engines": "wp-scripts check-engines", - "check-licenses": "wp-scripts check-licenses --production", - "lint:css": "wp-scripts lint-style '**/*.css'", - "lint:js": "wp-scripts lint-js .", - "lint:pkg-json": "wp-scripts lint-pkg-json .", + "check-licenses": "wp-scripts check-licenses", + "lint:css": "wp-scripts lint-style", + "lint:js": "wp-scripts lint-js", + "lint:pkg-json": "wp-scripts lint-pkg-json", "start": "wp-scripts start", "test:e2e": "wp-scripts test-e2e", "test:unit": "wp-scripts test-unit-js" @@ -101,7 +101,7 @@ _Example:_ _Flags_: - `--prod` (or `--production`): When present, validates only `dependencies` and not `devDependencies` -- `--dev` (or `--development`): When present, validates both `dependencies` and `devDependencies` +- `--dev` (or `--development`): When present, validates only `devDependencies` and not `dependencies` - `--gpl2`: Validates against [GPLv2 license compatibility](https://www.gnu.org/licenses/license-list.en.html) - `--ignore=a,b,c`: A comma-separated set of package names to ignore for validation. This is intended to be used primarily in cases where a dependency's `license` field is malformed. It's assumed that any `ignored` package argument would be manually vetted for compatibility by the project owner. @@ -114,7 +114,8 @@ _Example:_ ```json { "scripts": { - "lint:js": "wp-scripts lint-js ." + "lint:js": "wp-scripts lint-js", + "lint:js:src": "wp-scripts lint-js ./src" } } ``` @@ -122,7 +123,9 @@ _Example:_ This is how you execute the script with presented setup: * `npm run lint:js` - lints JavaScript files in the entire project's directories. +* `npm run lint:js:src` - lints JavaScript files in the project's `src` subfolder's directories. +When you run commands similar to the `npm run lint:js:src` example above, you can provide a file, a directory, or `glob` syntax or any combination of them. See [more examples](https://eslint.org/docs/user-guide/command-line-interface). By default, files located in `build` and `node_modules` folders are ignored. @@ -139,14 +142,18 @@ _Example:_ ```json { "scripts": { - "lint:pkg-json": "wp-scripts lint-pkg-json ." + "lint:pkg-json": "wp-scripts lint-pkg-json", + "lint:pkg-json:src": "wp-scripts lint-pkg-json ./src" } } ``` This is how you execute those scripts using the presented setup: -* `npm run lint:pkg-json` - lints `package.json` file in the project's root folder. +* `npm run lint:pkg-json` - lints `package.json` file in the entire project's directories. +* `npm run lint:pkg-json:src` - lints `package.json` file in the project's `src` subfolder's directories. + +When you run commands similar to the `npm run lint:pkg-json:src` example above, you can provide one or multiple directories to scan as well. See [more examples](https://github.com/tclindner/npm-package-json-lint/blob/HEAD/README.md#examples). By default, files located in `build` and `node_modules` folders are ignored. @@ -163,14 +170,18 @@ _Example:_ ```json { "scripts": { - "lint:css": "wp-scripts lint-style '**/*.css'" + "lint:style": "wp-scripts lint-style", + "lint:css:src": "wp-scripts lint-style 'src/**/*.css'" } } ``` This is how you execute the script with presented setup: -* `npm run lint:css` - lints CSS files in the whole project's directory. +* `npm run lint:style` - lints CSS and SCSS files in the entire project's directories. +* `npm run lint:css:src` - lints only CSS files in the project's `src` subfolder's directories. + +When you run commands similar to the `npm run lint:css:src` example above, be sure to include the quotation marks around file globs. This ensures that you can use the powers of [globby](https://github.com/sindresorhus/globby) (like the `**` globstar) regardless of your shell. See [more examples](https://github.com/stylelint/stylelint/blob/HEAD/docs/user-guide/cli.md#examples). By default, files located in `build` and `node_modules` folders are ignored. diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 712b5edde2b3f5..c8d503efb886c6 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -44,6 +44,7 @@ "eslint": "^5.16.0", "jest": "^24.7.1", "jest-puppeteer": "^4.0.0", + "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", "puppeteer": "1.6.1", "read-pkg-up": "^1.0.1", diff --git a/packages/scripts/scripts/lint-js.js b/packages/scripts/scripts/lint-js.js index 33bcdf83e5f897..04d24cd538fcb2 100644 --- a/packages/scripts/scripts/lint-js.js +++ b/packages/scripts/scripts/lint-js.js @@ -11,12 +11,15 @@ const { fromConfigRoot, getCliArgs, hasCliArg, + hasFileInCliArgs, hasPackageProp, hasProjectFile, } = require( '../utils' ); const args = getCliArgs(); +const defaultFilesArgs = ! hasFileInCliArgs ? [ '.' ] : []; + // See: https://eslint.org/docs/user-guide/configuring#using-configuration-files-1. const hasLintConfig = hasCliArg( '-c' ) || hasCliArg( '--config' ) || @@ -30,7 +33,7 @@ const hasLintConfig = hasCliArg( '-c' ) || // When a configuration is not provided by the project, use from the default // provided with the scripts module. Instruct ESLint to avoid discovering via // the `--no-eslintrc` flag, as otherwise it will still merge with inherited. -const config = ! hasLintConfig ? +const defaultConfigArgs = ! hasLintConfig ? [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc.js' ) ] : []; @@ -44,7 +47,7 @@ const defaultIgnoreArgs = ! hasIgnoredFiles ? const result = spawn( resolveBin( 'eslint' ), - [ ...config, ...defaultIgnoreArgs, ...args ], + [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-pkg-json.js b/packages/scripts/scripts/lint-pkg-json.js index c3cbd4f429c680..3d15b2bddb1d07 100644 --- a/packages/scripts/scripts/lint-pkg-json.js +++ b/packages/scripts/scripts/lint-pkg-json.js @@ -11,12 +11,15 @@ const { fromConfigRoot, getCliArgs, hasCliArg, + hasFileInCliArgs, hasProjectFile, hasPackageProp, } = require( '../utils' ); const args = getCliArgs(); +const defaultFilesArgs = ! hasFileInCliArgs ? [ '.' ] : []; + // See: https://github.com/tclindner/npm-package-json-lint/wiki/configuration#configuration. const hasLintConfig = hasCliArg( '-c' ) || hasCliArg( '--configFile' ) || @@ -24,7 +27,7 @@ const hasLintConfig = hasCliArg( '-c' ) || hasProjectFile( 'npmpackagejsonlint.config.js' ) || hasPackageProp( 'npmPackageJsonLintConfig' ); -const config = ! hasLintConfig ? +const defaultConfigArgs = ! hasLintConfig ? [ '--configFile', fromConfigRoot( 'npmpackagejsonlint.json' ) ] : []; @@ -38,7 +41,7 @@ const defaultIgnoreArgs = ! hasIgnoredFiles ? const result = spawn( resolveBin( 'npm-package-json-lint', { executable: 'npmPkgJsonLint' } ), - [ ...config, ...defaultIgnoreArgs, ...args ], + [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, defaultFilesArgs ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js index 32783a97f4eb2b..024e04983ca8b1 100644 --- a/packages/scripts/scripts/lint-style.js +++ b/packages/scripts/scripts/lint-style.js @@ -11,12 +11,15 @@ const { fromConfigRoot, getCliArgs, hasCliArg, + hasFileInCliArgs, hasProjectFile, hasPackageProp, } = require( '../utils' ); const args = getCliArgs(); +const defaultFilesArgs = ! hasFileInCliArgs ? [ '**/*.{css,scss}' ] : []; + // See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#loading-the-configuration-object. const hasLintConfig = hasCliArg( '--config' ) || hasProjectFile( '.stylelintrc' ) || @@ -27,7 +30,7 @@ const hasLintConfig = hasCliArg( '--config' ) || hasProjectFile( '.stylelint.config.js' ) || hasPackageProp( 'stylelint' ); -const config = ! hasLintConfig ? +const defaultConfigArgs = ! hasLintConfig ? [ '--config', fromConfigRoot( '.stylelintrc.json' ) ] : []; @@ -41,7 +44,7 @@ const defaultIgnoreArgs = ! hasIgnoredFiles ? const result = spawn( resolveBin( 'stylelint' ), - [ ...config, ...defaultIgnoreArgs, ...args ], + [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], { stdio: 'inherit' } ); diff --git a/packages/scripts/utils/cli.js b/packages/scripts/utils/cli.js index 3778379ced1920..db4ce5fcef2d49 100644 --- a/packages/scripts/utils/cli.js +++ b/packages/scripts/utils/cli.js @@ -1,6 +1,7 @@ /** * External dependencies */ +const minimist = require( 'minimist' ); const spawn = require( 'cross-spawn' ); /** @@ -26,6 +27,8 @@ const getCliArg = ( arg ) => { const hasCliArg = ( arg ) => getCliArg( arg ) !== undefined; +const hasFileInCliArgs = () => minimist( getCliArgs() )._.length > 0; + const handleSignal = ( signal ) => { if ( signal === 'SIGKILL' ) { // eslint-disable-next-line no-console @@ -81,5 +84,6 @@ module.exports = { getCliArg, getCliArgs, hasCliArg, + hasFileInCliArgs, spawnScript, }; diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index aebbcdca718007..2912c9aee22bbb 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -5,8 +5,8 @@ const { getCliArg, getCliArgs, hasCliArg, + hasFileInCliArgs, spawnScript, - cleanUpArgs, } = require( './cli' ); const { getWebpackArgs, @@ -32,9 +32,9 @@ module.exports = { getWebpackArgs, hasBabelConfig, hasCliArg, + hasFileInCliArgs, hasJestConfig, hasPackageProp, hasProjectFile, spawnScript, - cleanUpArgs, }; From 0f2b94896f8113fc1a4b3310355c45d931e20dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Wed, 5 Jun 2019 13:06:01 +0100 Subject: [PATCH 260/664] Style quotes in the mobile app (#15990) * Add spacer between BlockQuotation elements. * Use citation identifier to style rich-text * Remove margin on quote bar. --- .../src/components/rich-text/index.native.js | 2 +- .../src/primitives/block-quotation/index.native.js | 12 +++++++++++- .../src/primitives/block-quotation/style.native.scss | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index a89f293ff8efba..5adcbd23054122 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -862,7 +862,7 @@ export class RichText extends Component { color={ 'black' } maxImagesWidth={ 200 } fontFamily={ this.props.fontFamily || styles[ 'block-editor-rich-text' ].fontFamily } - fontSize={ this.props.fontSize } + fontSize={ this.props.fontSize || ( style && style.fontSize ) } fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } disableEditingMenu={ this.props.disableEditingMenu } diff --git a/packages/components/src/primitives/block-quotation/index.native.js b/packages/components/src/primitives/block-quotation/index.native.js index eea86435b5ebcd..dbcc249fe1bff5 100644 --- a/packages/components/src/primitives/block-quotation/index.native.js +++ b/packages/components/src/primitives/block-quotation/index.native.js @@ -2,15 +2,25 @@ * External dependencies */ import { View } from 'react-native'; +/** + * WordPress dependencies + */ +import { Children, cloneElement } from '@wordpress/element'; /** * Internal dependencies */ import styles from './style.scss'; export const BlockQuotation = ( props ) => { + const newChildren = Children.map( props.children, ( child ) => { + if ( child && child.props.identifier === 'citation' ) { + return cloneElement( child, { style: styles.wpBlockQuoteCitation } ); + } + return child; + } ); return ( <View style={ styles.wpBlockQuote } > - { props.children } + { newChildren } </View> ); }; diff --git a/packages/components/src/primitives/block-quotation/style.native.scss b/packages/components/src/primitives/block-quotation/style.native.scss index 02d315e1560b6b..2e88c68cf26fa9 100644 --- a/packages/components/src/primitives/block-quotation/style.native.scss +++ b/packages/components/src/primitives/block-quotation/style.native.scss @@ -3,5 +3,10 @@ border-left-color: $black; border-left-style: solid; padding-left: 8px; - margin-left: 8px; + margin-left: 0; +} + +.wpBlockQuoteCitation { + margin-top: 16px; + font-size: 14px; } From 788a3d2ea7d5490f149bf5ebe191cbbcb140c6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Wed, 5 Jun 2019 13:51:18 +0100 Subject: [PATCH 261/664] Refactor remove line separator to method in the rich-text package. (#15946) * Refactor remove line separator to method in the rich-text package. * Address review comments. --- .../src/components/rich-text/index.js | 56 +------------------ .../src/components/rich-text/index.native.js | 55 +----------------- packages/rich-text/src/index.js | 1 + .../rich-text/src/remove-line-separator.js | 56 +++++++++++++++++++ 4 files changed, 63 insertions(+), 105 deletions(-) create mode 100644 packages/rich-text/src/remove-line-separator.js diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 0b03bcca6a490d..40f758f064e081 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -30,6 +30,7 @@ import { getTextContent, insert, __unstableInsertLineSeparator as insertLineSeparator, + __unstableRemoveLineSeparator as removeLineSeparator, __unstableIsEmptyLine as isEmptyLine, __unstableToDom as toDom, remove, @@ -639,7 +640,7 @@ export class RichText extends Component { if ( keyCode === DELETE || keyCode === BACKSPACE ) { const value = this.createRecord(); - const { replacements, text, start, end } = value; + const { start, end } = value; // Always handle full content deletion ourselves. if ( start === 0 && end !== 0 && end === value.text.length ) { @@ -649,58 +650,7 @@ export class RichText extends Component { } if ( this.multilineTag ) { - let newValue; - - if ( keyCode === BACKSPACE ) { - const index = start - 1; - - if ( text[ index ] === LINE_SEPARATOR ) { - const collapsed = isCollapsed( value ); - - // If the line separator that is about te be removed - // contains wrappers, remove the wrappers first. - if ( collapsed && replacements[ index ] && replacements[ index ].length ) { - const newReplacements = replacements.slice(); - - newReplacements[ index ] = replacements[ index ].slice( 0, -1 ); - newValue = { - ...value, - replacements: newReplacements, - }; - } else { - newValue = remove( - value, - // Only remove the line if the selection is - // collapsed, otherwise remove the selection. - collapsed ? start - 1 : start, - end - ); - } - } - } else if ( text[ end ] === LINE_SEPARATOR ) { - const collapsed = isCollapsed( value ); - - // If the line separator that is about te be removed - // contains wrappers, remove the wrappers first. - if ( collapsed && replacements[ end ] && replacements[ end ].length ) { - const newReplacements = replacements.slice(); - - newReplacements[ end ] = replacements[ end ].slice( 0, -1 ); - newValue = { - ...value, - replacements: newReplacements, - }; - } else { - newValue = remove( - value, - start, - // Only remove the line if the selection is - // collapsed, otherwise remove the selection. - collapsed ? end + 1 : end, - ); - } - } - + const newValue = removeLineSeparator( value, keyCode === BACKSPACE ); if ( newValue ) { this.onChange( newValue ); event.preventDefault(); diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 5adcbd23054122..92143f16b10697 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -26,9 +26,9 @@ import { split, toHTMLString, insert, - __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR, __unstableInsertLineSeparator as insertLineSeparator, __unstableIsEmptyLine as isEmptyLine, + __unstableRemoveLineSeparator as removeLineSeparator, isCollapsed, remove, } from '@wordpress/rich-text'; @@ -386,7 +386,7 @@ export class RichText extends Component { this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; const value = this.createRecord(); - const { replacements, text, start, end } = value; + const { start, end } = value; let newValue; // Always handle full content deletion ourselves. @@ -398,56 +398,7 @@ export class RichText extends Component { } if ( this.multilineTag ) { - if ( keyCode === BACKSPACE ) { - const index = start - 1; - - if ( text[ index ] === LINE_SEPARATOR ) { - const collapsed = isCollapsed( value ); - - // If the line separator that is about te be removed - // contains wrappers, remove the wrappers first. - if ( collapsed && replacements[ index ] && replacements[ index ].length ) { - const newReplacements = replacements.slice(); - - newReplacements[ index ] = replacements[ index ].slice( 0, -1 ); - newValue = { - ...value, - replacements: newReplacements, - }; - } else { - newValue = remove( - value, - // Only remove the line if the selection is - // collapsed, otherwise remove the selection. - collapsed ? start - 1 : start, - end - ); - } - } - } else if ( text[ end ] === LINE_SEPARATOR ) { - const collapsed = isCollapsed( value ); - - // If the line separator that is about te be removed - // contains wrappers, remove the wrappers first. - if ( collapsed && replacements[ end ] && replacements[ end ].length ) { - const newReplacements = replacements.slice(); - - newReplacements[ end ] = replacements[ end ].slice( 0, -1 ); - newValue = { - ...value, - replacements: newReplacements, - }; - } else { - newValue = remove( - value, - start, - // Only remove the line if the selection is - // collapsed, otherwise remove the selection. - collapsed ? end + 1 : end, - ); - } - } - + newValue = removeLineSeparator( value, keyCode === BACKSPACE ); if ( newValue ) { this.onFormatChange( newValue ); return; diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 6dbfe46b6d93b4..8f57a21e4ddf34 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -20,6 +20,7 @@ export { remove } from './remove'; export { replace } from './replace'; export { insert } from './insert'; export { insertLineSeparator as __unstableInsertLineSeparator } from './insert-line-separator'; +export { removeLineSeparator as __unstableRemoveLineSeparator } from './remove-line-separator'; export { insertObject } from './insert-object'; export { slice } from './slice'; export { split } from './split'; diff --git a/packages/rich-text/src/remove-line-separator.js b/packages/rich-text/src/remove-line-separator.js new file mode 100644 index 00000000000000..f9e6dfb157952e --- /dev/null +++ b/packages/rich-text/src/remove-line-separator.js @@ -0,0 +1,56 @@ +/** + * Internal dependencies + */ + +import { LINE_SEPARATOR } from './special-characters'; +import { isCollapsed } from './is-collapsed'; +import { remove } from './remove'; + +/** + * Removes a line separator character, if existing, from a Rich Text value at the current + * indices. If no line separator exists on the indices it will return undefined. + * + * @param {Object} value Value to modify. + * @param {boolean} backward indicates if are removing from the start index or the end index. + * + * @return {Object|undefined} A new value with the line separator removed. Or undefined if no line separator is found on the position. + */ +export function removeLineSeparator( + value, + backward = true, +) { + const { replacements, text, start, end } = value; + const collapsed = isCollapsed( value ); + let index = start - 1; + let removeStart = collapsed ? start - 1 : start; + let removeEnd = end; + if ( ! backward ) { + index = end; + removeStart = start; + removeEnd = collapsed ? end + 1 : end; + } + + if ( text[ index ] !== LINE_SEPARATOR ) { + return; + } + + let newValue; + // If the line separator that is about te be removed + // contains wrappers, remove the wrappers first. + if ( collapsed && replacements[ index ] && replacements[ index ].length ) { + const newReplacements = replacements.slice(); + + newReplacements[ index ] = replacements[ index ].slice( 0, -1 ); + newValue = { + ...value, + replacements: newReplacements, + }; + } else { + newValue = remove( + value, + removeStart, + removeEnd + ); + } + return newValue; +} From 01db6008fdf7b4c4e77647bb31dac8ca94a12704 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 5 Jun 2019 16:56:08 +0100 Subject: [PATCH 262/664] Extract ServerSideRender component to an independent package (#15635) --- docs/manifest-devhub.json | 12 ++--- lib/client-assets.php | 15 ++++++ package-lock.json | 14 +++++- package.json | 1 + packages/block-library/package.json | 1 + packages/block-library/src/archives/edit.js | 2 +- .../block-library/src/latest-comments/edit.js | 2 +- .../src/legacy-widget/edit/index.js | 2 +- packages/components/CHANGELOG.md | 4 ++ packages/components/package.json | 1 - packages/components/src/index.js | 1 - packages/editor/CHANGELOG.md | 3 ++ packages/editor/src/components/deprecated.js | 1 + packages/editor/src/components/index.js | 1 - .../components/server-side-render/README.md | 3 -- .../components/server-side-render/index.js | 18 -------- packages/server-side-render/.npmrc | 1 + packages/server-side-render/CHANGELOG.md | 5 ++ .../src => }/server-side-render/README.md | 19 +++++++- packages/server-side-render/package.json | 37 +++++++++++++++ packages/server-side-render/src/index.js | 46 +++++++++++++++++++ .../src/server-side-render.js} | 10 ++-- .../src}/test/index.js | 2 +- webpack.config.js | 1 + 24 files changed, 160 insertions(+), 42 deletions(-) delete mode 100644 packages/editor/src/components/server-side-render/README.md delete mode 100644 packages/editor/src/components/server-side-render/index.js create mode 100644 packages/server-side-render/.npmrc create mode 100644 packages/server-side-render/CHANGELOG.md rename packages/{components/src => }/server-side-render/README.md (76%) create mode 100644 packages/server-side-render/package.json create mode 100644 packages/server-side-render/src/index.js rename packages/{components/src/server-side-render/index.js => server-side-render/src/server-side-render.js} (96%) rename packages/{components/src/server-side-render => server-side-render/src}/test/index.js (97%) diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 14b29c43ddb989..94f21727035536 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -899,12 +899,6 @@ "markdown_source": "../packages/components/src/select-control/README.md", "parent": "components" }, - { - "title": "ServerSideRender", - "slug": "server-side-render", - "markdown_source": "../packages/components/src/server-side-render/README.md", - "parent": "components" - }, { "title": "SlotFill", "slug": "slot-fill", @@ -1361,6 +1355,12 @@ "markdown_source": "../packages/scripts/README.md", "parent": "packages" }, + { + "title": "@wordpress/server-side-render", + "slug": "packages-server-side-render", + "markdown_source": "../packages/server-side-render/README.md", + "parent": "packages" + }, { "title": "@wordpress/shortcode", "slug": "packages-shortcode", diff --git a/lib/client-assets.php b/lib/client-assets.php index 4611cadab1172a..5e4d2651bb603d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -289,6 +289,21 @@ function gutenberg_register_scripts_and_styles() { ) ); + // Add back compatibility for calls to wp.components.ServerSideRender. + wp_add_inline_script( + 'wp-server-side-render', + implode( + "\n", + array( + '( function() {', + ' if ( wp && wp.components && wp.serverSideRender && ! wp.components.ServerSideRender ) {', + ' wp.components.ServerSideRender = wp.serverSideRender;', + ' };', + '} )();', + ) + ) + ); + // Editor Styles. // This empty stylesheet is defined to ensure backward compatibility. gutenberg_override_style( 'wp-blocks', false ); diff --git a/package-lock.json b/package-lock.json index 8632bb2c43cde7..e21b8504db5a29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3309,6 +3309,7 @@ "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/server-side-render": "file:packages/server-side-render", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", "fast-average-color": "4.3.0", @@ -3363,7 +3364,6 @@ "requires": { "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:packages/a11y", - "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/compose": "file:packages/compose", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", @@ -3826,6 +3826,18 @@ "webpack-livereload-plugin": "^2.2.0" } }, + "@wordpress/server-side-render": { + "version": "file:packages/server-side-render", + "requires": { + "@babel/runtime": "^7.4.4", + "@wordpress/components": "file:packages/components", + "@wordpress/data": "file:packages/data", + "@wordpress/element": "file:packages/element", + "@wordpress/i18n": "file:packages/i18n", + "@wordpress/url": "file:packages/url", + "lodash": "^4.17.11" + } + }, "@wordpress/shortcode": { "version": "file:packages/shortcode", "requires": { diff --git a/package.json b/package.json index 790c5db1328d9c..62de2dba7741d3 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@wordpress/priority-queue": "file:packages/priority-queue", "@wordpress/redux-routine": "file:packages/redux-routine", "@wordpress/rich-text": "file:packages/rich-text", + "@wordpress/server-side-render": "file:packages/server-side-render", "@wordpress/shortcode": "file:packages/shortcode", "@wordpress/token-list": "file:packages/token-list", "@wordpress/url": "file:packages/url", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 4465a8dfefc496..daa427cffd4a94 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -36,6 +36,7 @@ "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/server-side-render": "file:../server-side-render", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", "fast-average-color": "4.3.0", diff --git a/packages/block-library/src/archives/edit.js b/packages/block-library/src/archives/edit.js index c67cdc912e5260..6909b70ccc4271 100644 --- a/packages/block-library/src/archives/edit.js +++ b/packages/block-library/src/archives/edit.js @@ -8,7 +8,7 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; -import { ServerSideRender } from '@wordpress/editor'; +import ServerSideRender from '@wordpress/server-side-render'; export default function ArchivesEdit( { attributes, setAttributes } ) { const { showPostCounts, displayAsDropdown } = attributes; diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index 61e7ec3e3aaabb..029778e24f2c59 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -8,7 +8,7 @@ import { RangeControl, ToggleControl, } from '@wordpress/components'; -import { ServerSideRender } from '@wordpress/editor'; +import ServerSideRender from '@wordpress/server-side-render'; import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index ac38184d22989b..31f36c2f7fac94 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -14,7 +14,7 @@ import { BlockControls, InspectorControls, } from '@wordpress/block-editor'; -import { ServerSideRender } from '@wordpress/editor'; +import ServerSideRender from '@wordpress/server-side-render'; /** * Internal dependencies diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index e6db0bcd0c8ff4..5fc3cba4b28b46 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -13,6 +13,10 @@ - Added missing documentation for `DropdownMenu` props `menuLabel`, `position`, `className`. +### Breaking Change + +- `ServerSideRender` is no longer part of components. It was extracted to an independent package `@wordpress/server-side-render`. + ## 7.4.0 (2019-05-21) diff --git a/packages/components/package.json b/packages/components/package.json index d2aa5957d36a83..df7d8afa76457f 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -23,7 +23,6 @@ "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:../a11y", - "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/compose": "file:../compose", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", diff --git a/packages/components/src/index.js b/packages/components/src/index.js index ab0540fa2f232f..6395ea3d843a42 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -53,7 +53,6 @@ export { default as SelectControl } from './select-control'; export { default as Snackbar } from './snackbar'; export { default as SnackbarList } from './snackbar/list'; export { default as Spinner } from './spinner'; -export { default as ServerSideRender } from './server-side-render'; export { default as TabPanel } from './tab-panel'; export { default as TextControl } from './text-control'; export { default as TextareaControl } from './textarea-control'; diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 3840d850ed599a..921a2b53fdb4a1 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -3,6 +3,9 @@ ### Deprecations - The `getAutosave`, `getAutosaveAttribute`, and `hasAutosave` selectors are deprecated. Please use the `getAutosave` selector in the `@wordpress/core-data` package. - The `resetAutosave` action is deprecated. An equivalent action `receiveAutosaves` has been added to the `@wordpress/core-data` package. +- `ServerSideRender` component was deprecated. The component is now available in `@wordpress/server-side-render`. + + ### Internal diff --git a/packages/editor/src/components/deprecated.js b/packages/editor/src/components/deprecated.js index 8468f254c2dccf..330116fabec58e 100644 --- a/packages/editor/src/components/deprecated.js +++ b/packages/editor/src/components/deprecated.js @@ -117,3 +117,4 @@ export { withColors, withFontSizes, }; +export { default as ServerSideRender } from '@wordpress/server-side-render'; diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 9d12344c50dc2a..f8999c7868042f 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -1,6 +1,5 @@ // Block Creation Components export * from './autocompleters'; -export { default as ServerSideRender } from './server-side-render'; // Post Related Components export { default as AutosaveMonitor } from './autosave-monitor'; diff --git a/packages/editor/src/components/server-side-render/README.md b/packages/editor/src/components/server-side-render/README.md deleted file mode 100644 index 3ef53b188e644f..00000000000000 --- a/packages/editor/src/components/server-side-render/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# ServerSideRender - -This utility component is a wrapper for the generic ServerSideRender in `@wordpress/components`. It adds the `post_id` parameter to the `urlQueryArgs` prop of the wrapped component. Use this component to ensure that the global `$post` object is set up properly in the server-side `render_callback` when rendering within the editor. diff --git a/packages/editor/src/components/server-side-render/index.js b/packages/editor/src/components/server-side-render/index.js deleted file mode 100644 index 5830612ebcc36b..00000000000000 --- a/packages/editor/src/components/server-side-render/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * WordPress dependencies - */ -import { ServerSideRender } from '@wordpress/components'; -import { select } from '@wordpress/data'; - -export default function( { urlQueryArgs = {}, ...props } ) { - const { getCurrentPostId } = select( 'core/editor' ); - - urlQueryArgs = { - post_id: getCurrentPostId(), - ...urlQueryArgs, - }; - - return ( - <ServerSideRender urlQueryArgs={ urlQueryArgs } { ...props } /> - ); -} diff --git a/packages/server-side-render/.npmrc b/packages/server-side-render/.npmrc new file mode 100644 index 00000000000000..43c97e719a5a82 --- /dev/null +++ b/packages/server-side-render/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/server-side-render/CHANGELOG.md b/packages/server-side-render/CHANGELOG.md new file mode 100644 index 00000000000000..04ea9fe434a6fb --- /dev/null +++ b/packages/server-side-render/CHANGELOG.md @@ -0,0 +1,5 @@ +## Unreleased + +### Enhancements + +- Extracted the package from `@wordpress/components` and `@wordpress/editor`; diff --git a/packages/components/src/server-side-render/README.md b/packages/server-side-render/README.md similarity index 76% rename from packages/components/src/server-side-render/README.md rename to packages/server-side-render/README.md index df9d75dec8fa4f..a975cd36f19d10 100644 --- a/packages/components/src/server-side-render/README.md +++ b/packages/server-side-render/README.md @@ -8,6 +8,23 @@ ServerSideRender should be regarded as a fallback or legacy mechanism, it is not New blocks should be built in conjunction with any necessary REST API endpoints, so that JavaScript can be used for rendering client-side in the `edit` function. This gives the best user experience, instead of relying on using the PHP `render_callback`. The logic necessary for rendering should be included in the endpoint, so that both the client-side JavaScript and server-side PHP logic should require a minimal amount of differences. +> This package is meant to be used only with WordPress core. Feel free to use it in your own project but please keep in mind that it might never get fully documented. + +## Installation + +Install the module + +```bash +npm install @wordpress/server-side-render --save +``` + +_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ + +## Usage + +The props accepted by the component are described below. + + ## Props ### attributes @@ -43,7 +60,7 @@ E.g: `{ post_id: 12 }`. Render core/archives preview. ```jsx -import { ServerSideRender } from '@wordpress/components'; +import { ServerSideRender } from '@wordpress/server-side-render'; const MyServerSideRender = () => ( <ServerSideRender diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json new file mode 100644 index 00000000000000..1f81f0be515781 --- /dev/null +++ b/packages/server-side-render/package.json @@ -0,0 +1,37 @@ +{ + "name": "@wordpress/server-side-render", + "version": "1.0.0-alpha.1", + "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "wordpress", + "server-side", + "render" + ], + "homepage": "https://github.com/WordPress/gutenberg/tree/master/packages/server-side-render/README.md", + "repository": { + "type": "git", + "url": "https://github.com/WordPress/gutenberg.git", + "directory": "packages/server-side-render" + }, + "bugs": { + "url": "https://github.com/WordPress/gutenberg/issues" + }, + "main": "build/index.js", + "module": "build-module/index.js", + "react-native": "src/index", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@wordpress/api-fetch": "file:../api-fetch", + "@wordpress/components": "file:../components", + "@wordpress/data": "file:../data", + "@wordpress/element": "file:../element", + "@wordpress/i18n": "file:../i18n", + "@wordpress/url": "file:../url", + "lodash": "^4.17.11" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/server-side-render/src/index.js b/packages/server-side-render/src/index.js new file mode 100644 index 00000000000000..3958ee46941366 --- /dev/null +++ b/packages/server-side-render/src/index.js @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import ServerSideRender from './server-side-render'; + +/** + * Constants + */ +const EMPTY_OBJECT = {}; + +export default withSelect( + ( select ) => { + const coreEditorSelect = select( 'core/editor' ); + if ( coreEditorSelect ) { + const currentPostId = coreEditorSelect.getCurrentPostId(); + if ( currentPostId ) { + return { + currentPostId, + }; + } + } + return EMPTY_OBJECT; + } +)( + ( { urlQueryArgs = EMPTY_OBJECT, currentPostId, ...props } ) => { + const newUrlQueryArgs = useMemo( () => { + if ( ! currentPostId ) { + return urlQueryArgs; + } + return { + post_id: currentPostId, + ...urlQueryArgs, + }; + }, [ currentPostId, urlQueryArgs ] ); + + return ( + <ServerSideRender urlQueryArgs={ newUrlQueryArgs } { ...props } /> + ); + } +); diff --git a/packages/components/src/server-side-render/index.js b/packages/server-side-render/src/server-side-render.js similarity index 96% rename from packages/components/src/server-side-render/index.js rename to packages/server-side-render/src/server-side-render.js index d58564d2cd82eb..a0c6a915efae55 100644 --- a/packages/components/src/server-side-render/index.js +++ b/packages/server-side-render/src/server-side-render.js @@ -13,12 +13,10 @@ import { import { __, sprintf } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; - -/** - * Internal dependencies - */ -import Placeholder from '../placeholder'; -import Spinner from '../spinner'; +import { + Placeholder, + Spinner, +} from '@wordpress/components'; export function rendererPath( block, attributes = null, urlQueryArgs = {} ) { return addQueryArgs( `/wp/v2/block-renderer/${ block }`, { diff --git a/packages/components/src/server-side-render/test/index.js b/packages/server-side-render/src/test/index.js similarity index 97% rename from packages/components/src/server-side-render/test/index.js rename to packages/server-side-render/src/test/index.js index c09814771f7a75..b97a80aa91b2b8 100644 --- a/packages/components/src/server-side-render/test/index.js +++ b/packages/server-side-render/src/test/index.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { rendererPath } from '../index'; +import { rendererPath } from '../server-side-render'; describe( 'rendererPath', function() { test( 'should return an base path for empty input', function() { diff --git a/webpack.config.js b/webpack.config.js index 0c0748fb684062..8a8348bb494d52 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -88,6 +88,7 @@ module.exports = { 'dom-ready', 'redux-routine', 'token-list', + 'server-side-render', 'shortcode', ].map( camelCaseDash ) ), new CopyWebpackPlugin( From 4c2eb54ca36d87d702b566d26e1a4020471a231a Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Wed, 5 Jun 2019 12:53:05 -0400 Subject: [PATCH 263/664] Video block has black background when upload is in progress or upload has failed (#15991) * Fixed issue with a black background when the upload is in progress or upload has failed --- .../block-library/src/video/edit.native.js | 18 ++++++++++-------- .../block-library/src/video/style.ios.scss | 8 -------- .../block-library/src/video/style.native.scss | 5 +++++ 3 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 packages/block-library/src/video/style.ios.scss diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 17b6c416f016d9..c37302902e1f0c 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -212,16 +212,18 @@ class VideoEdit extends React.Component { return ( <View onLayout={ this.onVideoContanerLayout } style={ containerStyle }> { showVideo && isURL( src ) && - <Video - isSelected={ isSelected } - style={ videoStyle } - source={ { uri: src } } - paused={ true } - muted={ true } - /> + <View style={ style.videoContainer }> + <Video + isSelected={ isSelected } + style={ videoStyle } + source={ { uri: src } } + paused={ true } + muted={ true } + /> + </View> } { ! showVideo && - <View style={ { ...videoStyle, ...style.placeholder } }> + <View style={ { height: videoContainerHeight, width: '100%', ...style.placeholder } }> { videoContainerHeight > 0 && iconContainer } { isUploadFailed && <Text style={ style.uploadFailedText }>{ retryMessage }</Text> } </View> diff --git a/packages/block-library/src/video/style.ios.scss b/packages/block-library/src/video/style.ios.scss deleted file mode 100644 index d13a8ad1801291..00000000000000 --- a/packages/block-library/src/video/style.ios.scss +++ /dev/null @@ -1,8 +0,0 @@ -// @format - -@import "./style.native.scss"; - -.video { - background-color: #000; -} - diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss index 9825ae09babe83..85e82cc59b3678 100644 --- a/packages/block-library/src/video/style.native.scss +++ b/packages/block-library/src/video/style.native.scss @@ -4,6 +4,11 @@ width: 100%; } +.videoContainer { + flex: 1; + background-color: #000; +} + .placeholder { flex: 1; justify-content: center; From 3c0b12be9090975421cdaa68dc03e15c4913721f Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Wed, 5 Jun 2019 22:23:43 +0300 Subject: [PATCH 264/664] Allow passing style to `Icon` for mobile (#15778) * Allow passing style to `Icon` for mobile * Use the Icon component in image and video blocks to style our svgs * Update style as additionalProps * Fix syntax * Revert README * Spread additionalProps * Components: Icon: Update documentation for props pass-through * Compoonents: Icon: Update unit tests for props pass-through * Components: Icon: Remove redundant explicit className handling * Components: Icon: Apply additionalProps consistently as last of props There should be no effective difference with `size` being an explicitly named prop not overridable via additionalProps --- .../block-library/src/image/edit.native.js | 5 +- .../block-library/src/image/icon-retry.js | 10 --- .../src/image/icon-retry.native.js | 6 ++ packages/block-library/src/image/icon.js | 6 +- .../src/image/styles.native.scss | 4 + .../block-library/src/video/edit.native.js | 5 +- .../block-library/src/video/icon-retry.js | 10 --- .../src/video/icon-retry.native.js | 7 ++ packages/block-library/src/video/icon.js | 5 +- .../block-library/src/video/style.native.scss | 4 + packages/components/src/icon/README.md | 10 +-- packages/components/src/icon/index.js | 12 +-- packages/components/src/icon/test/index.js | 76 +++++++------------ 13 files changed, 64 insertions(+), 96 deletions(-) delete mode 100644 packages/block-library/src/image/icon-retry.js create mode 100644 packages/block-library/src/image/icon-retry.native.js delete mode 100644 packages/block-library/src/video/icon-retry.js create mode 100644 packages/block-library/src/video/icon-retry.native.js diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 0869ca4fd2a52e..479f82a2df7754 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -16,6 +16,7 @@ import { isEmpty } from 'lodash'; */ import { BottomSheet, + Icon, Toolbar, ToolbarButton, } from '@wordpress/components'; @@ -193,10 +194,10 @@ class ImageEdit extends React.Component { getIcon( isRetryIcon ) { if ( isRetryIcon ) { - return <SvgIconRetry fill={ styles.iconRetry.fill } />; + return <Icon icon={ SvgIconRetry } { ...styles.iconRetry } />; } - return <SvgIcon fill={ styles.icon.fill } />; + return <Icon icon={ SvgIcon } { ...styles.icon } />; } render() { diff --git a/packages/block-library/src/image/icon-retry.js b/packages/block-library/src/image/icon-retry.js deleted file mode 100644 index 66baaa93dcfb38..00000000000000 --- a/packages/block-library/src/image/icon-retry.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * WordPress dependencies - */ -import { Path, SVG } from '@wordpress/components'; - -function svg( props ) { - return <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" { ...props }><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill={ 'none' } /></SVG>; -} - -export default svg; diff --git a/packages/block-library/src/image/icon-retry.native.js b/packages/block-library/src/image/icon-retry.native.js new file mode 100644 index 00000000000000..bdd40f69efd64a --- /dev/null +++ b/packages/block-library/src/image/icon-retry.native.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill={ 'none' } /></SVG>; diff --git a/packages/block-library/src/image/icon.js b/packages/block-library/src/image/icon.js index ad8a11857013a0..b029bab8fbe98a 100644 --- a/packages/block-library/src/image/icon.js +++ b/packages/block-library/src/image/icon.js @@ -3,8 +3,4 @@ */ import { Path, SVG } from '@wordpress/components'; -function svg( props ) { - return <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" { ...props }><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; -} - -export default svg; +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index 4250ff170e328b..81578bd734ba3e 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -36,8 +36,12 @@ .iconRetry { fill: #fff; + width: 100%; + height: 100%; } .icon { fill: $gray-dark; + width: 100%; + height: 100%; } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index c37302902e1f0c..b125e9cb22143e 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -17,6 +17,7 @@ import { * WordPress dependencies */ import { + Icon, Toolbar, ToolbarButton, } from '@wordpress/components'; @@ -130,10 +131,10 @@ class VideoEdit extends React.Component { getIcon( isRetryIcon, isUploadInProgress ) { if ( isRetryIcon ) { - return <SvgIconRetry fill={ style.icon.fill } />; + return <Icon icon={ SvgIconRetry } { ...style.icon } />; } - return <SvgIcon fill={ isUploadInProgress ? style.iconUploading.fill : style.icon.fill } />; + return <Icon icon={ SvgIcon } { ...( isUploadInProgress ? style.iconUploading : style.icon ) } />; } render() { diff --git a/packages/block-library/src/video/icon-retry.js b/packages/block-library/src/video/icon-retry.js deleted file mode 100644 index 82a4be4805c455..00000000000000 --- a/packages/block-library/src/video/icon-retry.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * WordPress dependencies - */ -import { Path, SVG } from '@wordpress/components'; - -function svg( props ) { - return <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" { ...props }><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG>; -} - -export default svg; diff --git a/packages/block-library/src/video/icon-retry.native.js b/packages/block-library/src/video/icon-retry.native.js new file mode 100644 index 00000000000000..d56dff2cac124e --- /dev/null +++ b/packages/block-library/src/video/icon-retry.native.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG>; + diff --git a/packages/block-library/src/video/icon.js b/packages/block-library/src/video/icon.js index 9cf0a1b9989874..8c43e80279f78f 100644 --- a/packages/block-library/src/video/icon.js +++ b/packages/block-library/src/video/icon.js @@ -3,8 +3,5 @@ */ import { Path, SVG } from '@wordpress/components'; -function svg( props ) { - return <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" { ...props }><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /></SVG>; -} +export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /></SVG>; -export default svg; diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss index 85e82cc59b3678..e7b2a3014b579e 100644 --- a/packages/block-library/src/video/style.native.scss +++ b/packages/block-library/src/video/style.native.scss @@ -59,8 +59,12 @@ .icon { fill: $gray-dark; + width: 100%; + height: 100%; } .iconUploading { fill: $gray-lighten-20; + width: 100%; + height: 100%; } diff --git a/packages/components/src/icon/README.md b/packages/components/src/icon/README.md index de36accebd3753..9fbf69609ccbcb 100644 --- a/packages/components/src/icon/README.md +++ b/packages/components/src/icon/README.md @@ -57,7 +57,7 @@ const MyIcon = () => ( ## Props -The component accepts the following props: +The component accepts the following props. Any additional props are passed through to the underlying icon element. ### icon @@ -74,11 +74,3 @@ The size (width and height) of the icon. - Type: `Number` - Required: No - Default: `20` when a Dashicon is rendered, `24` for all other icons. - -### className - -An optional additional class name to apply to the rendered icon. - -- Type: `String` -- Required: No -- Default: `null` diff --git a/packages/components/src/icon/index.js b/packages/components/src/icon/index.js index f106f7c5615114..61a4fb0a2d6c47 100644 --- a/packages/components/src/icon/index.js +++ b/packages/components/src/icon/index.js @@ -8,13 +8,13 @@ import { cloneElement, createElement, Component, isValidElement } from '@wordpre */ import { Dashicon, SVG } from '../'; -function Icon( { icon = null, size, className } ) { +function Icon( { icon = null, size, ...additionalProps } ) { let iconSize; if ( 'string' === typeof icon ) { // Dashicons should be 20x20 by default iconSize = size || 20; - return <Dashicon icon={ icon } size={ iconSize } className={ className } />; + return <Dashicon icon={ icon } size={ iconSize } { ...additionalProps } />; } // Any other icons should be 24x24 by default @@ -22,18 +22,18 @@ function Icon( { icon = null, size, className } ) { if ( 'function' === typeof icon ) { if ( icon.prototype instanceof Component ) { - return createElement( icon, { className, size: iconSize } ); + return createElement( icon, { size: iconSize, ...additionalProps } ); } - return icon(); + return icon( { size: iconSize, ...additionalProps } ); } if ( icon && ( icon.type === 'svg' || icon.type === SVG ) ) { const appliedProps = { - className, width: iconSize, height: iconSize, ...icon.props, + ...additionalProps, }; return <SVG { ...appliedProps } />; @@ -41,8 +41,8 @@ function Icon( { icon = null, size, className } ) { if ( isValidElement( icon ) ) { return cloneElement( icon, { - className, size: iconSize, + ...additionalProps, } ); } diff --git a/packages/components/src/icon/test/index.js b/packages/components/src/icon/test/index.js index ec632ef74677b3..053b0cf390ff58 100644 --- a/packages/components/src/icon/test/index.js +++ b/packages/components/src/icon/test/index.js @@ -17,6 +17,7 @@ import { Path, SVG } from '../../'; describe( 'Icon', () => { const className = 'example-class'; const svg = <SVG><Path d="M5 4v3h5.5v12h3V7H19V4z" /></SVG>; + const style = { fill: 'red' }; it( 'renders nothing when icon omitted', () => { const wrapper = shallow( <Icon /> ); @@ -30,24 +31,12 @@ describe( 'Icon', () => { expect( wrapper.find( 'Dashicon' ).prop( 'icon' ) ).toBe( 'format-image' ); } ); - it( 'renders a dashicon and passes the classname to it', () => { - const wrapper = shallow( <Icon icon="format-image" className={ className } /> ); - - expect( wrapper.find( 'Dashicon' ).prop( 'className' ) ).toBe( 'example-class' ); - } ); - it( 'renders a dashicon and with a default size of 20', () => { const wrapper = shallow( <Icon icon="format-image" /> ); expect( wrapper.find( 'Dashicon' ).prop( 'size' ) ).toBe( 20 ); } ); - it( 'renders a dashicon and passes the size to it', () => { - const wrapper = shallow( <Icon icon="format-image" size={ 32 } /> ); - - expect( wrapper.find( 'Dashicon' ).prop( 'size' ) ).toBe( 32 ); - } ); - it( 'renders a function', () => { const wrapper = shallow( <Icon icon={ () => <span /> } /> ); @@ -60,30 +49,12 @@ describe( 'Icon', () => { expect( wrapper.name() ).toBe( 'span' ); } ); - it( 'renders an element and passes the classname to it', () => { - const wrapper = shallow( <Icon icon={ <span /> } className={ className } /> ); - - expect( wrapper.prop( 'className' ) ).toBe( 'example-class' ); - } ); - - it( 'renders an element and passes the size to it', () => { - const wrapper = shallow( <Icon icon="format-image" size={ 32 } /> ); - - expect( wrapper.prop( 'size' ) ).toBe( 32 ); - } ); - it( 'renders an svg element', () => { const wrapper = shallow( <Icon icon={ svg } /> ); expect( wrapper.name() ).toBe( 'SVG' ); } ); - it( 'renders an svg element and passes the classname to it', () => { - const wrapper = shallow( <Icon icon={ svg } className={ className } /> ); - - expect( wrapper.prop( 'className' ) ).toBe( 'example-class' ); - } ); - it( 'renders an svg element with a default width and height of 24', () => { const wrapper = shallow( <Icon icon={ svg } /> ); @@ -118,29 +89,38 @@ describe( 'Icon', () => { expect( wrapper.name() ).toBe( 'MyComponent' ); } ); - it( 'renders a component and passes the classname to it', () => { + describe( 'props passing', () => { class MyComponent extends Component { render( ) { return <span className={ this.props.className } />; } } - const wrapper = shallow( - <Icon icon={ MyComponent } className={ className } /> - ); - - expect( wrapper.prop( 'className' ) ).toBe( 'example-class' ); - } ); - - it( 'renders a component and passes the size to it', () => { - class MyComponent extends Component { - render( ) { - return <span size={ this.props.size } />; - } - } - const wrapper = shallow( - <Icon icon={ MyComponent } size={ 32 } /> - ); - expect( wrapper.prop( 'size' ) ).toBe( 32 ); + describe.each( [ + [ 'dashicon', { icon: 'format-image' } ], + [ 'element', { icon: <span /> } ], + [ 'svg element', { icon: svg } ], + [ 'component', { icon: MyComponent } ], + ] )( '%s', ( label, props ) => { + it( 'should pass through size', () => { + if ( label === 'svg element' ) { + // Custom logic for SVG elements tested separately. + // + // See: `renders an svg element and passes the size as its width and height` + return; + } + + const wrapper = shallow( <Icon { ...props } size={ 32 } /> ); + + expect( wrapper.prop( 'size' ) ).toBe( 32 ); + } ); + + it( 'should pass through all other props', () => { + const wrapper = shallow( <Icon { ...props } style={ style } className={ className } /> ); + + expect( wrapper.prop( 'style' ) ).toBe( style ); + expect( wrapper.prop( 'className' ) ).toBe( className ); + } ); + } ); } ); } ); From 705497bed8ad6c4baa43464e37d1d79feda6cb69 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Wed, 5 Jun 2019 15:36:16 -0400 Subject: [PATCH 265/664] add important clarification doc removed in #15896 (#15987) --- packages/data/README.md | 4 ++++ packages/data/src/components/with-dispatch/index.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/data/README.md b/packages/data/README.md index 82a151ebffb08e..e126a85781e4a6 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -684,6 +684,10 @@ const SaleButton = withDispatch( ( dispatch, ownProps, { select } ) => { // <SaleButton>Start Sale!</SaleButton> ``` +_Note:_ It is important that the `mapDispatchToProps` function always +returns an object with the same keys. For example, it should not contain +conditions under which a different value would be returned. + _Parameters_ - _mapDispatchToProps_ `Function`: A function of returning an object of prop names where value is a dispatch-bound action creator, or a function to be called with the component's props and returning an action creator. diff --git a/packages/data/src/components/with-dispatch/index.js b/packages/data/src/components/with-dispatch/index.js index 117edd1f598293..2247a253a189d5 100644 --- a/packages/data/src/components/with-dispatch/index.js +++ b/packages/data/src/components/with-dispatch/index.js @@ -81,6 +81,10 @@ import { useDispatchWithMap } from '../use-dispatch'; * // <SaleButton>Start Sale!</SaleButton> * ``` * + * _Note:_ It is important that the `mapDispatchToProps` function always + * returns an object with the same keys. For example, it should not contain + * conditions under which a different value would be returned. + * * @return {Component} Enhanced component with merged dispatcher props. */ const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent( From 7e3b2ab4a686b18ffd459bb77c8a8d1e221dd4c1 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Wed, 5 Jun 2019 22:46:54 +0300 Subject: [PATCH 266/664] Prevent rendering of settings button until it has an action (#16000) --- packages/block-library/src/video/edit.native.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index b125e9cb22143e..db2374bec916f0 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -180,11 +180,11 @@ class VideoEdit extends React.Component { { toolbarEditButton } </BlockControls> <InspectorControls> - <ToolbarButton + { false && <ToolbarButton //Not rendering settings button until it has an action label={ __( 'Video Settings' ) } icon="admin-generic" onClick={ () => ( null ) } - /> + /> } </InspectorControls> <MediaUploadProgress mediaId={ id } From 741921a8305a76956a8f28f80949e4263bfd9422 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Thu, 6 Jun 2019 11:16:41 +0300 Subject: [PATCH 267/664] [Mobile]Enable sound on video block (#15997) * Enable sound on video block * Play sound even if device silent switch is set --- packages/block-library/src/video/edit.native.js | 1 - packages/block-library/src/video/video-player.android.js | 1 + packages/block-library/src/video/video-player.ios.js | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index db2374bec916f0..1b7fc518313440 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -219,7 +219,6 @@ class VideoEdit extends React.Component { style={ videoStyle } source={ { uri: src } } paused={ true } - muted={ true } /> </View> } diff --git a/packages/block-library/src/video/video-player.android.js b/packages/block-library/src/video/video-player.android.js index efe91ceda019fa..b1ccc057ba03c3 100644 --- a/packages/block-library/src/video/video-player.android.js +++ b/packages/block-library/src/video/video-player.android.js @@ -19,6 +19,7 @@ const Video = ( props ) => { // We are using built-in player controls becasue manually // calling presentFullscreenPlayer() is not working for android controls={ isSelected } + muted={ !isSelected } /> </View> ); diff --git a/packages/block-library/src/video/video-player.ios.js b/packages/block-library/src/video/video-player.ios.js index 0713afc404b1a3..846b79844c107c 100644 --- a/packages/block-library/src/video/video-player.ios.js +++ b/packages/block-library/src/video/video-player.ios.js @@ -57,6 +57,7 @@ class Video extends Component { controls={ false } onLoad={ this.onLoad } onLoadStart={ this.onLoadStart } + ignoreSilentSwitch={ 'ignore' } /> { isLoaded && <TouchableOpacity disabled={ ! isSelected } onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> From 943a8401827b800ccf3fff84b7b5c97d2b9a4dc0 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Thu, 6 Jun 2019 10:36:10 +0200 Subject: [PATCH 268/664] Update package-lock.json wiht missing entry for server-side-render --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index e21b8504db5a29..7352bce3c4f67c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3830,6 +3830,7 @@ "version": "file:packages/server-side-render", "requires": { "@babel/runtime": "^7.4.4", + "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/components": "file:packages/components", "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", From d14166e01d555b56fd8094678bd0009d597d3101 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Thu, 6 Jun 2019 11:47:04 +0300 Subject: [PATCH 269/664] Start playback immediately after we go full screen (#15998) Otherwise we need user to tap play again --- packages/block-library/src/video/video-player.ios.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/video/video-player.ios.js b/packages/block-library/src/video/video-player.ios.js index 846b79844c107c..53d88380207080 100644 --- a/packages/block-library/src/video/video-player.ios.js +++ b/packages/block-library/src/video/video-player.ios.js @@ -20,6 +20,7 @@ class Video extends Component { super( ...arguments ); this.state = { isLoaded: false, + isFullScreen: false, }; this.onPressPlay = this.onPressPlay.bind( this ); this.onLoad = this.onLoad.bind( this ); @@ -42,7 +43,7 @@ class Video extends Component { render() { const { isSelected, style } = this.props; - const { isLoaded } = this.state; + const { isLoaded, isFullScreen } = this.state; return ( <View style={ styles.videoContainer }> @@ -58,6 +59,13 @@ class Video extends Component { onLoad={ this.onLoad } onLoadStart={ this.onLoadStart } ignoreSilentSwitch={ 'ignore' } + paused={ ! isFullScreen } + onFullscreenPlayerWillPresent={ () => { + this.setState( { isFullScreen: true } ); + } } + onFullscreenPlayerDidDismiss={ () => { + this.setState( { isFullScreen: false } ); + } } /> { isLoaded && <TouchableOpacity disabled={ ! isSelected } onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> From dfed30a6d2c18de35ba4ebd57928b5849c7c13df Mon Sep 17 00:00:00 2001 From: Seghir Nadir <nadir.seghir@gmail.com> Date: Thu, 6 Jun 2019 11:39:26 +0100 Subject: [PATCH 270/664] Fix: When adding a button block, Button receives focus first #15859 (#15951) --- packages/block-library/src/button/edit.js | 4 +++ .../blocks/__snapshots__/button.test.js.snap | 13 ++++++++ .../e2e-tests/specs/blocks/button.test.js | 30 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 packages/e2e-tests/specs/blocks/__snapshots__/button.test.js.snap create mode 100644 packages/e2e-tests/specs/blocks/button.test.js diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 6d3f279542e009..142f9b0a06835f 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -130,6 +130,10 @@ class ButtonEdit extends Component { <Dashicon icon="admin-links" /> <URLInput value={ url } + /* eslint-disable jsx-a11y/no-autofocus */ + // Disable Reason: The rule is meant to prevent enabling auto-focus, not disabling it. + autoFocus={ false } + /* eslint-enable jsx-a11y/no-autofocus */ onChange={ ( value ) => setAttributes( { url: value } ) } /> <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/button.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/button.test.js.snap new file mode 100644 index 00000000000000..82fca76a8fb04d --- /dev/null +++ b/packages/e2e-tests/specs/blocks/__snapshots__/button.test.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Button can jump focus back & forth 1`] = ` +"<!-- wp:button --> +<div class=\\"wp-block-button\\"><a class=\\"wp-block-button__link\\" href=\\"https://wordpress.org\\">WordPress</a></div> +<!-- /wp:button -->" +`; + +exports[`Button has focus on button content 1`] = ` +"<!-- wp:button --> +<div class=\\"wp-block-button\\"><a class=\\"wp-block-button__link\\">Content</a></div> +<!-- /wp:button -->" +`; diff --git a/packages/e2e-tests/specs/blocks/button.test.js b/packages/e2e-tests/specs/blocks/button.test.js new file mode 100644 index 00000000000000..69343633f4247f --- /dev/null +++ b/packages/e2e-tests/specs/blocks/button.test.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { + insertBlock, + getEditedPostContent, + createNewPost, +} from '@wordpress/e2e-test-utils'; + +describe( 'Button', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'has focus on button content', async () => { + await insertBlock( 'Button' ); + await page.keyboard.type( 'Content' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'can jump focus back & forth', async () => { + await insertBlock( 'Button' ); + await page.keyboard.type( 'WordPress' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( 'https://wordpress.org' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); From 6b2e834d019e5929e00540e6121272361de0ef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Thu, 6 Jun 2019 11:41:42 +0100 Subject: [PATCH 271/664] Fix mobile quotes insertion and removal of empty lines (#16013) * Make sure selection is not over length of text. * Replicate web behaviour for onSplit detection. --- .../src/components/rich-text/index.native.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 92143f16b10697..d2d3df25b5f253 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -165,7 +165,7 @@ export class RichText extends Component { * @return {Object} A RichText value with formats and selection. */ createRecord() { - return { + const value = { start: this.selectionStart, end: this.selectionEnd, ...create( { @@ -175,6 +175,9 @@ export class RichText extends Component { multilineWrapperTags: this.multilineWrapperTags, } ), }; + const start = Math.min( this.selectionStart, value.text.length ); + const end = Math.min( this.selectionEnd, value.text.length ); + return { ...value, start, end }; } /** @@ -347,15 +350,15 @@ export class RichText extends Component { this.lastEventCount = event.nativeEvent.eventCount; this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; - + const { onReplace, onSplit } = this.props; + const canSplit = onReplace && onSplit; const currentRecord = this.createRecord(); - if ( this.multilineTag ) { if ( event.shiftKey ) { this.needsSelectionUpdate = true; const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; this.onFormatChange( insertedLineBreak ); - } else if ( this.onSplit && isEmptyLine( currentRecord ) ) { + } else if ( canSplit && isEmptyLine( currentRecord ) ) { this.onSplit( currentRecord ); } else { this.needsSelectionUpdate = true; From 3c10eaaacc67d00eeb2495f4f5b0742133925ad6 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 6 Jun 2019 12:10:03 +0100 Subject: [PATCH 272/664] Always show side inserter on the last empty paragraph (#15864) --- .../src/components/block-list/block.js | 45 ++++++++++--------- .../src/components/block-list/style.scss | 2 - .../default-block-appender/style.scss | 12 ----- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index a9b28f618daf5f..b9f3c02ed30ead 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -79,6 +79,7 @@ function BlockListBlock( { className, name, isValid, + isLast, attributes, initialPosition, wrapperProps, @@ -343,8 +344,8 @@ function BlockListBlock( { // If the block is selected and we're typing the block should not appear. // Empty paragraph blocks should always show up as unselected. - const showEmptyBlockSideInserter = - ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const showInserterShortcuts = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const showEmptyBlockSideInserter = ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid; const shouldAppearSelected = ! isFocusMode && ! showEmptyBlockSideInserter && @@ -538,24 +539,24 @@ function BlockListBlock( { { !! hasError && <BlockCrashWarning /> } </IgnoreNestedEvents> </div> + { showInserterShortcuts && ( + <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> + <InserterWithShortcuts + clientId={ clientId } + rootClientId={ rootClientId } + onToggle={ selectOnOpen } + /> + </div> + ) } { showEmptyBlockSideInserter && ( - <> - <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> - <InserterWithShortcuts - clientId={ clientId } - rootClientId={ rootClientId } - onToggle={ selectOnOpen } - /> - </div> - <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> - <Inserter - position="top right" - onToggle={ selectOnOpen } - rootClientId={ rootClientId } - clientId={ clientId } - /> - </div> - </> + <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> + <Inserter + position="top right" + onToggle={ selectOnOpen } + rootClientId={ rootClientId } + clientId={ clientId } + /> + </div> ) } </IgnoreNestedEvents> ); @@ -580,6 +581,8 @@ const applyWithSelect = withSelect( getSettings, hasSelectedInnerBlock, getTemplateLock, + getBlockIndex, + getBlockOrder, __unstableGetBlockWithoutInnerBlocks, } = select( 'core/block-editor' ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); @@ -587,6 +590,8 @@ const applyWithSelect = withSelect( const { hasFixedToolbar, focusMode } = getSettings(); const templateLock = getTemplateLock( rootClientId ); const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); + const index = getBlockIndex( clientId, rootClientId ); + const blockOrder = getBlockOrder( rootClientId ); // The fallback to `{}` is a temporary fix. // This function should never be called when a block is not present in the state. @@ -611,6 +616,7 @@ const applyWithSelect = withSelect( isLocked: !! templateLock, isFocusMode: focusMode && isLargeViewport, hasFixedToolbar: hasFixedToolbar && isLargeViewport, + isLast: index === blockOrder.length - 1, // Users of the editor.BlockListBlock filter used to be able to access the block prop // Ideally these blocks would rely on the clientId prop only. @@ -637,7 +643,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks, replaceBlocks, toggleSelection, - } = dispatch( 'core/block-editor' ); return { diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 4aaf4e3f2c4490..02df5d8e80068d 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -274,13 +274,11 @@ } // Appender - &.is-typing .block-editor-block-list__empty-block-inserter, &.is-typing .block-editor-block-list__side-inserter { opacity: 0; animation: none; } - .block-editor-block-list__empty-block-inserter, .block-editor-block-list__side-inserter { @include edit-post__fade-in-animation; } diff --git a/packages/block-editor/src/components/default-block-appender/style.scss b/packages/block-editor/src/components/default-block-appender/style.scss index 831b844627062b..6f6c28da6aa8c8 100644 --- a/packages/block-editor/src/components/default-block-appender/style.scss +++ b/packages/block-editor/src/components/default-block-appender/style.scss @@ -29,22 +29,10 @@ } } - // Don't show the inserter until mousing over. - .block-editor-inserter__toggle:not([aria-expanded="true"]) { - opacity: 0; - transition: opacity 0.2s; - @include reduce-motion("transition"); - will-change: opacity; - } - &:hover { .block-editor-inserter-with-shortcuts { @include edit-post__fade-in-animation; } - - .block-editor-inserter__toggle { - opacity: 1; - } } // Dropzone. From c5e8784799f4e0f4d0891f52fae95375f43b76f7 Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Thu, 6 Jun 2019 13:16:52 +0200 Subject: [PATCH 273/664] Selecting Parent Blocks: Try clickthrough (#15537) * Selecting parents: Try clickthrough. Clickthrough has you select the parent before you can select the child. This is already in place on the mobile breakpoints, this just expands it to desktop as well. It is a work in progress, right now it is not working as intended: once you have "unlocked" the deepest level, it becomes immediately locked and you have to click through the layers again to unlock it again. The deepest layer should always be unlocked until you deselect all blocks again. * Render overlay on top of inner block only when none of the nested blocks is selected * Fix overlay, fix breadcrumb, polish. * Remove click-overlay. * Fix the selection of inner blocks for reusable blocks template * Disable async mode for parent blocks when nested block selected * Refactor BlockListBlock to use AsyncModeProvider * Make the reusable blocks save button clickable. i At least this means you can edit and save reusable blocks. * Fix so reusable blocks with nesting are editable. * Fix movers. The z-index was too low. It had to be higher to accommodate the other z-index changes. * Bring the behavior closer to what we have as of today --- assets/stylesheets/_z-index.scss | 17 ++++++++------ .../block-list/block-async-mode-provider.js | 23 +++++++++++++++++++ .../src/components/block-list/block.js | 5 +++- .../src/components/block-list/index.js | 8 ++++--- .../src/components/block-list/style.scss | 14 +++++++++++ .../src/components/inner-blocks/index.js | 12 ++++------ .../src/components/inner-blocks/style.scss | 18 ++++++++------- .../src/block/edit-panel/editor.scss | 4 ++++ .../block-library/src/columns/editor.scss | 18 ++++++--------- .../e2e-tests/specs/reusable-blocks.test.js | 12 ++++------ 10 files changed, 86 insertions(+), 45 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/block-async-mode-provider.js diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 92a044a7b899a3..12f9b16de20037 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -9,7 +9,6 @@ $z-layers: ( ".block-library-classic__toolbar": 10, ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__breadcrumb": 2, - ".editor-inner-blocks .block-editor-block-list__breadcrumb": 22, ".components-form-toggle__input": 1, ".components-panel__header.edit-post-sidebar__panel-tabs": -1, ".edit-post-sidebar .components-panel": -2, @@ -19,7 +18,6 @@ $z-layers: ( ".components-modal__header": 10, ".edit-post-meta-boxes-area.is-loading::before": 1, ".edit-post-meta-boxes-area .spinner": 5, - ".block-editor-block-contextual-toolbar": 21, ".components-popover__close": 5, ".block-editor-block-list__insertion-point": 6, ".block-editor-inserter-with-shortcuts": 5, @@ -51,16 +49,21 @@ $z-layers: ( ".components-drop-zone": 100, ".components-drop-zone__content": 110, - // The block mover, particularly in nested contexts, - // should overlap most block content. - ".block-editor-block-list__block.is-{selected,hovered} .block-editor-block-mover": 80, - // The block mover for floats should overlap the controls of adjacent blocks. ".block-editor-block-list__block {core/image aligned left or right}": 81, // Small screen inner blocks overlay must be displayed above drop zone, // settings menu, and movers. - ".block-editor-inner-blocks__small-screen-overlay:after": 120, + ".block-editor-inner-blocks.has-overlay::after": 120, + + // The toolbar, when contextual, should be above any adjacent nested block click overlays. + ".block-editor-block-list__layout .reusable-block-edit-panel": 121, + ".block-editor-block-contextual-toolbar": 121, + ".editor-inner-blocks .block-editor-block-list__breadcrumb": 122, + + // The block mover, particularly in nested contexts, + // should overlap most block content. + ".block-editor-block-list__block.is-{selected,hovered} .block-editor-block-mover": 121, // Show sidebar above wp-admin navigation bar for mobile viewports: // #wpadminbar { z-index: 99999 } diff --git a/packages/block-editor/src/components/block-list/block-async-mode-provider.js b/packages/block-editor/src/components/block-list/block-async-mode-provider.js new file mode 100644 index 00000000000000..aaa2e709db92c6 --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-async-mode-provider.js @@ -0,0 +1,23 @@ +/** + * WordPress dependencies + */ +import { + __experimentalAsyncModeProvider as AsyncModeProvider, + useSelect, +} from '@wordpress/data'; + +const BlockAsyncModeProvider = ( { children, clientId, isBlockInSelection } ) => { + const isParentOfSelectedBlock = useSelect( ( select ) => { + return select( 'core/block-editor' ).hasSelectedInnerBlock( clientId, true ); + } ); + + const isSyncModeForced = isBlockInSelection || isParentOfSelectedBlock; + + return ( + <AsyncModeProvider value={ ! isSyncModeForced }> + { children } + </AsyncModeProvider> + ); +}; + +export default BlockAsyncModeProvider; diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index b9f3c02ed30ead..82a237625e6a77 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -23,7 +23,10 @@ import { } from '@wordpress/blocks'; import { KeyboardShortcuts, withFilters } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { + withDispatch, + withSelect, +} from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { compose, pure } from '@wordpress/compose'; diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 2da9071693d5fa..79cc1b29273375 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -24,6 +24,7 @@ import { compose } from '@wordpress/compose'; /** * Internal dependencies */ +import BlockAsyncModeProvider from './block-async-mode-provider'; import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; import { getBlockDOMNode } from '../../utils/dom'; @@ -207,9 +208,10 @@ class BlockList extends Component { selectedBlockClientId === clientId; return ( - <AsyncModeProvider + <BlockAsyncModeProvider key={ 'block-' + clientId } - value={ ! isBlockInSelection } + clientId={ clientId } + isBlockInSelection={ isBlockInSelection } > <BlockListBlock clientId={ clientId } @@ -218,7 +220,7 @@ class BlockList extends Component { rootClientId={ rootClientId } isDraggable={ isDraggable } /> - </AsyncModeProvider> + </BlockAsyncModeProvider> ); } ) } diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 02df5d8e80068d..8ee3558ddf6398 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -303,6 +303,20 @@ } } + // Reusable Blocks clickthrough overlays + &.is-reusable > .block-editor-block-list__block-edit .block-editor-inner-blocks.has-overlay { + // Remove only the top click overlay. + &::after { + display: none; + } + + // Restore it for subsequent. + .block-editor-inner-blocks.has-overlay::after { + display: block; + } + } + + // Alignments &[data-align="left"], &[data-align="right"] { diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 2a69e5305fc3fa..75a6731f42db57 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -7,7 +7,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { withViewportMatch } from '@wordpress/viewport'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks'; @@ -106,14 +105,13 @@ class InnerBlocks extends Component { render() { const { clientId, - isSmallScreen, - isSelectedBlockInRoot, + hasOverlay, renderAppender, } = this.props; const { templateInProcess } = this.state; const classes = classnames( 'editor-inner-blocks block-editor-inner-blocks', { - 'has-overlay': isSmallScreen && ! isSelectedBlockInRoot, + 'has-overlay': hasOverlay, } ); return ( @@ -131,7 +129,6 @@ class InnerBlocks extends Component { InnerBlocks = compose( [ withBlockEditContext( ( context ) => pick( context, [ 'clientId' ] ) ), - withViewportMatch( { isSmallScreen: '< medium' } ), withSelect( ( select, ownProps ) => { const { isBlockSelected, @@ -142,12 +139,13 @@ InnerBlocks = compose( [ getTemplateLock, } = select( 'core/block-editor' ); const { clientId } = ownProps; + const block = getBlock( clientId ); const rootClientId = getBlockRootClientId( clientId ); return { - isSelectedBlockInRoot: isBlockSelected( clientId ) || hasSelectedInnerBlock( clientId ), - block: getBlock( clientId ), + block, blockListSettings: getBlockListSettings( clientId ), + hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ), parentLock: getTemplateLock( rootClientId ), }; } ), diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index f4218ef0667ebe..61ed91c42ac02f 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -1,9 +1,11 @@ -.block-editor-inner-blocks.has-overlay::after { - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: z-index(".block-editor-inner-blocks__small-screen-overlay:after"); +.block-editor-inner-blocks.has-overlay { + &::after { + content: ""; + position: absolute; + top: -$block-padding; + right: -$block-padding; + bottom: -$block-padding; + left: -$block-padding; + z-index: z-index(".block-editor-inner-blocks.has-overlay::after"); + } } diff --git a/packages/block-library/src/block/edit-panel/editor.scss b/packages/block-library/src/block/edit-panel/editor.scss index 6c11e19d5e227c..d5b5c81fd7e714 100644 --- a/packages/block-library/src/block/edit-panel/editor.scss +++ b/packages/block-library/src/block/edit-panel/editor.scss @@ -11,6 +11,10 @@ margin: 0 (-$block-padding); padding: $grid-size $block-padding; + // Elevate the reusable blocks toolbar above the clickthrough overlay. + position: relative; + z-index: z-index(".block-editor-block-list__layout .reusable-block-edit-panel"); + // Use opacity to work in various editor styles. border: $border-width dashed $dark-opacity-light-500; border-bottom: none; diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index c771c79f5f9d3c..f8780b91ebab53 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -164,17 +164,13 @@ div.block-core-columns.is-vertically-aligned-bottom { */ [data-type="core/column"] > .editor-block-list__block-edit > .editor-block-list__breadcrumb { right: 0; + left: auto; } -// The empty state of a columns block has the default appenders. -// Since those appenders are not blocks, the parent, actual block, appears "hovered" when hovering the appenders. -// Because the column shouldn't be hovered as part of this temporary passthrough, we unset the hover style. -.wp-block-columns [data-type="core/column"].is-hovered { - > .block-editor-block-list__block-edit::before { - content: none; - } - - .block-editor-block-list__breadcrumb { - display: none; - } +/** + * Make single Column overlay not extend past boundaries of parent + */ +.block-core-columns > .block-editor-inner-blocks.has-overlay::after { + left: 0; + right: 0; } diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index 7a9174f5b6246a..1eb25f41735e11 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -45,10 +45,8 @@ describe( 'Reusable Blocks', () => { '//*[contains(@class, "components-snackbar")]/*[text()="Block created."]' ); - // Select all of the text in the title field by triple-clicking on it. We - // triple-click because, on Mac, Mod+A doesn't work. This step can be removed - // when https://github.com/WordPress/gutenberg/issues/7972 is fixed - await page.click( '.reusable-block-edit-panel__title', { clickCount: 3 } ); + // Select all of the text in the title field. + await pressKeyWithModifier( 'primary', 'a' ); // Give the reusable block a title await page.keyboard.type( 'Greeting block' ); @@ -223,10 +221,8 @@ describe( 'Reusable Blocks', () => { '//*[contains(@class, "components-snackbar")]/*[text()="Block created."]' ); - // Select all of the text in the title field by triple-clicking on it. We - // triple-click because, on Mac, Mod+A doesn't work. This step can be removed - // when https://github.com/WordPress/gutenberg/issues/7972 is fixed - await page.click( '.reusable-block-edit-panel__title', { clickCount: 3 } ); + // Select all of the text in the title field. + await pressKeyWithModifier( 'primary', 'a' ); // Give the reusable block a title await page.keyboard.type( 'Multi-selection reusable block' ); From ff70de60086e8f505de51a9f9883d77bf5eda119 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 6 Jun 2019 14:13:34 +0200 Subject: [PATCH 274/664] [Mobile] Move unselected block accessibility handling to block-holder (v2) (#15225) * Update paragraph block accessibility label * Move block unselected state accessibility handling to block-holder * Add getAccessibilityLabel to block settings * Rename getAccessibilityLabel into __experimentalGetAccessibilityLabel * Make __experimentalGetAccessibilityLabel only available for native mobile * Handle not alt and a caption case * Handle no caption case * Fix merge mistake in prop names * Handle setting UnrecognizedBlock accessibility label in BlockHolder * Fix lint errors --- .../block-library/src/heading/edit.native.js | 129 +++++++----------- .../block-library/src/heading/index.native.js | 39 ++++++ .../block-library/src/image/edit.native.js | 7 - .../block-library/src/image/index.native.js | 30 ++++ .../block-library/src/missing/edit.native.js | 17 +-- .../src/paragraph/edit.native.js | 27 +--- .../src/paragraph/index.native.js | 28 ++++ 7 files changed, 149 insertions(+), 128 deletions(-) create mode 100644 packages/block-library/src/heading/index.native.js create mode 100644 packages/block-library/src/image/index.native.js create mode 100644 packages/block-library/src/paragraph/index.native.js diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index 97d2228346466a..e7d295402c92e1 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -8,94 +8,61 @@ import styles from './editor.scss'; * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; import { RichText, BlockControls } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; -import { create } from '@wordpress/rich-text'; -class HeadingEdit extends Component { - plainTextContent( html ) { - const result = create( { html } ); - if ( result ) { - return result.text; - } - return ''; - } - - render() { - const { - attributes, - setAttributes, - mergeBlocks, - style, - onReplace, - } = this.props; - - const { - level, - placeholder, - content, - } = attributes; - - const tagName = 'h' + level; - - return ( - <View - accessible={ ! this.props.isSelected } - accessibilityLabel={ - isEmpty( content ) ? - sprintf( - /* translators: accessibility text. %s: heading level. */ - __( 'Heading block. Level %s. Empty.' ), - level - ) : - sprintf( - /* translators: accessibility text. 1: heading level. 2: heading content. */ - __( 'Heading block. Level %1$s. %2$s' ), - level, - this.plainTextContent( content ) - ) +const HeadingEdit = ( { + attributes, + isSelected, + mergeBlocks, + onBlur, + onFocus, + onReplace, + setAttributes, + style, +} ) => ( + <View onAccessibilityTap={ onFocus }> + <BlockControls> + <HeadingToolbar + minLevel={ 2 } + maxLevel={ 5 } + selectedLevel={ attributes.level } + onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } + /> + </BlockControls> + <RichText + identifier="content" + tagName={ 'h' + attributes.level } + value={ attributes.content } + isSelected={ isSelected } + style={ { + ...style, + minHeight: styles[ 'wp-block-heading' ].minHeight, + } } + onFocus={ onFocus } // always assign onFocus as a props + onBlur={ onBlur } // always assign onBlur as a props + onChange={ ( value ) => setAttributes( { content: value } ) } + onMerge={ mergeBlocks } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( 'core/paragraph' ); } - onAccessibilityTap={ this.props.onFocus } - > - <BlockControls> - <HeadingToolbar minLevel={ 2 } maxLevel={ 5 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> - </BlockControls> - <RichText - identifier="content" - tagName={ tagName } - value={ content } - isSelected={ this.props.isSelected } - style={ { - ...style, - minHeight: styles[ 'wp-block-heading' ].minHeight, - } } - onFocus={ this.props.onFocus } // always assign onFocus as a props - onBlur={ this.props.onBlur } // always assign onBlur as a props - onChange={ ( value ) => setAttributes( { content: value } ) } - onMerge={ mergeBlocks } - onSplit={ ( value ) => { - if ( ! value ) { - return createBlock( 'core/paragraph' ); - } - return createBlock( 'core/heading', { - ...attributes, - content: value, - } ); - } } - onReplace={ onReplace } - onRemove={ () => onReplace( [] ) } - placeholder={ placeholder || __( 'Write heading…' ) } - /> - </View> - ); - } -} + return createBlock( 'core/heading', { + ...attributes, + content: value, + } ); + } } + onReplace={ onReplace } + onRemove={ () => onReplace( [] ) } + placeholder={ attributes.placeholder || __( 'Write heading…' ) } + /> + </View> +); + export default HeadingEdit; diff --git a/packages/block-library/src/heading/index.native.js b/packages/block-library/src/heading/index.native.js new file mode 100644 index 00000000000000..67a6d2d91ded01 --- /dev/null +++ b/packages/block-library/src/heading/index.native.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import { isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { create } from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { settings as webSettings } from './index.js'; + +export { metadata, name } from './index.js'; + +export const settings = { + ...webSettings, + __experimentalGetAccessibilityLabel( attributes ) { + const { content, level } = attributes; + + const plainTextContent = ( html ) => create( { html } ).text || ''; + + return isEmpty( content ) ? + sprintf( + /* translators: accessibility text. %s: heading level. */ + __( 'Level %s. Empty.' ), + level + ) : + sprintf( + /* translators: accessibility text. 1: heading level. 2: heading content. */ + __( 'Level %1$s. %2$s' ), + level, + plainTextContent( content ) + ); + }, +}; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 479f82a2df7754..61a0adf3635ce2 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -274,13 +274,6 @@ class ImageEdit extends React.Component { const getImageComponent = ( openMediaOptions, getMediaOptions ) => ( <TouchableWithoutFeedback accessible={ ! isSelected } - accessibilityLabel={ sprintf( - /* translators: accessibility text. 1: image alt text. 2: image caption. */ - __( 'Image block. %1$s. %2$s' ), - alt, - caption - ) } - accessibilityRole={ 'button' } onPress={ this.onImagePressed } onLongPress={ openMediaOptions } disabled={ ! isSelected } diff --git a/packages/block-library/src/image/index.native.js b/packages/block-library/src/image/index.native.js new file mode 100644 index 00000000000000..d7e0d17b53a9cb --- /dev/null +++ b/packages/block-library/src/image/index.native.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { settings as webSettings } from './index.js'; + +export { metadata, name } from './index.js'; + +export const settings = { + ...webSettings, + __experimentalGetAccessibilityLabel( attributes ) { + const { caption, alt, url } = attributes; + + if ( ! url ) { + return __( 'Empty' ); + } + + if ( ! alt ) { + return caption || ''; + } + + // This is intended to be read by a screen reader. + // A period simply means a pause, no need to translate it. + return alt + ( caption ? '. ' + caption : '' ); + }, +}; diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index 970975aa711418..838c64d88b56c4 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -10,7 +10,7 @@ import { Icon } from '@wordpress/components'; import { coreBlocks } from '@wordpress/block-library'; import { normalizeIconObject } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -25,20 +25,7 @@ export default class UnsupportedBlockEdit extends Component { const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins'; return ( - <View style={ styles.unsupportedBlock } - accessible={ true } - accessibilityLabel={ - blockType ? - sprintf( - /* translators: accessibility text. %s: unsupported block type. */ - __( 'Unsupported block: %s' ), - title - ) : - /* translators: accessibility text. */ - __( 'Unsupported block' ) - } - onAccessibilityTap={ this.props.onFocus } - > + <View style={ styles.unsupportedBlock }> <Icon className="unsupported-icon" icon={ icon && icon.src ? icon.src : icon } /> <Text style={ styles.unsupportedBlockMessage }>{ title }</Text> </View> diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index a97885c213c3a0..717677658e5a15 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -2,16 +2,14 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { createBlock } from '@wordpress/blocks'; import { RichText } from '@wordpress/block-editor'; -import { create } from '@wordpress/rich-text'; /** * Internal dependencies @@ -39,14 +37,6 @@ class ParagraphEdit extends Component { ) ) ); } - plainTextContent( html ) { - const result = create( { html } ); - if ( result ) { - return result.text; - } - return ''; - } - render() { const { attributes, @@ -62,20 +52,7 @@ class ParagraphEdit extends Component { } = attributes; return ( - <View - accessible={ ! this.props.isSelected } - accessibilityLabel={ - isEmpty( content ) ? - /* translators: accessibility text. empty paragraph block. */ - __( 'Paragraph block. Empty' ) : - sprintf( - /* translators: accessibility text. %s: text content of the paragraph block. */ - __( 'Paragraph block. %s' ), - this.plainTextContent( content ) - ) - } - onAccessibilityTap={ this.props.onFocus } - > + <View> <RichText identifier="content" tagName="p" diff --git a/packages/block-library/src/paragraph/index.native.js b/packages/block-library/src/paragraph/index.native.js new file mode 100644 index 00000000000000..4de4948f251807 --- /dev/null +++ b/packages/block-library/src/paragraph/index.native.js @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { create } from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { settings as webSettings } from './index.js'; + +export { metadata, name } from './index.js'; + +export const settings = { + ...webSettings, + __experimentalGetAccessibilityLabel( attributes ) { + const { content } = attributes; + + const plainTextContent = ( html ) => create( { html } ).text || ''; + + return isEmpty( content ) ? __( 'Empty' ) : plainTextContent( content ); + }, +}; From b030fceb5a6e62ee3a15e8bca36c04f576b99972 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Thu, 6 Jun 2019 16:04:14 +0200 Subject: [PATCH 275/664] [Mobile] Modified More block to be read-only (#16005) * Update More block to be read-only. Copying style from Page Break block * Using sans-serif on more and nextpage blocks * Extend borders of HR component on more and nextpage blocks * Update accessibility configuration of More block --- .../block-library/src/more/edit.native.js | 62 +++---------------- .../block-library/src/more/editor.native.scss | 18 ++---- .../block-library/src/more/index.native.js | 13 ++++ .../block-library/src/nextpage/edit.native.js | 2 + .../src/nextpage/editor.native.scss | 1 - 5 files changed, 29 insertions(+), 67 deletions(-) create mode 100644 packages/block-library/src/more/index.native.js diff --git a/packages/block-library/src/more/edit.native.js b/packages/block-library/src/more/edit.native.js index e6d519c26b3042..8b369284cb5962 100644 --- a/packages/block-library/src/more/edit.native.js +++ b/packages/block-library/src/more/edit.native.js @@ -2,85 +2,43 @@ * External dependencies */ import { View } from 'react-native'; +import Hr from 'react-native-hr'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { - getDefaultBlockName, - createBlock, -} from '@wordpress/blocks'; /** * Internal dependencies */ -import { PlainText } from '@wordpress/block-editor'; import styles from './editor.scss'; export default class MoreEdit extends Component { constructor() { super( ...arguments ); - this.onChangeInput = this.onChangeInput.bind( this ); this.state = { defaultText: __( 'Read more' ), }; } - onChangeInput( newValue ) { - // Detect Enter.key and add new empty block after. - // Note: This is detected after the fact, and the newline could be visible on the block - // for a very small time. This is OK for the alpha, we will revisit the logic later. - // See https://github.com/wordpress-mobile/gutenberg-mobile/issues/324 - if ( newValue.indexOf( '\n' ) !== -1 ) { - const { insertBlocksAfter } = this.props; - insertBlocksAfter( [ createBlock( getDefaultBlockName() ) ] ); - return; - } - // Set defaultText to an empty string, allowing the user to clear/replace the input field's text - this.setState( { - defaultText: '', - } ); - const value = newValue.length === 0 ? undefined : newValue; - this.props.setAttributes( { customText: value } ); - } - - renderLine() { - return <View style={ styles[ 'block-library-more__line' ] } />; - } - - renderText() { - const { attributes, onFocus, onBlur } = this.props; - const { customText } = attributes; + render() { + const { customText } = this.props.attributes; const { defaultText } = this.state; - const value = customText !== undefined ? customText : defaultText; + const content = customText || defaultText; return ( <View> - <PlainText - style={ styles[ 'block-library-more__text' ] } - value={ value } - multiline={ true } - underlineColorAndroid="transparent" - onChange={ this.onChangeInput } - placeholder={ defaultText } - isSelected={ this.props.isSelected } - onFocus={ onFocus } - onBlur={ onBlur } + <Hr + text={ content } + marginLeft={ 0 } + marginRight={ 0 } + textStyle={ styles[ 'block-library-more__text' ] } + lineStyle={ styles[ 'block-library-more__line' ] } /> </View> ); } - - render() { - return ( - <View style={ styles[ 'block-library-more__container' ] }> - { this.renderLine() } - { this.renderText() } - { this.renderLine() } - </View> - ); - } } diff --git a/packages/block-library/src/more/editor.native.scss b/packages/block-library/src/more/editor.native.scss index beb5ef423776ff..eb4a1d60d9431f 100644 --- a/packages/block-library/src/more/editor.native.scss +++ b/packages/block-library/src/more/editor.native.scss @@ -1,23 +1,13 @@ // @format -.block-library-more__container { - align-items: center; - padding: 4px; - flex-direction: row; -} - .block-library-more__line { - background-color: #555d66; + background-color: $gray-lighten-20; height: 2; - flex: 1; } .block-library-more__text { + color: $gray; text-decoration-style: solid; - flex: 0; - width: 200; - text-align: center; - margin-left: 15; - margin-right: 15; - margin-bottom: 5; + text-transform: uppercase; } + diff --git a/packages/block-library/src/more/index.native.js b/packages/block-library/src/more/index.native.js new file mode 100644 index 00000000000000..1525b6694abe52 --- /dev/null +++ b/packages/block-library/src/more/index.native.js @@ -0,0 +1,13 @@ +/** + * Internal dependencies + */ +import { settings as webSettings } from './index.js'; + +export { metadata, name } from './index.js'; + +export const settings = { + ...webSettings, + __experimentalGetAccessibilityLabel( attributes ) { + return attributes.customText; + }, +}; diff --git a/packages/block-library/src/nextpage/edit.native.js b/packages/block-library/src/nextpage/edit.native.js index ae887204538769..e3aa69b15e5e41 100644 --- a/packages/block-library/src/nextpage/edit.native.js +++ b/packages/block-library/src/nextpage/edit.native.js @@ -33,6 +33,8 @@ export default function NextPageEdit( { attributes, isSelected, onFocus } ) { onAccessibilityTap={ onFocus } > <Hr text={ customText } + marginLeft={ 0 } + marginRight={ 0 } textStyle={ styles[ 'block-library-nextpage__text' ] } lineStyle={ styles[ 'block-library-nextpage__line' ] } /> </View> diff --git a/packages/block-library/src/nextpage/editor.native.scss b/packages/block-library/src/nextpage/editor.native.scss index ae6f1f3788df3b..869851fdd37c63 100644 --- a/packages/block-library/src/nextpage/editor.native.scss +++ b/packages/block-library/src/nextpage/editor.native.scss @@ -7,7 +7,6 @@ .block-library-nextpage__text { color: $gray; - font-family: $default-regular-font; text-decoration-style: solid; text-transform: uppercase; } From 77f7b38badef80de5cec758206b10783a34b5b9c Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Thu, 6 Jun 2019 17:55:16 +0200 Subject: [PATCH 276/664] Remove a call to a removed function. (#16018) This function is not needed anymore since selection is handed in the store --- packages/block-editor/src/components/rich-text/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index d2d3df25b5f253..1430702991dbdd 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -396,7 +396,6 @@ export class RichText extends Component { if ( start === 0 && end !== 0 && end >= value.text.length ) { newValue = remove( value, start, end ); this.props.onChange( newValue ); - this.forceSelectionUpdate( 0, 0 ); return; } From 2564c2122f75541cbdf4fcfdebdfc839dadeeb7a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 6 Jun 2019 18:27:01 +0100 Subject: [PATCH 277/664] Refactor the hovered area component to a hook (#15038) --- .../src/components/block-list/block.js | 461 +++++++++--------- .../src/components/block-list/hover-area.js | 99 ++-- 2 files changed, 259 insertions(+), 301 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 82a237625e6a77..aac68e2e6faa88 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -48,7 +48,7 @@ import BlockInsertionPoint from './insertion-point'; import IgnoreNestedEvents from '../ignore-nested-events'; import InserterWithShortcuts from '../inserter-with-shortcuts'; import Inserter from '../inserter'; -import HoverArea from './hover-area'; +import useHoveredArea from './hover-area'; import { isInsideRootBlock } from '../../utils/dom'; /** @@ -79,6 +79,7 @@ function BlockListBlock( { isParentOfSelectedBlock, isDraggable, isSelectionEnabled, + isRTL, className, name, isValid, @@ -105,13 +106,14 @@ function BlockListBlock( { const wrapper = useRef( null ); useEffect( () => { blockRef( wrapper.current, clientId ); - // We need to rerender to trigger a rerendering of HoverArea. - rerender(); }, [] ); // Reference to the block edit node const blockNodeRef = useRef(); + // Hovered area of the block + const hoverArea = useHoveredArea( wrapper ); + // Keep track of touchstart to disable hover on iOS const hadTouchStart = useRef( false ); const onTouchStart = () => { @@ -333,240 +335,236 @@ function BlockListBlock( { } }; + // Rendering the output + const isHovered = isBlockHovered && ! isPartOfMultiSelection; + const blockType = getBlockType( name ); + // translators: %s: Type of block (i.e. Text, Image etc) + const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); + // The block as rendered in the editor is composed of general block UI + // (mover, toolbar, wrapper) and the display of the block content. + + const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); + + // If the block is selected and we're typing the block should not appear. + // Empty paragraph blocks should always show up as unselected. + const showInserterShortcuts = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const showEmptyBlockSideInserter = ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid; + const shouldAppearSelected = + ! isFocusMode && + ! showEmptyBlockSideInserter && + isSelected && + ! isTypingWithinBlock; + const shouldAppearHovered = + ! isFocusMode && + ! hasFixedToolbar && + isHovered && + ! isEmptyDefaultBlock; + // We render block movers and block settings to keep them tabbale even if hidden + const shouldRenderMovers = + ( isSelected || hoverArea === ( isRTL ? 'right' : 'left' ) ) && + ! showEmptyBlockSideInserter && + ! isPartOfMultiSelection && + ! isTypingWithinBlock; + const shouldShowBreadcrumb = + ! isFocusMode && isHovered && ! isEmptyDefaultBlock; + const shouldShowContextualToolbar = + ! hasFixedToolbar && + ! showEmptyBlockSideInserter && + ( + ( isSelected && ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || + isFirstMultiSelected + ); + const shouldShowMobileToolbar = shouldAppearSelected; + + // Insertion point can only be made visible if the block is at the + // the extent of a multi-selection, or not in a multi-selection. + const shouldShowInsertionPoint = + ( isPartOfMultiSelection && isFirstMultiSelected ) || + ! isPartOfMultiSelection; + + // The wp-block className is important for editor styles. + // Generate the wrapper class names handling the different states of the block. + const wrapperClassName = classnames( + 'wp-block editor-block-list__block block-editor-block-list__block', + { + 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, + 'is-selected': shouldAppearSelected, + 'is-multi-selected': isPartOfMultiSelection, + 'is-hovered': shouldAppearHovered, + 'is-reusable': isReusableBlock( blockType ), + 'is-dragging': isDragging, + 'is-typing': isTypingWithinBlock, + 'is-focused': isFocusMode && ( isSelected || isParentOfSelectedBlock ), + 'is-focus-mode': isFocusMode, + }, + className + ); + + // Determine whether the block has props to apply to the wrapper. + let blockWrapperProps = wrapperProps; + if ( blockType.getEditWrapperProps ) { + blockWrapperProps = { + ...blockWrapperProps, + ...blockType.getEditWrapperProps( attributes ), + }; + } + const blockElementId = `block-${ clientId }`; + + // We wrap the BlockEdit component in a div that hides it when editing in + // HTML mode. This allows us to render all of the ancillary pieces + // (InspectorControls, etc.) which are inside `BlockEdit` but not + // `BlockHTML`, even in HTML mode. + let blockEdit = ( + <BlockEdit + name={ name } + isSelected={ isSelected } + attributes={ attributes } + setAttributes={ setAttributes } + insertBlocksAfter={ isLocked ? undefined : onInsertBlocksAfter } + onReplace={ isLocked ? undefined : onReplace } + mergeBlocks={ isLocked ? undefined : onMerge } + clientId={ clientId } + isSelectionEnabled={ isSelectionEnabled } + toggleSelection={ toggleSelection } + /> + ); + if ( mode !== 'visual' ) { + blockEdit = <div style={ { display: 'none' } }>{ blockEdit }</div>; + } + + // Disable reasons: + // + // jsx-a11y/mouse-events-have-key-events: + // - onMouseOver is explicitly handling hover effects + // + // jsx-a11y/no-static-element-interactions: + // - Each block can be selected by clicking on it + + /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + return ( - <HoverArea container={ wrapper.current }> - { ( { hoverArea } ) => { - const isHovered = isBlockHovered && ! isPartOfMultiSelection; - const blockType = getBlockType( name ); - // translators: %s: Type of block (i.e. Text, Image etc) - const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); - // The block as rendered in the editor is composed of general block UI - // (mover, toolbar, wrapper) and the display of the block content. - - const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); - - // If the block is selected and we're typing the block should not appear. - // Empty paragraph blocks should always show up as unselected. - const showInserterShortcuts = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; - const showEmptyBlockSideInserter = ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid; - const shouldAppearSelected = - ! isFocusMode && - ! showEmptyBlockSideInserter && + <IgnoreNestedEvents + id={ blockElementId } + ref={ wrapper } + onMouseOver={ maybeHover } + onMouseOverHandled={ hideHoverEffects } + onMouseLeave={ hideHoverEffects } + className={ wrapperClassName } + data-type={ name } + onTouchStart={ onTouchStart } + onFocus={ onFocus } + onClick={ onTouchStop } + onKeyDown={ deleteOrInsertAfterWrapper } + tabIndex="0" + aria-label={ blockLabel } + childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } + { ...blockWrapperProps } + > + { shouldShowInsertionPoint && ( + <BlockInsertionPoint + clientId={ clientId } + rootClientId={ rootClientId } + /> + ) } + <BlockDropZone + clientId={ clientId } + rootClientId={ rootClientId } + /> + { isFirstMultiSelected && ( + <BlockMultiControls rootClientId={ rootClientId } /> + ) } + <div className="editor-block-list__block-edit block-editor-block-list__block-edit"> + { shouldRenderMovers && ( + <BlockMover + clientIds={ clientId } + blockElementId={ blockElementId } + isHidden={ ! ( isHovered || isSelected ) || hoverArea !== ( isRTL ? 'right' : 'left' ) } + isDraggable={ + isDraggable !== false && + ( ! isPartOfMultiSelection && isMovable ) + } + onDragStart={ onDragStart } + onDragEnd={ onDragEnd } + /> + ) } + { shouldShowBreadcrumb && ( + <BlockBreadcrumb + clientId={ clientId } + isHidden={ + ! ( isHovered || isSelected ) || hoverArea !== ( isRTL ? 'right' : 'left' ) + } + /> + ) } + { ( shouldShowContextualToolbar || isForcingContextualToolbar.current ) && ( + <BlockContextualToolbar + // If the toolbar is being shown because of being forced + // it should focus the toolbar right after the mount. + focusOnMount={ isForcingContextualToolbar.current } + /> + ) } + { + ! shouldShowContextualToolbar && isSelected && - ! isTypingWithinBlock; - const shouldAppearHovered = - ! isFocusMode && - ! hasFixedToolbar && - isHovered && - ! isEmptyDefaultBlock; - // We render block movers and block settings to keep them tabbale even if hidden - const shouldRenderMovers = - ( isSelected || hoverArea === 'left' ) && - ! showEmptyBlockSideInserter && - ! isPartOfMultiSelection && - ! isTypingWithinBlock; - const shouldShowBreadcrumb = - ! isFocusMode && isHovered && ! isEmptyDefaultBlock; - const shouldShowContextualToolbar = ! hasFixedToolbar && - ! showEmptyBlockSideInserter && - ( ( isSelected && - ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || - isFirstMultiSelected ); - const shouldShowMobileToolbar = shouldAppearSelected; - - // Insertion point can only be made visible if the block is at the - // the extent of a multi-selection, or not in a multi-selection. - const shouldShowInsertionPoint = - ( isPartOfMultiSelection && isFirstMultiSelected ) || - ! isPartOfMultiSelection; - - // The wp-block className is important for editor styles. - // Generate the wrapper class names handling the different states of the block. - const wrapperClassName = classnames( - 'wp-block editor-block-list__block block-editor-block-list__block', - { - 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, - 'is-selected': shouldAppearSelected, - 'is-multi-selected': isPartOfMultiSelection, - 'is-hovered': shouldAppearHovered, - 'is-reusable': isReusableBlock( blockType ), - 'is-dragging': isDragging, - 'is-typing': isTypingWithinBlock, - 'is-focused': - isFocusMode && ( isSelected || isParentOfSelectedBlock ), - 'is-focus-mode': isFocusMode, - }, - className - ); - - // Determine whether the block has props to apply to the wrapper. - let blockWrapperProps = wrapperProps; - if ( blockType.getEditWrapperProps ) { - blockWrapperProps = { - ...blockWrapperProps, - ...blockType.getEditWrapperProps( attributes ), - }; + ! isEmptyDefaultBlock && ( + <KeyboardShortcuts + bindGlobal + eventName="keydown" + shortcuts={ { + 'alt+f10': forceFocusedContextualToolbar, + } } + /> + ) } - const blockElementId = `block-${ clientId }`; - - // We wrap the BlockEdit component in a div that hides it when editing in - // HTML mode. This allows us to render all of the ancillary pieces - // (InspectorControls, etc.) which are inside `BlockEdit` but not - // `BlockHTML`, even in HTML mode. - let blockEdit = ( - <BlockEdit - name={ name } - isSelected={ isSelected } - attributes={ attributes } - setAttributes={ setAttributes } - insertBlocksAfter={ isLocked ? undefined : onInsertBlocksAfter } - onReplace={ isLocked ? undefined : onReplace } - mergeBlocks={ isLocked ? undefined : onMerge } + <IgnoreNestedEvents + ref={ blockNodeRef } + onDragStart={ preventDrag } + onMouseDown={ onPointerDown } + data-block={ clientId } + > + <BlockCrashBoundary onError={ onBlockError }> + { isValid && blockEdit } + { isValid && mode === 'html' && ( + <BlockHtml clientId={ clientId } /> + ) } + { ! isValid && [ + <BlockInvalidWarning + key="invalid-warning" + clientId={ clientId } + />, + <div key="invalid-preview"> + { getSaveElement( blockType, attributes ) } + </div>, + ] } + </BlockCrashBoundary> + { shouldShowMobileToolbar && ( + <BlockMobileToolbar clientId={ clientId } /> + ) } + { !! hasError && <BlockCrashWarning /> } + </IgnoreNestedEvents> + </div> + { showInserterShortcuts && ( + <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> + <InserterWithShortcuts clientId={ clientId } - isSelectionEnabled={ isSelectionEnabled } - toggleSelection={ toggleSelection } + rootClientId={ rootClientId } + onToggle={ selectOnOpen } /> - ); - if ( mode !== 'visual' ) { - blockEdit = <div style={ { display: 'none' } }>{ blockEdit }</div>; - } - - // Disable reasons: - // - // jsx-a11y/mouse-events-have-key-events: - // - onMouseOver is explicitly handling hover effects - // - // jsx-a11y/no-static-element-interactions: - // - Each block can be selected by clicking on it - - /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - - return ( - <IgnoreNestedEvents - id={ blockElementId } - ref={ wrapper } - onMouseOver={ maybeHover } - onMouseOverHandled={ hideHoverEffects } - onMouseLeave={ hideHoverEffects } - className={ wrapperClassName } - data-type={ name } - onTouchStart={ onTouchStart } - onFocus={ onFocus } - onClick={ onTouchStop } - onKeyDown={ deleteOrInsertAfterWrapper } - tabIndex="0" - aria-label={ blockLabel } - childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } - { ...blockWrapperProps } - > - { shouldShowInsertionPoint && ( - <BlockInsertionPoint - clientId={ clientId } - rootClientId={ rootClientId } - /> - ) } - <BlockDropZone - clientId={ clientId } - rootClientId={ rootClientId } - /> - { isFirstMultiSelected && ( - <BlockMultiControls rootClientId={ rootClientId } /> - ) } - <div className="editor-block-list__block-edit block-editor-block-list__block-edit"> - { shouldRenderMovers && ( - <BlockMover - clientIds={ clientId } - blockElementId={ blockElementId } - isHidden={ ! ( isHovered || isSelected ) || hoverArea !== 'left' } - isDraggable={ - isDraggable !== false && - ( ! isPartOfMultiSelection && isMovable ) - } - onDragStart={ onDragStart } - onDragEnd={ onDragEnd } - /> - ) } - { shouldShowBreadcrumb && ( - <BlockBreadcrumb - clientId={ clientId } - isHidden={ - ! ( isHovered || isSelected ) || hoverArea !== 'left' - } - /> - ) } - { ( shouldShowContextualToolbar || - isForcingContextualToolbar.current ) && ( - <BlockContextualToolbar - // If the toolbar is being shown because of being forced - // it should focus the toolbar right after the mount. - focusOnMount={ isForcingContextualToolbar.current } - /> - ) } - { ! shouldShowContextualToolbar && - isSelected && - ! hasFixedToolbar && - ! isEmptyDefaultBlock && ( - <KeyboardShortcuts - bindGlobal - eventName="keydown" - shortcuts={ { - 'alt+f10': forceFocusedContextualToolbar, - } } - /> - ) } - <IgnoreNestedEvents - ref={ blockNodeRef } - onDragStart={ preventDrag } - onMouseDown={ onPointerDown } - data-block={ clientId } - > - <BlockCrashBoundary onError={ onBlockError }> - { isValid && blockEdit } - { isValid && mode === 'html' && ( - <BlockHtml clientId={ clientId } /> - ) } - { ! isValid && [ - <BlockInvalidWarning - key="invalid-warning" - clientId={ clientId } - />, - <div key="invalid-preview"> - { getSaveElement( blockType, attributes ) } - </div>, - ] } - </BlockCrashBoundary> - { shouldShowMobileToolbar && ( - <BlockMobileToolbar clientId={ clientId } /> - ) } - { !! hasError && <BlockCrashWarning /> } - </IgnoreNestedEvents> - </div> - { showInserterShortcuts && ( - <div className="editor-block-list__side-inserter block-editor-block-list__side-inserter"> - <InserterWithShortcuts - clientId={ clientId } - rootClientId={ rootClientId } - onToggle={ selectOnOpen } - /> - </div> - ) } - { showEmptyBlockSideInserter && ( - <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> - <Inserter - position="top right" - onToggle={ selectOnOpen } - rootClientId={ rootClientId } - clientId={ clientId } - /> - </div> - ) } - </IgnoreNestedEvents> - ); - /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - } } - </HoverArea> + </div> + ) } + { showEmptyBlockSideInserter && ( + <div className="editor-block-list__empty-block-inserter block-editor-block-list__empty-block-inserter"> + <Inserter + position="top right" + onToggle={ selectOnOpen } + rootClientId={ rootClientId } + clientId={ clientId } + /> + </div> + ) } + </IgnoreNestedEvents> ); + /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } const applyWithSelect = withSelect( @@ -590,7 +588,7 @@ const applyWithSelect = withSelect( } = select( 'core/block-editor' ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const isSelected = isBlockSelected( clientId ); - const { hasFixedToolbar, focusMode } = getSettings(); + const { hasFixedToolbar, focusMode, isRTL } = getSettings(); const templateLock = getTemplateLock( rootClientId ); const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); const index = getBlockIndex( clientId, rootClientId ); @@ -620,6 +618,7 @@ const applyWithSelect = withSelect( isFocusMode: focusMode && isLargeViewport, hasFixedToolbar: hasFixedToolbar && isLargeViewport, isLast: index === blockOrder.length - 1, + isRTL, // Users of the editor.BlockListBlock filter used to be able to access the block prop // Ideally these blocks would rely on the clientId prop only. diff --git a/packages/block-editor/src/components/block-list/hover-area.js b/packages/block-editor/src/components/block-list/hover-area.js index a79b0bcd9b088b..36664e3c9d797c 100644 --- a/packages/block-editor/src/components/block-list/hover-area.js +++ b/packages/block-editor/src/components/block-list/hover-area.js @@ -1,82 +1,41 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; +import { useState, useEffect } from '@wordpress/element'; -class HoverArea extends Component { - constructor() { - super( ...arguments ); - this.state = { - hoverArea: null, - }; - this.onMouseLeave = this.onMouseLeave.bind( this ); - this.onMouseMove = this.onMouseMove.bind( this ); - } - - componentWillUnmount() { - if ( this.props.container ) { - this.toggleListeners( this.props.container, false ); - } - } - - componentDidMount() { - if ( this.props.container ) { - this.toggleListeners( this.props.container ); - } - } - - componentDidUpdate( prevProps ) { - if ( prevProps.container === this.props.container ) { - return; - } - if ( prevProps.container ) { - this.toggleListeners( prevProps.container, false ); - } - if ( this.props.container ) { - this.toggleListeners( this.props.container, true ); - } - } +const useHoveredArea = ( wrapper ) => { + const [ hoveredArea, setHoveredArea ] = useState( null ); - toggleListeners( container, shouldListnerToEvents = true ) { - const method = shouldListnerToEvents ? 'addEventListener' : 'removeEventListener'; - container[ method ]( 'mousemove', this.onMouseMove ); - container[ method ]( 'mouseleave', this.onMouseLeave ); - } - - onMouseLeave() { - if ( this.state.hoverArea ) { - this.setState( { hoverArea: null } ); - } - } + useEffect( () => { + const onMouseLeave = () => { + if ( hoveredArea ) { + setHoveredArea( null ); + } + }; - onMouseMove( event ) { - const { isRTL, container } = this.props; - const { width, left, right } = container.getBoundingClientRect(); + const onMouseMove = ( event ) => { + const { width, left, right } = wrapper.current.getBoundingClientRect(); - let hoverArea = null; - if ( ( event.clientX - left ) < width / 3 ) { - hoverArea = isRTL ? 'right' : 'left'; - } else if ( ( right - event.clientX ) < width / 3 ) { - hoverArea = isRTL ? 'left' : 'right'; - } + let newHoveredArea = null; + if ( ( event.clientX - left ) < width / 3 ) { + newHoveredArea = 'left'; + } else if ( ( right - event.clientX ) < width / 3 ) { + newHoveredArea = 'right'; + } - if ( hoverArea !== this.state.hoverArea ) { - this.setState( { hoverArea } ); - } - } + setHoveredArea( newHoveredArea ); + }; - render() { - const { hoverArea } = this.state; - const { children } = this.props; + wrapper.current.addEventListener( 'mousemove', onMouseMove ); + wrapper.current.addEventListener( 'mouseleave', onMouseLeave ); - return children( { hoverArea } ); - } -} + return () => { + wrapper.current.removeEventListener( 'mousemove', onMouseMove ); + wrapper.current.removeEventListener( 'mouseleave', onMouseLeave ); + }; + }, [] ); -export default withSelect( ( select ) => { - return { - isRTL: select( 'core/block-editor' ).getSettings().isRTL, - }; -} )( HoverArea ); + return hoveredArea; +}; +export default useHoveredArea; From d3c13700a386fc1d56a50704b750e262d88a7e6f Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 6 Jun 2019 15:09:23 -0400 Subject: [PATCH 278/664] Framework: Introduce "Milestone It" GitHub action (#15826) * Framework: Introduce "Milestone It" GitHub action * Framework: Send milestone request result to the abyss * Framework: Reset minor to zero at next major * Framework: Include milestone description text * Framework: Abort milestone assignment if one is applied * Framework: Ensure entrypoint is executable * Framework: Install ca-certificates to avoid cURL errors * Framework: Use bash shell to avoid redirect error https://stackoverflow.com/questions/2462317/bash-syntax-error-redirection-unexpected * Framework: Compute due date for milestone * Framework: Remove debugging output * Framework: Search all milestone array members for title --- .github/actions/milestone-it/Dockerfile | 18 +++++ .github/actions/milestone-it/entrypoint.sh | 81 ++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 .github/actions/milestone-it/Dockerfile create mode 100755 .github/actions/milestone-it/entrypoint.sh diff --git a/.github/actions/milestone-it/Dockerfile b/.github/actions/milestone-it/Dockerfile new file mode 100644 index 00000000000000..af20456bcc34e5 --- /dev/null +++ b/.github/actions/milestone-it/Dockerfile @@ -0,0 +1,18 @@ +FROM debian:stable-slim + +LABEL "name"="Milestone It" +LABEL "maintainer"="The WordPress Contributors" +LABEL "version"="1.0.0" + +LABEL "com.github.actions.name"="Milestone It" +LABEL "com.github.actions.description"="Assigns a pull request to the next milestone" +LABEL "com.github.actions.icon"="flag" +LABEL "com.github.actions.color"="green" + +RUN apt-get update && \ + apt-get install --no-install-recommends -y jq curl ca-certificates && \ + apt-get clean -y + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/.github/actions/milestone-it/entrypoint.sh b/.github/actions/milestone-it/entrypoint.sh new file mode 100755 index 00000000000000..a0b94110f532a2 --- /dev/null +++ b/.github/actions/milestone-it/entrypoint.sh @@ -0,0 +1,81 @@ +#!/bin/bash +set -e + +# 1. Determine if milestone already exists (don't replace one which has already +# been assigned). + +pr=$(jq -r '.number' $GITHUB_EVENT_PATH) + +current_milestone=$( + curl \ + --silent \ + -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/issues/$pr" \ + | jq '.milestone' +) + +if [ "$current_milestone" != 'null' ]; then + echo 'Milestone already applied. Aborting.' + exit 1; +fi + +# 2. Read current version. + +version=$(jq -r '.version' package.json) + +IFS='.' read -ra parts <<< "$version" +major=${parts[0]} +minor=${parts[1]} + +# 3. Determine next milestone. + +if [ minor == '9' ]; then + major=$((major+1)) + minor="0" +else + minor=$((minor+1)) +fi + +milestone="Gutenberg $major.$minor" + +# 4. Calculate next milestone due date, using a static reference of an earlier +# release (v5.0) as a reference point for the biweekly release schedule. + +reference_major=5 +reference_minor=0 +reference_date=1549238400 +num_versions_elapsed=$(((major-reference_major)*10+(minor-reference_minor))) +weeks=$((num_versions_elapsed*2)) +due=$(date -u --iso-8601=seconds -d "$(date -d @$(echo $reference_date)) + $(echo $weeks) weeks") + +# 5. Create milestone. This may fail for duplicates, which is expected and +# ignored. + +curl \ + --silent \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"title\":\"$milestone\",\"due_on\":\"$due\",\"description\":\"Tasks to be included in the $milestone plugin release.\"}" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/milestones" > /dev/null + +# 6. Find milestone number. This could be improved to allow for non-open status +# or paginated results. + +number=$( + curl \ + --silent \ + -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/milestones" \ + | jq ".[] | select(.title == \"$milestone\") | .number" +) + +# 7. Assign pull request to milestone. + +curl \ + --silent \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"milestone\":$number}" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/issues/$pr" > /dev/null From aafab3e71b9587f57bd23d5b818f32cd04929556 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 6 Jun 2019 15:14:31 -0400 Subject: [PATCH 279/664] Add workflow "Milestone merged pull requests" --- .github/main.workflow | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/main.workflow diff --git a/.github/main.workflow b/.github/main.workflow new file mode 100644 index 00000000000000..885e4e0bcf69dc --- /dev/null +++ b/.github/main.workflow @@ -0,0 +1,15 @@ +workflow "Milestone merged pull requests" { + on = "pull_request" + resolves = ["Milestone It"] +} + +action "Filter merged" { + uses = "actions/bin/filter@3c0b4f0e63ea54ea5df2914b4fabf383368cd0da" + args = "merged true" +} + +action "Milestone It" { + uses = "./actions/milestone-it/Dockerfile" + needs = ["Filter merged"] + secrets = ["GITHUB_TOKEN"] +} From 2d66c599a7b316ab17e67b9ade40144413f7699c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 7 Jun 2019 09:06:28 +0200 Subject: [PATCH 280/664] Format Library: Remove secondary (access) shortcuts (#15191) * Format Library: Remove secondary (access) shortcuts * Update help modal * Fix tests --- .../__snapshots__/rich-text.test.js.snap | 6 ----- packages/e2e-tests/specs/rich-text.test.js | 9 ------- .../keyboard-shortcut-help-modal/config.js | 12 ++------- .../test/__snapshots__/index.js.snap | 26 +++---------------- packages/format-library/src/code/index.js | 23 +++++----------- packages/format-library/src/link/index.js | 10 ------- .../format-library/src/strikethrough/index.js | 23 +++++----------- 7 files changed, 19 insertions(+), 90 deletions(-) diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap index 8aab0dcd5b734b..3c7044b5fb8053 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -6,12 +6,6 @@ exports[`RichText should apply formatting when selection is collapsed 1`] = ` <!-- /wp:paragraph -->" `; -exports[`RichText should apply formatting with access shortcut 1`] = ` -"<!-- wp:paragraph --> -<p><s>test</s></p> -<!-- /wp:paragraph -->" -`; - exports[`RichText should apply formatting with primary shortcut 1`] = ` "<!-- wp:paragraph --> <p><strong>test</strong></p> diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index 1bd6d79c1b10fc..6dce07f78146ca 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -27,15 +27,6 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'should apply formatting with access shortcut', async () => { - await clickBlockAppender(); - await page.keyboard.type( 'test' ); - await pressKeyWithModifier( 'primary', 'a' ); - await pressKeyWithModifier( 'access', 'd' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - it( 'should apply formatting with primary shortcut', async () => { await clickBlockAppender(); await page.keyboard.type( 'test' ); diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js index 5c6037c0467f4b..340efee1c5d55d 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js @@ -132,10 +132,6 @@ const textFormattingShortcuts = { keyCombination: primary( 'i' ), description: __( 'Make the selected text italic.' ), }, - { - keyCombination: primary( 'u' ), - description: __( 'Underline the selected text.' ), - }, { keyCombination: primary( 'k' ), description: __( 'Convert the selected text into a link.' ), @@ -145,12 +141,8 @@ const textFormattingShortcuts = { description: __( 'Remove a link.' ), }, { - keyCombination: access( 'd' ), - description: __( 'Add a strikethrough to the selected text.' ), - }, - { - keyCombination: access( 'x' ), - description: __( 'Display the selected text in a monospaced font.' ), + keyCombination: primary( 'u' ), + description: __( 'Underline the selected text.' ), }, ], }; diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap index fe522a985d3b9a..e08b7e6501cdcb 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap @@ -235,14 +235,6 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ "I", ], }, - Object { - "description": "Underline the selected text.", - "keyCombination": Array [ - "Ctrl", - "+", - "U", - ], - }, Object { "description": "Convert the selected text into a link.", "keyCombination": Array [ @@ -262,23 +254,11 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ ], }, Object { - "description": "Add a strikethrough to the selected text.", - "keyCombination": Array [ - "Shift", - "+", - "Alt", - "+", - "D", - ], - }, - Object { - "description": "Display the selected text in a monospaced font.", + "description": "Underline the selected text.", "keyCombination": Array [ - "Shift", - "+", - "Alt", + "Ctrl", "+", - "X", + "U", ], }, ] diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index aaccbde3946f49..4eb3cfb1118ef8 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut, RichTextToolbarButton } from '@wordpress/block-editor'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; const name = 'core/code'; const title = __( 'Inline Code' ); @@ -17,21 +17,12 @@ export const code = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <> - <RichTextShortcut - type="access" - character="x" - onUse={ onToggle } - /> - <RichTextToolbarButton - icon="editor-code" - title={ title } - onClick={ onToggle } - isActive={ isActive } - shortcutType="access" - shortcutCharacter="x" - /> - </> + <RichTextToolbarButton + icon="editor-code" + title={ title } + onClick={ onToggle } + isActive={ isActive } + /> ); }, }; diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index 913285f2e8b0f3..f68d4932f62420 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -71,16 +71,6 @@ export const link = { return ( <> - <RichTextShortcut - type="access" - character="a" - onUse={ this.addLink } - /> - <RichTextShortcut - type="access" - character="s" - onUse={ this.onRemoveFormat } - /> <RichTextShortcut type="primary" character="k" diff --git a/packages/format-library/src/strikethrough/index.js b/packages/format-library/src/strikethrough/index.js index 244b2787fb4d04..23c17f1fea4a8d 100644 --- a/packages/format-library/src/strikethrough/index.js +++ b/packages/format-library/src/strikethrough/index.js @@ -3,7 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; const name = 'core/strikethrough'; const title = __( 'Strikethrough' ); @@ -17,21 +17,12 @@ export const strikethrough = { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); return ( - <> - <RichTextShortcut - type="access" - character="d" - onUse={ onToggle } - /> - <RichTextToolbarButton - icon="editor-strikethrough" - title={ title } - onClick={ onToggle } - isActive={ isActive } - shortcutType="access" - shortcutCharacter="d" - /> - </> + <RichTextToolbarButton + icon="editor-strikethrough" + title={ title } + onClick={ onToggle } + isActive={ isActive } + /> ); }, }; From f410c46874f9a211f4094c67b1ee3f497a88d54a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 7 Jun 2019 09:09:39 +0100 Subject: [PATCH 281/664] Adds an is-busy state to the save button of the widgets page (#16019) --- .../src/components/header/index.js | 24 +++++--------- .../src/components/save-button/index.js | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 packages/edit-widgets/src/components/save-button/index.js diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js index 3dc3f650e9d8ea..f18519fc3d5fcc 100644 --- a/packages/edit-widgets/src/components/header/index.js +++ b/packages/edit-widgets/src/components/header/index.js @@ -1,12 +1,14 @@ /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; -import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { withDispatch } from '@wordpress/data'; -function Header( { saveWidgetAreas } ) { +/** + * Internal dependencies + */ +import SaveButton from '../save-button'; + +function Header() { return ( <div className="edit-widgets-header" @@ -19,20 +21,10 @@ function Header( { saveWidgetAreas } ) { </h1> <div className="edit-widgets-header__actions"> - <Button isPrimary isLarge onClick={ saveWidgetAreas }> - { __( 'Update' ) } - </Button> + <SaveButton /> </div> </div> ); } -export default compose( [ - withDispatch( ( dispatch ) => { - const { saveWidgetAreas } = dispatch( 'core/edit-widgets' ); - return { - saveWidgetAreas, - }; - } ), -] )( Header ); - +export default Header; diff --git a/packages/edit-widgets/src/components/save-button/index.js b/packages/edit-widgets/src/components/save-button/index.js new file mode 100644 index 00000000000000..dd9040356d95ee --- /dev/null +++ b/packages/edit-widgets/src/components/save-button/index.js @@ -0,0 +1,31 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { useState, useCallback } from '@wordpress/element'; + +function SaveButton() { + const [ isSaving, setIsSaving ] = useState( false ); + const { saveWidgetAreas } = useDispatch( 'core/edit-widgets' ); + const onClick = useCallback( async () => { + setIsSaving( true ); + await saveWidgetAreas(); + setIsSaving( false ); + }, [] ); + + return ( + <Button + isPrimary + isLarge + isBusy={ isSaving } + aria-disabled={ isSaving } + onClick={ isSaving ? undefined : onClick } + > + { __( 'Update' ) } + </Button> + ); +} + +export default SaveButton; From 1fbcd783f779641ea03fabbfdeac475349617700 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Fri, 7 Jun 2019 12:23:19 +0200 Subject: [PATCH 282/664] [RNMobile] Fix for extra BR tag on Title field on Android (#16021) * Remove extra BR tag that may happen at the end of the Title field on Android when pressing Enter.key * Make sure to set `deleteEnter={ true }` so that Android does delete the new line / BR tag on enter pressed --- packages/editor/src/components/post-title/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 0bb3591fa52ecc..07e6fe1be2c6c4 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -95,6 +95,7 @@ class PostTitle extends Component { style={ style } fontSize={ 24 } fontWeight={ 'bold' } + deleteEnter={ true } onChange={ ( value ) => { this.props.onUpdate( value ); } } From d9debc1454a15d4c13e76823306da29f9f37739c Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Fri, 7 Jun 2019 13:31:02 +0300 Subject: [PATCH 283/664] [Mobile]Open video/quote/more blocks to public (#16031) * Remove unregistration of video, more, quote blocks * Update code comment --- packages/edit-post/src/index.native.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index cc5dfaec90ae1d..1abd926f26bc8c 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -18,13 +18,10 @@ export function initializeEditor() { // register and setup blocks registerCoreBlocks(); - // disable Code and More blocks for the release + // disable Code block for the release // eslint-disable-next-line no-undef if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); - unregisterBlockType( 'core/more' ); - unregisterBlockType( 'core/video' ); - unregisterBlockType( 'core/quote' ); } } From ace322feafa74e9a27ca82a788a391e66277ed92 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 7 Jun 2019 11:31:30 +0100 Subject: [PATCH 284/664] Add: Format library to the widget screen. (#15948) --- lib/widgets-page.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/widgets-page.php b/lib/widgets-page.php index 858866c02f1755..f790e60d24b7a1 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -68,6 +68,8 @@ function gutenberg_widgets_init( $hook ) { 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');' ); wp_enqueue_script( 'wp-edit-widgets' ); + wp_enqueue_script( 'wp-format-library' ); wp_enqueue_style( 'wp-edit-widgets' ); + wp_enqueue_style( 'wp-format-library' ); } add_action( 'admin_enqueue_scripts', 'gutenberg_widgets_init' ); From 0fff1c5379e74196dc4b948cdc1414d7922fa6a2 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 7 Jun 2019 12:16:13 +0100 Subject: [PATCH 285/664] Fix: DateTimePicker passes current seconds as the selected seconds value (#15495) DateTimePicker does not allow users to pick a value for seconds, so when the user changes a time/date, it should look like a value of 0 was selected as the seconds value. Currently, the seconds of the current date are passed as the selected seconds, which may make it look like a "random" value was selected as the seconds value. This causes a regression where WP_CRON missed some scheduled posts. --- packages/components/CHANGELOG.md | 3 ++ packages/components/src/date-time/date.js | 2 +- packages/components/src/date-time/time.js | 37 ++++++++++++----------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 5fc3cba4b28b46..c1a4e6fef2b5f7 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -17,6 +17,9 @@ - `ServerSideRender` is no longer part of components. It was extracted to an independent package `@wordpress/server-side-render`. +### Bug Fix + +- Although `DateTimePicker` does not allow picking the seconds, passed the current seconds as the selected value for seconds when calling `onChange`. Now it passes zero. ## 7.4.0 (2019-05-21) diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index a8e4130e739c8c..0d5058789f17f6 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -30,7 +30,7 @@ class DatePicker extends Component { const momentTime = { hours: momentDate.hours(), minutes: momentDate.minutes(), - seconds: momentDate.seconds(), + seconds: 0, }; onChange( newDate.set( momentTime ).format( TIMEZONELESS_FORMAT ) ); diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 55937b10170319..0cfc8c7dc8ea68 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -33,6 +33,7 @@ class TimePicker extends Component { am: true, date: null, }; + this.changeDate = this.changeDate.bind( this ); this.updateMonth = this.updateMonth.bind( this ); this.onChangeMonth = this.onChangeMonth.bind( this ); this.updateDay = this.updateDay.bind( this ); @@ -61,6 +62,17 @@ class TimePicker extends Component { this.syncState( this.props ); } } + /** + * Function that sets the date state and calls the onChange with a new date. + * The date is truncated at the minutes. + * + * @param {Object} newDate The date object. + */ + changeDate( newDate ) { + const dateWithStartOfMinutes = newDate.clone().startOf( 'minute' ); + this.setState( { date: dateWithStartOfMinutes } ); + this.props.onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + } getMaxHours() { return this.props.is12Hour ? 12 : 23; @@ -83,7 +95,7 @@ class TimePicker extends Component { } updateHours() { - const { is12Hour, onChange } = this.props; + const { is12Hour } = this.props; const { am, hours, date } = this.state; const value = parseInt( hours, 10 ); if ( @@ -98,12 +110,10 @@ class TimePicker extends Component { const newDate = is12Hour ? date.clone().hours( am === 'AM' ? value % 12 : ( ( ( value % 12 ) + 12 ) % 24 ) ) : date.clone().hours( value ); - this.setState( { date: newDate } ); - onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + this.changeDate( newDate ); } updateMinutes() { - const { onChange } = this.props; const { minutes, date } = this.state; const value = parseInt( minutes, 10 ); if ( ! isInteger( value ) || value < 0 || value > 59 ) { @@ -111,12 +121,10 @@ class TimePicker extends Component { return; } const newDate = date.clone().minutes( value ); - this.setState( { date: newDate } ); - onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + this.changeDate( newDate ); } updateDay() { - const { onChange } = this.props; const { day, date } = this.state; const value = parseInt( day, 10 ); if ( ! isInteger( value ) || value < 1 || value > 31 ) { @@ -124,12 +132,10 @@ class TimePicker extends Component { return; } const newDate = date.clone().date( value ); - this.setState( { date: newDate } ); - onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + this.changeDate( newDate ); } updateMonth() { - const { onChange } = this.props; const { month, date } = this.state; const value = parseInt( month, 10 ); if ( ! isInteger( value ) || value < 1 || value > 12 ) { @@ -137,12 +143,10 @@ class TimePicker extends Component { return; } const newDate = date.clone().month( value - 1 ); - this.setState( { date: newDate } ); - onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + this.changeDate( newDate ); } updateYear() { - const { onChange } = this.props; const { year, date } = this.state; const value = parseInt( year, 10 ); if ( ! isInteger( value ) || value < 0 || value > 9999 ) { @@ -150,13 +154,11 @@ class TimePicker extends Component { return; } const newDate = date.clone().year( value ); - this.setState( { date: newDate } ); - onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + this.changeDate( newDate ); } updateAmPm( value ) { return () => { - const { onChange } = this.props; const { am, date, hours } = this.state; if ( am === value ) { return; @@ -167,8 +169,7 @@ class TimePicker extends Component { } else { newDate = date.clone().hours( parseInt( hours, 10 ) % 12 ); } - this.setState( { date: newDate } ); - onChange( newDate.format( TIMEZONELESS_FORMAT ) ); + this.changeDate( newDate ); }; } From 9febd559adfc7eb705852d59cc1aafbd6d991d52 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Fri, 7 Jun 2019 08:08:38 -0400 Subject: [PATCH 286/664] Add improved "Group" and "Ungroup" icons (#16001) * Update Group block icon * Update Group/Ungroup action icons --- packages/block-library/src/group/icon.js | 2 +- .../src/components/convert-to-group-buttons/icons.js | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/block-library/src/group/icon.js b/packages/block-library/src/group/icon.js index 959b844350615f..e1b36f7f72cadb 100644 --- a/packages/block-library/src/group/icon.js +++ b/packages/block-library/src/group/icon.js @@ -4,5 +4,5 @@ import { Path, SVG } from '@wordpress/components'; export default ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M19 12h-2v3h-3v2h5v-5zM7 9h3V7H5v5h2V9zm14-6H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16.01H3V4.99h18v14.02z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG> + <SVG width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill-rule="evenodd" clip-rule="evenodd" d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" /><Path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" /></SVG> ); diff --git a/packages/editor/src/components/convert-to-group-buttons/icons.js b/packages/editor/src/components/convert-to-group-buttons/icons.js index 8ca249c2fa7ee6..d1ee497662cd49 100644 --- a/packages/editor/src/components/convert-to-group-buttons/icons.js +++ b/packages/editor/src/components/convert-to-group-buttons/icons.js @@ -3,17 +3,11 @@ */ import { Icon, SVG, Path } from '@wordpress/components'; -const GroupSVG = <SVG width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <Path fillRule="evenodd" clipRule="evenodd" d="M10 4H18C19.1 4 20 4.9 20 6V14C20 15.1 19.1 16 18 16H10C8.9 16 8 15.1 8 14V6C8 4.9 8.9 4 10 4ZM10 14H18V6H10V14Z" /> - <Path d="M6 8C4.9 8 4 8.9 4 10V18C4 19.1 4.9 20 6 20H14C15.1 20 16 19.1 16 18H6V8Z" /> -</SVG>; +const GroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fill-rule="evenodd" clip-rule="evenodd" d="M8 5a1 1 0 0 0-1 1v3H6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3h1a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H8zm3 6H7v2h4v-2zM9 9V7h4v2H9z" /><Path fill-rule="evenodd" clip-rule="evenodd" d="M1 3a2 2 0 0 0 1 1.732v10.536A2 2 0 1 0 4.732 18h10.536A2 2 0 1 0 18 15.268V4.732A2 2 0 1 0 15.268 2H4.732A2 2 0 0 0 1 3zm14.268 1H4.732A2.01 2.01 0 0 1 4 4.732v10.536c.304.175.557.428.732.732h10.536a2.01 2.01 0 0 1 .732-.732V4.732A2.01 2.01 0 0 1 15.268 4z" /></SVG>; export const Group = <Icon icon={ GroupSVG } />; -const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> - <Path fillRule="evenodd" clipRule="evenodd" d="M12 2H20C21.1 2 22 2.9 22 4V12C22 13.1 21.1 14 20 14H12C10.9 14 10 13.1 10 12V4C10 2.9 10.9 2 12 2ZM12 12H20V4H12V12Z" /> - <Path d="M4 10H8V12H4V20H12V16H14V20C14 21.1 13.1 22 12 22H4C2.9 22 2 21.1 2 20V12C2 10.9 2.9 10 4 10Z" /> -</SVG>; +const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fill-rule="evenodd" clip-rule="evenodd" d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" /><Path fill-rule="evenodd" clip-rule="evenodd" d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" /></SVG>; export const Ungroup = <Icon icon={ UngroupSVG } />; From 27cc2635d24b38e84113ea790faa66d646777da2 Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Fri, 7 Jun 2019 05:15:05 -0700 Subject: [PATCH 287/664] Spacer block: Add clear:both; to clear any floats around it (#15874) * Fixes #15099. Adds a to the Spacer block so that it clears any floated blocks and functions as a Spacer block should. * Minor lint-style fix. * Moved the import file into alphabetical position. --- packages/block-library/src/spacer/editor.scss | 1 + packages/block-library/src/spacer/style.scss | 3 +++ packages/block-library/src/style.scss | 1 + 3 files changed, 5 insertions(+) create mode 100644 packages/block-library/src/spacer/style.scss diff --git a/packages/block-library/src/spacer/editor.scss b/packages/block-library/src/spacer/editor.scss index 77fba2118222a7..b20657931c97ef 100644 --- a/packages/block-library/src/spacer/editor.scss +++ b/packages/block-library/src/spacer/editor.scss @@ -3,5 +3,6 @@ } .block-library-spacer__resize-container { + clear: both; margin-bottom: $default-block-margin; } diff --git a/packages/block-library/src/spacer/style.scss b/packages/block-library/src/spacer/style.scss new file mode 100644 index 00000000000000..e6b5ee9d5085bd --- /dev/null +++ b/packages/block-library/src/spacer/style.scss @@ -0,0 +1,3 @@ +.wp-block-spacer { + clear: both; +} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 7ebad4a855135e..d33dd3c393c3b1 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -17,6 +17,7 @@ @import "./rss/style.scss"; @import "./search/style.scss"; @import "./separator/style.scss"; +@import "./spacer/style.scss"; @import "./subhead/style.scss"; @import "./table/style.scss"; @import "./text-columns/style.scss"; From 3daee7a84a737a42b1b3233a8239de0375158d51 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 7 Jun 2019 08:29:53 -0400 Subject: [PATCH 288/664] Fix "Milestone It" workflow container path --- .github/main.workflow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/main.workflow b/.github/main.workflow index 885e4e0bcf69dc..a714ca269ada60 100644 --- a/.github/main.workflow +++ b/.github/main.workflow @@ -9,7 +9,7 @@ action "Filter merged" { } action "Milestone It" { - uses = "./actions/milestone-it/Dockerfile" + uses = "./.github/actions/milestone-it" needs = ["Filter merged"] secrets = ["GITHUB_TOKEN"] } From 72badf245dc32ed25ba81a63efb14156c68b4d48 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 7 Jun 2019 15:03:57 +0100 Subject: [PATCH 289/664] Remove @wordpress/format-library dependency from edit-post (#16034) The edit-post does not use the format-library package. The package is loaded by core and calls the correct mechanism to register the formats being used, but the edit-post package does not directly depend on format-library. --- packages/edit-post/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 74a706fffc4dc1..a2566dd750a75f 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -33,7 +33,6 @@ "@wordpress/data": "file:../data", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", - "@wordpress/format-library": "file:../format-library", "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", From 60b667ef7bcadec8a938ac7aef9a264fb0c6ff25 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 7 Jun 2019 11:57:26 -0400 Subject: [PATCH 290/664] Framework: Update package-lock.json per #16034 --- package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 7352bce3c4f67c..c8943767a50d28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3528,7 +3528,6 @@ "@wordpress/data": "file:packages/data", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", - "@wordpress/format-library": "file:packages/format-library", "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", From 92fcaa82ba9a6d4663fabe98565a0eba4f4bcfc8 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 7 Jun 2019 17:44:06 +0100 Subject: [PATCH 291/664] Update increase tooltip selector specificity (#16043) --- packages/components/src/tooltip/style.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/src/tooltip/style.scss b/packages/components/src/tooltip/style.scss index 79739693294c30..93642af8cf2052 100644 --- a/packages/components/src/tooltip/style.scss +++ b/packages/components/src/tooltip/style.scss @@ -12,6 +12,10 @@ &.is-bottom::after { border-bottom-color: $dark-gray-900; } + + &:not(.is-mobile) .components-popover__content { + min-width: 0; + } } .components-tooltip .components-popover__content { @@ -23,10 +27,6 @@ text-align: center; } -.components-tooltip:not(.is-mobile) .components-popover__content { - min-width: 0; -} - .components-tooltip__shortcut { display: block; color: $dark-gray-200; From 28eb941c91d23c91e377de63e56865864ac9f3ca Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 7 Jun 2019 13:46:02 -0400 Subject: [PATCH 292/664] Block API: Downgrade `convert` API to experimental (#16047) --- packages/block-library/src/group/index.js | 2 +- packages/blocks/CHANGELOG.md | 1 - packages/blocks/src/api/factory.js | 8 ++++---- packages/blocks/src/api/test/factory.js | 8 ++++---- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index 386ab20a80955c..844ddd8c821600 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -33,7 +33,7 @@ export const settings = { type: 'block', isMultiBlock: true, blocks: [ '*' ], - convert( blocks ) { + __experimentalConvert( blocks ) { // Avoid transforming a single `core/group` Block if ( blocks.length === 1 && blocks[ 0 ].name === 'core/group' ) { return; diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index df5b183e688d45..8f50c3899591da 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -4,7 +4,6 @@ - Added a default implementation for `save` setting in `registerBlockType` which saves no markup in the post content. - Added wildcard block transforms which allows for transforming all/any blocks in another block. -- Added `convert()` method option to `transforms` definition. It receives complete block object(s) as it's argument(s). It is now preferred over the older `transform()` (note that `transform()` is still fully supported). ## 6.1.0 (2019-03-06) diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 304329d0be657f..92074c35a6bafd 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -404,16 +404,16 @@ export function switchToBlockType( blocks, name ) { let transformationResults; if ( transformation.isMultiBlock ) { - if ( has( transformation, 'convert' ) ) { - transformationResults = transformation.convert( blocksArray ); + if ( has( transformation, '__experimentalConvert' ) ) { + transformationResults = transformation.__experimentalConvert( blocksArray ); } else { transformationResults = transformation.transform( blocksArray.map( ( currentBlock ) => currentBlock.attributes ), blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ), ); } - } else if ( has( transformation, 'convert' ) ) { - transformationResults = transformation.convert( firstBlock ); + } else if ( has( transformation, '__experimentalConvert' ) ) { + transformationResults = transformation.__experimentalConvert( firstBlock ); } else { transformationResults = transformation.transform( firstBlock.attributes, firstBlock.innerBlocks ); } diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index 3e0d42c103bb9a..973964bfe05bae 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -1301,7 +1301,7 @@ describe( 'block factory', () => { expect( transformedBlocks[ 1 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after1' ); } ); - it( 'should pass entire block object(s) to the "convert" method if defined', () => { + it( 'should pass entire block object(s) to the "__experimentalConvert" method if defined', () => { registerBlockType( 'core/test-group-block', { attributes: { value: { @@ -1313,7 +1313,7 @@ describe( 'block factory', () => { type: 'block', blocks: [ '*' ], isMultiBlock: true, - convert( blocks ) { + __experimentalConvert( blocks ) { const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { return createBlock( name, attributes, innerBlocks ); } ); @@ -1343,7 +1343,7 @@ describe( 'block factory', () => { expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( numOfBlocksToGroup ); } ); - it( 'should prefer "convert" method over "transform" method when running a transformation', () => { + it( 'should prefer "__experimentalConvert" method over "transform" method when running a transformation', () => { const convertSpy = jest.fn( ( blocks ) => { const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { return createBlock( name, attributes, innerBlocks ); @@ -1364,7 +1364,7 @@ describe( 'block factory', () => { type: 'block', blocks: [ '*' ], isMultiBlock: true, - convert: convertSpy, + __experimentalConvert: convertSpy, transform: transformSpy, } ], }, From 42b27486c11c2dd937b81234e3db27169e53e6e5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 7 Jun 2019 14:06:36 -0400 Subject: [PATCH 293/664] Revert "adds selection persistence on sidebar tab switch (#15583)" (#16042) This reverts commit 4d5998d7b8558c94c6030d167111c11f92c5b46a. --- .../developers/data/data-core-block-editor.md | 21 +------- .../block-selection-clearer/index.js | 8 +-- packages/block-editor/src/store/actions.js | 27 +--------- packages/block-editor/src/store/reducer.js | 15 +----- .../block-editor/src/store/test/actions.js | 18 ------- .../block-editor/src/store/test/reducer.js | 1 - packages/e2e-tests/specs/a11y.test.js | 51 ------------------- .../sidebar/settings-header/index.js | 4 +- packages/edit-post/src/store/effects.js | 2 +- 9 files changed, 9 insertions(+), 138 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index f765454e948878..6e496446d6be20 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -824,9 +824,7 @@ _Returns_ <a name="clearSelectedBlock" href="#clearSelectedBlock">#</a> **clearSelectedBlock** -Returns an action object used in signaling that the block selection is cleared. -This will save the current selection in a state called `previousSelection` and -`restoreSelectedBlock` will be able to restore the selection. +Returns an action object used in signalling that the block selection is cleared. _Returns_ @@ -1041,14 +1039,6 @@ _Returns_ - `Object`: Action object. -<a name="restoreSelectedBlock" href="#restoreSelectedBlock">#</a> **restoreSelectedBlock** - -Returns an action object used in restoring the previously cleared selected blocks. - -_Returns_ - -- `Object`: Action object. - <a name="selectBlock" href="#selectBlock">#</a> **selectBlock** Returns an action object used in signalling that the block with the @@ -1235,13 +1225,4 @@ _Returns_ Undocumented declaration. -<a name="wipeSelectedBlock" href="#wipeSelectedBlock">#</a> **wipeSelectedBlock** - -Returns an action object used in signaling that the block selection is wiped. -This will remove block selection so that `restoreSelectedBlock` will have no effect. - -_Returns_ - -- `Object`: Action object. - <!-- END TOKEN(Autogenerated actions) --> diff --git a/packages/block-editor/src/components/block-selection-clearer/index.js b/packages/block-editor/src/components/block-selection-clearer/index.js index cc2e27944e06bd..be39e5df9e7de5 100644 --- a/packages/block-editor/src/components/block-selection-clearer/index.js +++ b/packages/block-editor/src/components/block-selection-clearer/index.js @@ -33,12 +33,12 @@ class BlockSelectionClearer extends Component { const { hasSelectedBlock, hasMultiSelection, - wipeSelectedBlock, + clearSelectedBlock, } = this.props; const hasSelection = ( hasSelectedBlock || hasMultiSelection ); if ( event.target === this.container && hasSelection ) { - wipeSelectedBlock(); + clearSelectedBlock(); } } @@ -68,7 +68,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { wipeSelectedBlock } = dispatch( 'core/block-editor' ); - return { wipeSelectedBlock }; + const { clearSelectedBlock } = dispatch( 'core/block-editor' ); + return { clearSelectedBlock }; } ), ] )( BlockSelectionClearer ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index e1e4c1f69ed040..4dafaa291fcf31 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -193,21 +193,7 @@ export function multiSelect( start, end ) { } /** - * Returns an action object used in signaling that the block selection is wiped. - * This will remove block selection so that `restoreSelectedBlock` will have no effect. - * - * @return {Object} Action object. - */ -export function wipeSelectedBlock() { - return { - type: 'WIPE_SELECTED_BLOCK', - }; -} - -/** - * Returns an action object used in signaling that the block selection is cleared. - * This will save the current selection in a state called `previousSelection` and - * `restoreSelectedBlock` will be able to restore the selection. + * Returns an action object used in signalling that the block selection is cleared. * * @return {Object} Action object. */ @@ -217,17 +203,6 @@ export function clearSelectedBlock() { }; } -/** - * Returns an action object used in restoring the previously cleared selected blocks. - * - * @return {Object} Action object. - */ -export function restoreSelectedBlock() { - return { - type: 'RESTORE_SELECTED_BLOCK', - }; -} - /** * Returns an action object that enables or disables block selection. * diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index e62734d0412e2c..d9d0ecbdd28b64 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -717,21 +717,8 @@ const BLOCK_SELECTION_INITIAL_STATE = { */ export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action ) { switch ( action.type ) { - case 'WIPE_SELECTED_BLOCK': - return BLOCK_SELECTION_INITIAL_STATE; case 'CLEAR_SELECTED_BLOCK': - if ( isEqual( state, BLOCK_SELECTION_INITIAL_STATE ) ) { - return BLOCK_SELECTION_INITIAL_STATE; - } - return { - ...BLOCK_SELECTION_INITIAL_STATE, - previousSelection: omit( state, [ 'previousSelection' ] ), - }; - case 'RESTORE_SELECTED_BLOCK': - return { - ...BLOCK_SELECTION_INITIAL_STATE, - ...state.previousSelection, - }; + return BLOCK_SELECTION_INITIAL_STATE; case 'START_MULTI_SELECT': if ( state.isMultiSelecting ) { return state; diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 93123c17bf987b..1fb97cd38d9bde 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -3,8 +3,6 @@ */ import { clearSelectedBlock, - wipeSelectedBlock, - restoreSelectedBlock, enterFormattedText, exitFormattedText, hideInsertionPoint, @@ -118,22 +116,6 @@ describe( 'actions', () => { } ); } ); - describe( 'wipeSelectedBlock', () => { - it( 'should return WIPE_SELECTED_BLOCK action', () => { - expect( wipeSelectedBlock() ).toEqual( { - type: 'WIPE_SELECTED_BLOCK', - } ); - } ); - } ); - - describe( 'restoreSelectedBlock', () => { - it( 'should return RESTORE_SELECTED_BLOCK action', () => { - expect( restoreSelectedBlock() ).toEqual( { - type: 'RESTORE_SELECTED_BLOCK', - } ); - } ); - } ); - describe( 'replaceBlock', () => { it( 'should yield the REPLACE_BLOCKS action if the new block can be inserted in the destination root block', () => { const block = { diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index b0d79d8911292e..7543fdad57dd8b 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -1740,7 +1740,6 @@ describe( 'state', () => { initialPosition: null, isMultiSelecting: false, isEnabled: true, - previousSelection: original, } ); } ); diff --git a/packages/e2e-tests/specs/a11y.test.js b/packages/e2e-tests/specs/a11y.test.js index 784ee4dc556743..6f6cc960f204aa 100644 --- a/packages/e2e-tests/specs/a11y.test.js +++ b/packages/e2e-tests/specs/a11y.test.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { - clickBlockAppender, createNewPost, pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; @@ -30,56 +29,6 @@ describe( 'a11y', () => { expect( isFocusedToggle ).toBe( true ); } ); - it( 'checks persistent selection', async () => { - await clickBlockAppender(); - - // adding one Paragraph block which contains a focusable RichText - await page.keyboard.type( 'Testing editor selection persistence' ); - - let isFocusedRichText = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.classList.contains( 'block-editor-rich-text__editable' ); - } ); - - expect( isFocusedRichText ).toBe( true ); - - // moving focus backwards using keyboard shortcuts - // twice to get to the inspector tabs - await pressKeyWithModifier( 'ctrlShift', '`' ); - await pressKeyWithModifier( 'ctrlShift', '`' ); - - await page.keyboard.press( 'Tab' ); - - const isFocusedInspectorDocumentTab = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.getAttribute( 'data-label' ); - } ); - - expect( isFocusedInspectorDocumentTab ).toEqual( 'Document' ); - - await page.keyboard.press( 'Space' ); - - isFocusedRichText = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.classList.contains( 'block-editor-rich-text__editable' ); - } ); - - expect( isFocusedRichText ).toBe( false ); - - await page.keyboard.press( 'Tab' ); - - const isFocusedInspectorBlockTab = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.getAttribute( 'data-label' ); - } ); - - expect( isFocusedInspectorBlockTab ).toEqual( 'Block' ); - - await page.keyboard.press( 'Space' ); - - isFocusedRichText = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.classList.contains( 'block-editor-rich-text__editable' ); - } ); - - expect( isFocusedRichText ).toBe( true ); - } ); - it( 'constrains focus to a modal when tabbing', async () => { // Open keyboard help modal. await pressKeyWithModifier( 'access', 'h' ); diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index 47d00bce72048a..eeb95a872166f5 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -57,8 +57,7 @@ const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName export default withDispatch( ( dispatch ) => { const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - const { clearSelectedBlock, restoreSelectedBlock } = dispatch( 'core/block-editor' ); - + const { clearSelectedBlock } = dispatch( 'core/block-editor' ); return { openDocumentSettings() { openGeneralSidebar( 'edit-post/document' ); @@ -66,7 +65,6 @@ export default withDispatch( ( dispatch ) => { }, openBlockSettings() { openGeneralSidebar( 'edit-post/block' ); - restoreSelectedBlock(); }, }; } )( SettingsHeader ); diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index 36d3c2b501369c..54bfe07fbd67b5 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -109,7 +109,7 @@ const effects = { SWITCH_MODE( action ) { // Unselect blocks when we switch to the code editor. if ( action.mode !== 'visual' ) { - dispatch( 'core/block-editor' ).wipeSelectedBlock(); + dispatch( 'core/block-editor' ).clearSelectedBlock(); } const message = action.mode === 'visual' ? __( 'Visual editor selected' ) : __( 'Code editor selected' ); From cbac39d35e0dc2bd2a6906ffaa9b621bb8fd0b9b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Sat, 8 Jun 2019 00:09:56 +0100 Subject: [PATCH 294/664] Add availability checks to widget related code (#15983) This commit adds availability checks for the function get_current_screen and for the globals wp_widget_factory, wp_registered_widgets. The function get_current_screen should only be called in admin pages, but according to the docs, we may have admin pages without the function so I think a check should be added. For example, I guess it may be possible to have an admin page with admin_footer but without the function defined. Regarding the globals wp_widget_factory, wp_registered_widgets used in gutenberg_get_legacy_widget_settings function, the function is called as part of the filter block_editor_settings, I guess the filter may be executed in a situation where the widget globals are not initialized e.g: in a custom plugin page with a custom block editor. I think it is better to check the availability of the globals. If the globals are not available we will not pass available legacy widgets to the front end. --- lib/widgets.php | 60 ++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/lib/widgets.php b/lib/widgets.php index 7ea5d3a5e770dd..3c528b14469271 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -11,6 +11,11 @@ * @return boolean True if a screen containing the block editor is being loaded. */ function gutenberg_is_block_editor() { + // If get_current_screen does not exist, we are neither in the standard block editor for posts, or the widget block editor. + // We can safely return false. + if ( ! function_exists( 'get_current_screen' ) ) { + return false; + } $screen = get_current_screen(); return $screen->is_block_editor() || 'gutenberg_page_gutenberg-widgets' === $screen->id; } @@ -100,33 +105,38 @@ function gutenberg_get_legacy_widget_settings() { $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); $available_legacy_widgets = array(); - global $wp_widget_factory, $wp_registered_widgets; - foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { - $available_legacy_widgets[ $class ] = array( - 'name' => html_entity_decode( $widget_obj->name ), - // wp_widget_description is not being used because its input parameter is a Widget Id. - // Widgets id's reference to a specific widget instance. - // Here we are iterating on all the available widget classes even if no widget instance exists for them. - 'description' => isset( $widget_obj->widget_options['description'] ) ? - html_entity_decode( $widget_obj->widget_options['description'] ) : - null, - 'isCallbackWidget' => false, - 'isHidden' => in_array( $class, $core_widgets, true ), - ); + global $wp_widget_factory; + if ( ! empty( $wp_widget_factory ) ) { + foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { + $available_legacy_widgets[ $class ] = array( + 'name' => html_entity_decode( $widget_obj->name ), + // wp_widget_description is not being used because its input parameter is a Widget Id. + // Widgets id's reference to a specific widget instance. + // Here we are iterating on all the available widget classes even if no widget instance exists for them. + 'description' => isset( $widget_obj->widget_options['description'] ) ? + html_entity_decode( $widget_obj->widget_options['description'] ) : + null, + 'isCallbackWidget' => false, + 'isHidden' => in_array( $class, $core_widgets, true ), + ); + } } - foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { - if ( - is_array( $widget_obj['callback'] ) && - isset( $widget_obj['callback'][0] ) && - ( $widget_obj['callback'][0] instanceof WP_Widget ) - ) { - continue; + global $wp_registered_widgets; + if ( ! empty( $wp_registered_widgets ) ) { + foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { + if ( + is_array( $widget_obj['callback'] ) && + isset( $widget_obj['callback'][0] ) && + ( $widget_obj['callback'][0] instanceof WP_Widget ) + ) { + continue; + } + $available_legacy_widgets[ $widget_id ] = array( + 'name' => html_entity_decode( $widget_obj['name'] ), + 'description' => html_entity_decode( wp_widget_description( $widget_id ) ), + 'isCallbackWidget' => true, + ); } - $available_legacy_widgets[ $widget_id ] = array( - 'name' => html_entity_decode( $widget_obj['name'] ), - 'description' => html_entity_decode( wp_widget_description( $widget_id ) ), - 'isCallbackWidget' => true, - ); } $settings['hasPermissionsToManageWidgets'] = $has_permissions_to_manage_widgets; From a57202e635ff0cd190a09128d27951620db9a870 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Sat, 8 Jun 2019 02:43:42 +0300 Subject: [PATCH 295/664] [RNMobile] keyboard focus issues (#15999) * Focus RichText on mount if block is selected and says so Some blocks have multiple RichText or a RichText among other children. Example: Quote blocks has 2 RichTexts and Image block has a RichText for the caption. We want to control when and which of those RichTexts will request focus. On the web side, the DOM is used to search for a inputbox to focus but on RN, we don't have a DOM to search. Instead, this commit makes the assumption that a RichText always request focus if its parent has passed `true` in `isSelected` and only if the parent hasn't inhibited that behavior by using the `noFocusOnMount` prop. * Simplify the passing of noFocusOnMount * Focus and selection logic closer to GB web's * RichText's API uses unstableOnFocus at the moment * Don't force selection update if position is unset That can happen when the RichText based block gets tapped somewhere and the caret position is not updated yet from Aztec. --- .../src/components/rich-text/index.native.js | 28 +++++++++++-------- .../block-library/src/heading/edit.native.js | 3 -- .../block-library/src/image/edit.native.js | 2 +- .../src/paragraph/edit.native.js | 3 -- .../src/components/post-title/index.native.js | 2 +- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 1430702991dbdd..d9fc545e8fa9f1 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -531,10 +531,18 @@ export class RichText extends Component { onFocus() { this.isTouched = true; - if ( this.props.onFocus ) { - this.props.onFocus(); + const { unstableOnFocus } = this.props; + + if ( unstableOnFocus ) { + unstableOnFocus(); } + // We know for certain that on focus, the old selection is invalid. It + // will be recalculated on `selectionchange`. + const index = undefined; + + this.props.onSelectionChange( index, index ); + this.lastAztecEventType = 'focus'; } @@ -657,7 +665,9 @@ export class RichText extends Component { } if ( ! this.comesFromAztec ) { - if ( nextProps.selectionStart !== this.props.selectionStart && + if ( ( typeof nextProps.selectionStart !== 'undefined' ) && + ( typeof nextProps.selectionEnd !== 'undefined' ) && + nextProps.selectionStart !== this.props.selectionStart && nextProps.selectionStart !== this.selectionStart && nextProps.isSelected ) { this.needsSelectionUpdate = true; @@ -681,7 +691,7 @@ export class RichText extends Component { componentWillUnmount() { if ( this._editor.isFocused() ) { - this._editor.blur(); + // this._editor.blur(); } } @@ -695,7 +705,7 @@ export class RichText extends Component { // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange // if its internal value hasn't change. When created, default value is 0, 0 this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); - } else if ( ! this.props.isSelected && prevProps.isSelected && this.isIOS ) { + } else if ( ! this.props.isSelected && prevProps.isSelected ) { this._editor.blur(); } } @@ -810,7 +820,6 @@ export class RichText extends Component { onContentSizeChange={ this.onContentSizeChange } onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } onSelectionChange={ this.onSelectionChangeFromAztec } - isSelected={ isSelected } blockType={ { tag: tagName } } color={ 'black' } maxImagesWidth={ 200 } @@ -836,15 +845,10 @@ RichText.defaultProps = { const RichTextContainer = compose( [ withInstanceId, - withBlockEditContext( ( { clientId, onFocus, onCaretVerticalPositionChange, isSelected }, ownProps ) => { - // ownProps.onFocus needs precedence over the block edit context - if ( ownProps.onFocus !== undefined ) { - onFocus = ownProps.onFocus; - } + withBlockEditContext( ( { clientId, onCaretVerticalPositionChange, isSelected }, ownProps ) => { return { clientId, blockIsSelected: ownProps.isSelected !== undefined ? ownProps.isSelected : isSelected, - onFocus, onCaretVerticalPositionChange, }; } ), diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index e7d295402c92e1..1f409356b12a1f 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -39,13 +39,10 @@ const HeadingEdit = ( { identifier="content" tagName={ 'h' + attributes.level } value={ attributes.content } - isSelected={ isSelected } style={ { ...style, minHeight: styles[ 'wp-block-heading' ].minHeight, } } - onFocus={ onFocus } // always assign onFocus as a props - onBlur={ onBlur } // always assign onBlur as a props onChange={ ( value ) => setAttributes( { content: value } ) } onMerge={ mergeBlocks } onSplit={ ( value ) => { diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 61a0adf3635ce2..cb0cc5a9302630 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -361,7 +361,7 @@ class ImageEdit extends React.Component { placeholder={ __( 'Write caption…' ) } value={ caption } onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } - onFocus={ this.onFocusCaption } + unstableOnFocus={ this.onFocusCaption } onBlur={ this.props.onBlur } // always assign onBlur as props isSelected={ this.state.isCaptionSelected } __unstableMobileNoFocusOnMount diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 717677658e5a15..6dbcaf8a1459dd 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -57,9 +57,6 @@ class ParagraphEdit extends Component { identifier="content" tagName="p" value={ content } - isSelected={ this.props.isSelected } - onFocus={ this.props.onFocus } // always assign onFocus as a props - onBlur={ this.props.onBlur } // always assign onBlur as a props deleteEnter={ true } style={ style } onChange={ ( nextContent ) => { diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 07e6fe1be2c6c4..c895abcbebc108 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -89,7 +89,7 @@ class PostTitle extends Component { <RichText tagName={ 'p' } rootTagsToEliminate={ [ 'strong' ] } - onFocus={ this.onSelect } + unstableOnFocus={ this.onSelect } onBlur={ this.props.onBlur } // always assign onBlur as a props multiline={ false } style={ style } From 3550aad006ac58feccdfc94c75422e95a10d14d6 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Mon, 10 Jun 2019 03:31:46 -0400 Subject: [PATCH 296/664] [docs] document missing prop in MenuItem (#16061) --- packages/components/src/menu-item/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/components/src/menu-item/README.md b/packages/components/src/menu-item/README.md index 38251ccedf2614..1741f8102992f3 100644 --- a/packages/components/src/menu-item/README.md +++ b/packages/components/src/menu-item/README.md @@ -32,8 +32,6 @@ MenuItem supports the following props. Any additional props are passed through t Element to render as child of button. -Element - ### `info` - Type: `string` @@ -50,6 +48,13 @@ Refer to documentation for [`label`](#label). Refer to documentation for [IconButton's `icon` prop](/packages/components/src/icon-button/README.md#icon). +### `isSelected` + +- Type: `boolean` +- Required: No + +Whether or not the menu item is currently selected. + ### `shortcut` - Type: `string` From 2afa08f1eb85000aeb0e2e5d7298850d21542a2e Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Mon, 10 Jun 2019 06:36:53 -0400 Subject: [PATCH 297/664] [docs] fix incorect prop name in KeyboardShortcuts (#16059) * [docs] fix incorect prop name in KeyboardShortcuts * [docs] KeyboardShortcuts - shortcuts prop is required --- packages/components/src/keyboard-shortcuts/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/src/keyboard-shortcuts/README.md b/packages/components/src/keyboard-shortcuts/README.md index 4e1bb1a9dd73f3..7c1046087d9b38 100644 --- a/packages/components/src/keyboard-shortcuts/README.md +++ b/packages/components/src/keyboard-shortcuts/README.md @@ -48,13 +48,13 @@ Elements to render, upon whom key events are to be monitored. An object of shortcut bindings, where each key is a keyboard combination, the value of which is the callback to be invoked when the key combination is pressed. - Type: `Object` -- Required: No +- Required: Yes __Note:__ The value of each shortcut should be a consistent function reference, not an anonymous function. Otherwise, the callback will not be correctly unbound when the component unmounts. __Note:__ The `KeyboardShortcuts` component will not update to reflect a changed `shortcuts` prop. If you need to change shortcuts, mount a separate `KeyboardShortcuts` element, which can be achieved by assigning a unique `key` prop. -## bindGlobal +### bindGlobal By default, a callback will not be invoked if the key combination occurs in an editable field. Pass `bindGlobal` as `true` if the key events should be observed globally, including within editable fields. @@ -63,9 +63,9 @@ By default, a callback will not be invoked if the key combination occurs in an e _Tip:_ If you need some but not all keyboard events to be observed globally, simply render two distinct `KeyboardShortcuts` elements, one with and one without the `bindGlobal` prop. -## event +### eventName -By default, a callback is invoked in response to the `keydown` event. To override this, pass `event` with the name of a specific keyboard event. +By default, a callback is invoked in response to the `keydown` event. To override this, pass `eventName` with the name of a specific keyboard event. - Type: `String` - Required: No From a8e96920032ab4f9ccb38c29de34f90c296ed7a7 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 10 Jun 2019 11:42:51 +0100 Subject: [PATCH 298/664] Deprecated the selectors/actions and components that moved to the block editor module (#15770) --- packages/e2e-test-utils/README.md | 2 +- packages/e2e-test-utils/src/get-all-blocks.js | 4 +- .../src/select-block-by-client-id.js | 2 +- .../e2e-test-utils/src/set-post-content.js | 2 +- packages/e2e-tests/plugins/align-hook.php | 2 +- .../e2e-tests/plugins/align-hook/index.js | 2 +- packages/e2e-tests/plugins/block-icons.php | 2 +- .../e2e-tests/plugins/block-icons/index.js | 2 +- .../plugins/container-without-paragraph.php | 2 +- .../container-without-paragraph/index.js | 4 +- .../plugins/deprecated-node-matcher.php | 2 +- .../plugins/deprecated-node-matcher/index.js | 2 +- packages/e2e-tests/plugins/format-api.php | 2 +- .../e2e-tests/plugins/format-api/index.js | 2 +- packages/e2e-tests/plugins/hooks-api.php | 2 +- packages/e2e-tests/plugins/hooks-api/index.js | 2 +- .../plugins/inner-blocks-allowed-blocks.php | 2 +- .../inner-blocks-allowed-blocks/index.js | 4 +- .../plugins/inner-blocks-templates.php | 2 +- .../plugins/inner-blocks-templates/index.js | 2 +- packages/e2e-tests/plugins/plugins-api.php | 3 +- .../plugins-api/annotations-sidebar.js | 4 +- .../e2e-tests/plugins/plugins-api/sidebar.js | 2 +- .../e2e-tests/specs/reusable-blocks.test.js | 4 +- packages/editor/CHANGELOG.md | 147 ++++++++++- packages/editor/src/components/deprecated.js | 246 ++++++++++-------- packages/editor/src/store/actions.js | 3 + packages/editor/src/store/selectors.js | 4 + 28 files changed, 316 insertions(+), 143 deletions(-) diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 2a3954c47e4b9b..d8f14e5e6c343b 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -207,7 +207,7 @@ _Returns_ <a name="getAllBlocks" href="#getAllBlocks">#</a> **getAllBlocks** -Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/editor' ).getBlocks(); +Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/block-editor' ).getBlocks(); _Returns_ diff --git a/packages/e2e-test-utils/src/get-all-blocks.js b/packages/e2e-test-utils/src/get-all-blocks.js index 6f87f827615cba..7aca29bbdcecc2 100644 --- a/packages/e2e-test-utils/src/get-all-blocks.js +++ b/packages/e2e-test-utils/src/get-all-blocks.js @@ -4,10 +4,10 @@ import { wpDataSelect } from './wp-data-select'; /** - * Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/editor' ).getBlocks(); + * Returns an array with all blocks; Equivalent to calling wp.data.select( 'core/block-editor' ).getBlocks(); * * @return {Promise} Promise resolving with an array containing all blocks in the document. */ export function getAllBlocks() { - return wpDataSelect( 'core/editor', 'getBlocks' ); + return wpDataSelect( 'core/block-editor', 'getBlocks' ); } diff --git a/packages/e2e-test-utils/src/select-block-by-client-id.js b/packages/e2e-test-utils/src/select-block-by-client-id.js index cf7e4624d621fc..cfcf8b2bc54d5f 100644 --- a/packages/e2e-test-utils/src/select-block-by-client-id.js +++ b/packages/e2e-test-utils/src/select-block-by-client-id.js @@ -5,6 +5,6 @@ */ export async function selectBlockByClientId( clientId ) { await page.evaluate( ( id ) => { - wp.data.dispatch( 'core/editor' ).selectBlock( id ); + wp.data.dispatch( 'core/block-editor' ).selectBlock( id ); }, clientId ); } diff --git a/packages/e2e-test-utils/src/set-post-content.js b/packages/e2e-test-utils/src/set-post-content.js index 62398b646fc532..18e47c55820033 100644 --- a/packages/e2e-test-utils/src/set-post-content.js +++ b/packages/e2e-test-utils/src/set-post-content.js @@ -8,6 +8,6 @@ export async function setPostContent( content ) { return await page.evaluate( ( _content ) => { const { dispatch } = window.wp.data; const blocks = wp.blocks.parse( _content ); - dispatch( 'core/editor' ).resetBlocks( blocks ); + dispatch( 'core/block-editor' ).resetBlocks( blocks ); }, content ); } diff --git a/packages/e2e-tests/plugins/align-hook.php b/packages/e2e-tests/plugins/align-hook.php index 89435541e3d2ce..dbf5a3cb83a681 100644 --- a/packages/e2e-tests/plugins/align-hook.php +++ b/packages/e2e-tests/plugins/align-hook.php @@ -17,7 +17,7 @@ function enqueue_align_plugin_script() { array( 'wp-blocks', 'wp-element', - 'wp-editor', + 'wp-block-editor', 'wp-i18n', ), filemtime( plugin_dir_path( __FILE__ ) . 'align-hook/index.js' ), diff --git a/packages/e2e-tests/plugins/align-hook/index.js b/packages/e2e-tests/plugins/align-hook/index.js index 927c491e843717..0380758fbaaf05 100644 --- a/packages/e2e-tests/plugins/align-hook/index.js +++ b/packages/e2e-tests/plugins/align-hook/index.js @@ -1,7 +1,7 @@ ( function() { var registerBlockType = wp.blocks.registerBlockType; var el = wp.element.createElement; - var InnerBlocks = wp.editor.InnerBlocks; + var InnerBlocks = wp.blockEditor.InnerBlocks; var __ = wp.i18n.__; var TEMPLATE = [ [ 'core/paragraph', { fontSize: 'large', content: __( 'Content…' ) } ], diff --git a/packages/e2e-tests/plugins/block-icons.php b/packages/e2e-tests/plugins/block-icons.php index 6f79bf7d7e1d6a..ce1013f041c889 100644 --- a/packages/e2e-tests/plugins/block-icons.php +++ b/packages/e2e-tests/plugins/block-icons.php @@ -18,7 +18,7 @@ function enqueue_block_icons_plugin_script() { 'wp-blocks', 'wp-components', 'wp-element', - 'wp-editor', + 'wp-block-editor', 'wp-hooks', 'wp-i18n', ), diff --git a/packages/e2e-tests/plugins/block-icons/index.js b/packages/e2e-tests/plugins/block-icons/index.js index 705b03ebfbc91b..fe05c1d23dcb33 100644 --- a/packages/e2e-tests/plugins/block-icons/index.js +++ b/packages/e2e-tests/plugins/block-icons/index.js @@ -1,7 +1,7 @@ ( function() { var registerBlockType = wp.blocks.registerBlockType; var el = wp.element.createElement; - var InnerBlocks = wp.editor.InnerBlocks; + var InnerBlocks = wp.blockEditor.InnerBlocks; var circle = el( 'circle', { cx: 10, cy: 10, r: 10, fill: 'red', stroke: 'blue', strokeWidth: '10' } ); var svg = el( 'svg', { width: 20, height: 20, viewBox: '0 0 20 20' }, circle ); diff --git a/packages/e2e-tests/plugins/container-without-paragraph.php b/packages/e2e-tests/plugins/container-without-paragraph.php index 315e3eb4258e41..59c06641ce0419 100644 --- a/packages/e2e-tests/plugins/container-without-paragraph.php +++ b/packages/e2e-tests/plugins/container-without-paragraph.php @@ -17,7 +17,7 @@ function enqueue_container_without_paragraph_plugin_script() { array( 'wp-blocks', 'wp-element', - 'wp-editor', + 'wp-block-editor', ), filemtime( plugin_dir_path( __FILE__ ) . 'container-without-paragraph/index.js' ), true diff --git a/packages/e2e-tests/plugins/container-without-paragraph/index.js b/packages/e2e-tests/plugins/container-without-paragraph/index.js index 03eec8105a10aa..7ec20eef893ace 100644 --- a/packages/e2e-tests/plugins/container-without-paragraph/index.js +++ b/packages/e2e-tests/plugins/container-without-paragraph/index.js @@ -5,13 +5,13 @@ icon: 'yes', edit() { - return wp.element.createElement(wp.editor.InnerBlocks, { + return wp.element.createElement(wp.blockEditor.InnerBlocks, { allowedBlocks: ['core/image', 'core/gallery'] }); }, save() { - return wp.element.createElement(wp.editor.InnerBlocks.Content); + return wp.element.createElement(wp.blockEditor.InnerBlocks.Content); }, }) } )(); diff --git a/packages/e2e-tests/plugins/deprecated-node-matcher.php b/packages/e2e-tests/plugins/deprecated-node-matcher.php index 964cc7eec9c362..7e0a6e3d2c5578 100644 --- a/packages/e2e-tests/plugins/deprecated-node-matcher.php +++ b/packages/e2e-tests/plugins/deprecated-node-matcher.php @@ -18,7 +18,7 @@ function enqueue_deprecated_node_matcher_plugin_script() { 'lodash', 'wp-blocks', 'wp-element', - 'wp-editor', + 'wp-block-editor', ), filemtime( plugin_dir_path( __FILE__ ) . 'deprecated-node-matcher/index.js' ), true diff --git a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js index 5f41ed3325afd9..c49c1df95b8360 100644 --- a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js +++ b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js @@ -1,6 +1,6 @@ ( function() { var registerBlockType = wp.blocks.registerBlockType; - var RichText = wp.editor.RichText; + var RichText = wp.blockEditor.RichText; var el = wp.element.createElement; var el = wp.element.createElement; diff --git a/packages/e2e-tests/plugins/format-api.php b/packages/e2e-tests/plugins/format-api.php index dc33462affbc89..cd03a89e76aa48 100644 --- a/packages/e2e-tests/plugins/format-api.php +++ b/packages/e2e-tests/plugins/format-api.php @@ -14,7 +14,7 @@ function gutenberg_test_format_api_scripts() { wp_enqueue_script( 'gutenberg-test-format-api', plugins_url( 'format-api/index.js', __FILE__ ), - array( 'wp-editor', 'wp-element', 'wp-rich-text' ), + array( 'wp-block-editor', 'wp-element', 'wp-rich-text' ), filemtime( plugin_dir_path( __FILE__ ) . 'format-api/index.js' ), true ); diff --git a/packages/e2e-tests/plugins/format-api/index.js b/packages/e2e-tests/plugins/format-api/index.js index 671767e2049269..d22f696737ecdc 100644 --- a/packages/e2e-tests/plugins/format-api/index.js +++ b/packages/e2e-tests/plugins/format-api/index.js @@ -9,7 +9,7 @@ className: 'my-plugin-link', edit: function( props ) { return wp.element.createElement( - wp.editor.RichTextToolbarButton, { + wp.blockEditor.RichTextToolbarButton, { icon: 'admin-links', title: 'Custom Link', onClick: function() { diff --git a/packages/e2e-tests/plugins/hooks-api.php b/packages/e2e-tests/plugins/hooks-api.php index f3533d8d86748c..c4b6d70b95f182 100644 --- a/packages/e2e-tests/plugins/hooks-api.php +++ b/packages/e2e-tests/plugins/hooks-api.php @@ -18,7 +18,7 @@ function enqueue_hooks_plugin_script() { 'wp-blocks', 'wp-components', 'wp-element', - 'wp-editor', + 'wp-block-editor', 'wp-hooks', 'wp-i18n', ), diff --git a/packages/e2e-tests/plugins/hooks-api/index.js b/packages/e2e-tests/plugins/hooks-api/index.js index eb45316565f386..3cfe4ca1590322 100644 --- a/packages/e2e-tests/plugins/hooks-api/index.js +++ b/packages/e2e-tests/plugins/hooks-api/index.js @@ -3,7 +3,7 @@ var Fragment = wp.element.Fragment; var Button = wp.components.Button; var PanelBody = wp.components.PanelBody; - var InspectorControls = wp.editor.InspectorControls; + var InspectorControls = wp.blockEditor.InspectorControls; var addFilter = wp.hooks.addFilter; var createBlock = wp.blocks.createBlock; var __ = wp.i18n.__; diff --git a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php index 8dcf752ba7df44..a8a0e6ee9a254c 100644 --- a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php +++ b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks.php @@ -16,7 +16,7 @@ function enqueue_inner_blocks_allowed_blocks_script() { plugins_url( 'inner-blocks-allowed-blocks/index.js', __FILE__ ), array( 'wp-blocks', - 'wp-editor', + 'wp-block-editor', 'wp-element', 'wp-i18n', ), diff --git a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js index 2b1ba6eadd2c1b..f6f61c27d5c82c 100644 --- a/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js +++ b/packages/e2e-tests/plugins/inner-blocks-allowed-blocks/index.js @@ -2,7 +2,7 @@ const { withSelect } = wp.data; const { registerBlockType } = wp.blocks; const { createElement: el } = wp.element; - const { InnerBlocks } = wp.editor; + const { InnerBlocks } = wp.blockEditor; const __ = wp.i18n.__; const divProps = { className: 'product', style: { outline: '1px solid gray', padding: 5 } }; const template = [ @@ -64,7 +64,7 @@ category: 'common', edit: withSelect( function( select, ownProps ) { - var getBlockOrder = select( 'core/editor' ).getBlockOrder; + var getBlockOrder = select( 'core/block-editor' ).getBlockOrder; return { numberOfChildren: getBlockOrder( ownProps.clientId ).length, }; diff --git a/packages/e2e-tests/plugins/inner-blocks-templates.php b/packages/e2e-tests/plugins/inner-blocks-templates.php index 792a2dc0234d15..212992e3619623 100644 --- a/packages/e2e-tests/plugins/inner-blocks-templates.php +++ b/packages/e2e-tests/plugins/inner-blocks-templates.php @@ -18,7 +18,7 @@ function enqueue_container_without_paragraph_plugin_script() { 'wp-blocks', 'wp-components', 'wp-element', - 'wp-editor', + 'wp-block-editor', 'wp-hooks', 'wp-i18n', ), diff --git a/packages/e2e-tests/plugins/inner-blocks-templates/index.js b/packages/e2e-tests/plugins/inner-blocks-templates/index.js index 6c0b62c51fea4e..7d3662f17dbfc4 100644 --- a/packages/e2e-tests/plugins/inner-blocks-templates/index.js +++ b/packages/e2e-tests/plugins/inner-blocks-templates/index.js @@ -2,7 +2,7 @@ var registerBlockType = wp.blocks.registerBlockType; var createBlock = wp.blocks.createBlock; var el = wp.element.createElement; - var InnerBlocks = wp.editor.InnerBlocks; + var InnerBlocks = wp.blockEditor.InnerBlocks; var __ = wp.i18n.__; var TEMPLATE = [ [ 'core/paragraph', { diff --git a/packages/e2e-tests/plugins/plugins-api.php b/packages/e2e-tests/plugins/plugins-api.php index 75c1180e44afc6..a1c91596faea52 100644 --- a/packages/e2e-tests/plugins/plugins-api.php +++ b/packages/e2e-tests/plugins/plugins-api.php @@ -45,6 +45,7 @@ function enqueue_plugins_api_plugin_scripts() { 'wp-compose', 'wp-data', 'wp-edit-post', + 'wp-block-editor', 'wp-editor', 'wp-element', 'wp-i18n', @@ -63,7 +64,7 @@ function enqueue_plugins_api_plugin_scripts() { 'wp-compose', 'wp-data', 'wp-edit-post', - 'wp-editor', + 'wp-block-editor', 'wp-element', 'wp-i18n', 'wp-plugins', diff --git a/packages/e2e-tests/plugins/plugins-api/annotations-sidebar.js b/packages/e2e-tests/plugins/plugins-api/annotations-sidebar.js index 5dfa0e6a52888b..88ff01965f9762 100644 --- a/packages/e2e-tests/plugins/plugins-api/annotations-sidebar.js +++ b/packages/e2e-tests/plugins/plugins-api/annotations-sidebar.js @@ -7,7 +7,7 @@ var withSelect = wp.data.withSelect; var select = wp.data.select; var dispatch = wp.data.dispatch; - var PlainText = wp.editor.PlainText; + var PlainText = wp.blockEditor.PlainText; var Fragment = wp.element.Fragment; var el = wp.element.createElement; var Component = wp.element.Component; @@ -63,7 +63,7 @@ onClick: () => { dispatch( 'core/annotations' ).__experimentalAddAnnotation( { source: 'e2e-tests', - blockClientId: select( 'core/editor' ).getBlockOrder()[ 0 ], + blockClientId: select( 'core/block-editor' ).getBlockOrder()[ 0 ], richTextIdentifier: 'content', range: { start: parseInt( this.state.start, 10 ), diff --git a/packages/e2e-tests/plugins/plugins-api/sidebar.js b/packages/e2e-tests/plugins/plugins-api/sidebar.js index 22038c81c4c928..5ce92bcd4e09a2 100644 --- a/packages/e2e-tests/plugins/plugins-api/sidebar.js +++ b/packages/e2e-tests/plugins/plugins-api/sidebar.js @@ -7,7 +7,7 @@ var withSelect = wp.data.withSelect; var select = wp.data.select; var dispatch = wp.data.dispatch; - var PlainText = wp.editor.PlainText; + var PlainText = wp.blockEditor.PlainText; var Fragment = wp.element.Fragment; var el = wp.element.createElement; var __ = wp.i18n.__; diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index 1eb25f41735e11..81f8db73d48629 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -24,9 +24,9 @@ describe( 'Reusable Blocks', () => { beforeEach( async () => { // Remove all blocks from the post so that we're working with a clean slate await page.evaluate( () => { - const blocks = wp.data.select( 'core/editor' ).getBlocks(); + const blocks = wp.data.select( 'core/block-editor' ).getBlocks(); const clientIds = blocks.map( ( block ) => block.clientId ); - wp.data.dispatch( 'core/editor' ).removeBlocks( clientIds ); + wp.data.dispatch( 'core/block-editor' ).removeBlocks( clientIds ); } ); } ); diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 921a2b53fdb4a1..2bac55e10ac2e6 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,3 +1,148 @@ +## Master + +### Deprecations + +- The following components are deprecated as moved to the `@wordpress/block-editor` package: + - Autocomplete, + - AlignmentToolbar, + - BlockAlignmentToolbar, + - BlockControls, + - BlockEdit, + - BlockEditorKeyboardShortcuts, + - BlockFormatControls, + - BlockIcon, + - BlockInspector, + - BlockList, + - BlockMover, + - BlockNavigationDropdown, + - BlockSelectionClearer, + - BlockSettingsMenu, + - BlockTitle, + - BlockToolbar, + - ColorPalette, + - ContrastChecker, + - CopyHandler, + - createCustomColorsHOC, + - DefaultBlockAppender, + - FontSizePicker, + - getColorClassName, + - getColorObjectByAttributeValues, + - getColorObjectByColorValue, + - getFontSize, + - getFontSizeClass, + - Inserter, + - InnerBlocks, + - InspectorAdvancedControls, + - InspectorControls, + - PanelColorSettings, + - PlainText, + - RichText, + - RichTextShortcut, + - RichTextToolbarButton, + - RichTextInserterItem, + - MediaPlaceholder, + - MediaUpload, + - MediaUploadCheck, + - MultiBlocksSwitcher, + - MultiSelectScrollIntoView, + - NavigableToolbar, + - ObserveTyping, + - PreserveScrollInReorder, + - SkipToSelectedBlock, + - URLInput, + - URLInputButton, + - URLPopover, + - Warning, + - WritingFlow, + - withColorContext, + - withColors, + - withFontSizes. +- The following actions are deprecated as moved to the `core/block-editor` store: + - resetBlocks, + - receiveBlocks, + - updateBlock, + - updateBlockAttributes, + - selectBlock, + - startMultiSelect, + - stopMultiSelect, + - multiSelect, + - clearSelectedBlock, + - toggleSelection, + - replaceBlocks, + - replaceBlock, + - moveBlocksDown, + - moveBlocksUp, + - moveBlockToPosition, + - insertBlock, + - insertBlocks, + - showInsertionPoint, + - hideInsertionPoint, + - setTemplateValidity, + - synchronizeTemplate, + - mergeBlocks, + - removeBlocks, + - removeBlock, + - toggleBlockMode, + - startTyping, + - stopTyping, + - enterFormattedText, + - exitFormattedText, + - insertDefaultBlock, + - updateBlockListSettings. +- The following selectors are deprecated as moved to the `core/block-editor` store: + - getBlockDependantsCacheBust, + - getBlockName, + - isBlockValid, + - getBlockAttributes, + - getBlock, + - getBlocks, + - getClientIdsOfDescendants, + - getClientIdsWithDescendants, + - getGlobalBlockCount, + - getBlocksByClientId, + - getBlockCount, + - getBlockSelectionStart, + - getBlockSelectionEnd, + - getSelectedBlockCount, + - hasSelectedBlock, + - getSelectedBlockClientId, + - getSelectedBlock, + - getBlockRootClientId, + - getBlockHierarchyRootClientId, + - getAdjacentBlockClientId, + - getPreviousBlockClientId, + - getNextBlockClientId, + - getSelectedBlocksInitialCaretPosition, + - getMultiSelectedBlockClientIds, + - getMultiSelectedBlocks, + - getFirstMultiSelectedBlockClientId, + - getLastMultiSelectedBlockClientId, + - isFirstMultiSelectedBlock, + - isBlockMultiSelected, + - isAncestorMultiSelected, + - getMultiSelectedBlocksStartClientId, + - getMultiSelectedBlocksEndClientId, + - getBlockOrder, + - getBlockIndex, + - isBlockSelected, + - hasSelectedInnerBlock, + - isBlockWithinSelection, + - hasMultiSelection, + - isMultiSelecting, + - isSelectionEnabled, + - getBlockMode =, + - isTyping, + - isCaretWithinFormattedText, + - getBlockInsertionPoint, + - isBlockInsertionPointVisible, + - isValidTemplate, + - getTemplate, + - getTemplateLock, + - canInsertBlockType, + - getInserterItems, + - hasInserterItems, + - getBlockListSettings. + ## 9.3.0 (2019-05-21) ### Deprecations @@ -5,8 +150,6 @@ - The `resetAutosave` action is deprecated. An equivalent action `receiveAutosaves` has been added to the `@wordpress/core-data` package. - `ServerSideRender` component was deprecated. The component is now available in `@wordpress/server-side-render`. - - ### Internal - Refactor setupEditor effects to action-generator using controls ([#14513](https://github.com/WordPress/gutenberg/pull/14513)) diff --git a/packages/editor/src/components/deprecated.js b/packages/editor/src/components/deprecated.js index 330116fabec58e..44758b2ea29a4e 100644 --- a/packages/editor/src/components/deprecated.js +++ b/packages/editor/src/components/deprecated.js @@ -2,119 +2,141 @@ /** * WordPress dependencies */ +import deprecated from '@wordpress/deprecated'; +import { forwardRef } from '@wordpress/element'; import { - Autocomplete, - AlignmentToolbar, - BlockAlignmentToolbar, - BlockControls, - BlockEdit, - BlockEditorKeyboardShortcuts, - BlockFormatControls, - BlockIcon, - BlockInspector, - BlockList, - BlockMover, - BlockNavigationDropdown, - BlockSelectionClearer, - BlockSettingsMenu, - BlockTitle, - BlockToolbar, - ColorPalette, - ContrastChecker, - CopyHandler, - createCustomColorsHOC, - DefaultBlockAppender, - FontSizePicker, - getColorClassName, - getColorObjectByAttributeValues, - getColorObjectByColorValue, - getFontSize, - getFontSizeClass, - Inserter, - InnerBlocks, - InspectorAdvancedControls, - InspectorControls, - PanelColorSettings, - PlainText, - RichText, - RichTextShortcut, - RichTextToolbarButton, - RichTextInserterItem, - __unstableRichTextInputEvent, - MediaPlaceholder, - MediaUpload, - MediaUploadCheck, - MultiBlocksSwitcher, - MultiSelectScrollIntoView, - NavigableToolbar, - ObserveTyping, - PreserveScrollInReorder, - SkipToSelectedBlock, - URLInput, - URLInputButton, - URLPopover, - Warning, - WritingFlow, - withColorContext, - withColors, - withFontSizes, + Autocomplete as RootAutocomplete, + AlignmentToolbar as RootAlignmentToolbar, + BlockAlignmentToolbar as RootBlockAlignmentToolbar, + BlockControls as RootBlockControls, + BlockEdit as RootBlockEdit, + BlockEditorKeyboardShortcuts as RootBlockEditorKeyboardShortcuts, + BlockFormatControls as RootBlockFormatControls, + BlockIcon as RootBlockIcon, + BlockInspector as RootBlockInspector, + BlockList as RootBlockList, + BlockMover as RootBlockMover, + BlockNavigationDropdown as RootBlockNavigationDropdown, + BlockSelectionClearer as RootBlockSelectionClearer, + BlockSettingsMenu as RootBlockSettingsMenu, + BlockTitle as RootBlockTitle, + BlockToolbar as RootBlockToolbar, + ColorPalette as RootColorPalette, + ContrastChecker as RootContrastChecker, + CopyHandler as RootCopyHandler, + createCustomColorsHOC as rootCreateCustomColorsHOC, + DefaultBlockAppender as RootDefaultBlockAppender, + FontSizePicker as RootFontSizePicker, + getColorClassName as rootGetColorClassName, + getColorObjectByAttributeValues as rootGetColorObjectByAttributeValues, + getColorObjectByColorValue as rootGetColorObjectByColorValue, + getFontSize as rootGetFontSize, + getFontSizeClass as rootGetFontSizeClass, + Inserter as RootInserter, + InnerBlocks as RootInnerBlocks, + InspectorAdvancedControls as RootInspectorAdvancedControls, + InspectorControls as RootInspectorControls, + PanelColorSettings as RootPanelColorSettings, + PlainText as RootPlainText, + RichText as RootRichText, + RichTextShortcut as RootRichTextShortcut, + RichTextToolbarButton as RootRichTextToolbarButton, + RichTextInserterItem as RootRichTextInserterItem, + __unstableRichTextInputEvent as __unstableRootRichTextInputEvent, + MediaPlaceholder as RootMediaPlaceholder, + MediaUpload as RootMediaUpload, + MediaUploadCheck as RootMediaUploadCheck, + MultiBlocksSwitcher as RootMultiBlocksSwitcher, + MultiSelectScrollIntoView as RootMultiSelectScrollIntoView, + NavigableToolbar as RootNavigableToolbar, + ObserveTyping as RootObserveTyping, + PreserveScrollInReorder as RootPreserveScrollInReorder, + SkipToSelectedBlock as RootSkipToSelectedBlock, + URLInput as RootURLInput, + URLInputButton as RootURLInputButton, + URLPopover as RootURLPopover, + Warning as RootWarning, + WritingFlow as RootWritingFlow, + withColorContext as rootWithColorContext, + withColors as rootWithColors, + withFontSizes as rootWithFontSizes, } from '@wordpress/block-editor'; -export { - Autocomplete, - AlignmentToolbar, - BlockAlignmentToolbar, - BlockControls, - BlockEdit, - BlockEditorKeyboardShortcuts, - BlockFormatControls, - BlockIcon, - BlockInspector, - BlockList, - BlockMover, - BlockNavigationDropdown, - BlockSelectionClearer, - BlockSettingsMenu, - BlockTitle, - BlockToolbar, - ColorPalette, - ContrastChecker, - CopyHandler, - createCustomColorsHOC, - DefaultBlockAppender, - FontSizePicker, - getColorClassName, - getColorObjectByAttributeValues, - getColorObjectByColorValue, - getFontSize, - getFontSizeClass, - Inserter, - InnerBlocks, - InspectorAdvancedControls, - InspectorControls, - PanelColorSettings, - PlainText, - RichText, - RichTextShortcut, - RichTextToolbarButton, - RichTextInserterItem, - __unstableRichTextInputEvent, - MediaPlaceholder, - MediaUpload, - MediaUploadCheck, - MultiBlocksSwitcher, - MultiSelectScrollIntoView, - NavigableToolbar, - ObserveTyping, - PreserveScrollInReorder, - SkipToSelectedBlock, - URLInput, - URLInputButton, - URLPopover, - Warning, - WritingFlow, - withColorContext, - withColors, - withFontSizes, -}; export { default as ServerSideRender } from '@wordpress/server-side-render'; + +function deprecateComponent( name, Wrapped ) { + return forwardRef( ( props, ref ) => { + deprecated( 'wp.editor.' + name, { + alternative: 'wp.blockEditor.' + name, + } ); + + return <Wrapped ref={ ref }{ ...props } />; + } ); +} + +function deprecateFunction( name, func ) { + return ( ...args ) => { + deprecated( 'wp.editor.' + name, { + alternative: 'wp.blockEditor.' + name, + } ); + + return func( ...args ); + }; +} + +export const Autocomplete = deprecateComponent( 'Autocomplete', RootAutocomplete ); +export const AlignmentToolbar = deprecateComponent( 'AlignmentToolbar', RootAlignmentToolbar ); +export const BlockAlignmentToolbar = deprecateComponent( 'BlockAlignmentToolbar', RootBlockAlignmentToolbar ); +export const BlockControls = deprecateComponent( 'BlockControls', RootBlockControls ); +export const BlockEdit = deprecateComponent( 'BlockEdit', RootBlockEdit ); +export const BlockEditorKeyboardShortcuts = deprecateComponent( 'BlockEditorKeyboardShortcuts', RootBlockEditorKeyboardShortcuts ); +export const BlockFormatControls = deprecateComponent( 'BlockFormatControls', RootBlockFormatControls ); +export const BlockIcon = deprecateComponent( 'BlockIcon', RootBlockIcon ); +export const BlockInspector = deprecateComponent( 'BlockInspector', RootBlockInspector ); +export const BlockList = deprecateComponent( 'BlockList', RootBlockList ); +export const BlockMover = deprecateComponent( 'BlockMover', RootBlockMover ); +export const BlockNavigationDropdown = deprecateComponent( 'BlockNavigationDropdown', RootBlockNavigationDropdown ); +export const BlockSelectionClearer = deprecateComponent( 'BlockSelectionClearer', RootBlockSelectionClearer ); +export const BlockSettingsMenu = deprecateComponent( 'BlockSettingsMenu', RootBlockSettingsMenu ); +export const BlockTitle = deprecateComponent( 'BlockTitle', RootBlockTitle ); +export const BlockToolbar = deprecateComponent( 'BlockToolbar', RootBlockToolbar ); +export const ColorPalette = deprecateComponent( 'ColorPalette', RootColorPalette ); +export const ContrastChecker = deprecateComponent( 'ContrastChecker', RootContrastChecker ); +export const CopyHandler = deprecateComponent( 'CopyHandler', RootCopyHandler ); +export const DefaultBlockAppender = deprecateComponent( 'DefaultBlockAppender', RootDefaultBlockAppender ); +export const FontSizePicker = deprecateComponent( 'FontSizePicker', RootFontSizePicker ); +export const Inserter = deprecateComponent( 'Inserter', RootInserter ); +export const InnerBlocks = deprecateComponent( 'InnerBlocks', RootInnerBlocks ); +export const InspectorAdvancedControls = deprecateComponent( 'InspectorAdvancedControls', RootInspectorAdvancedControls ); +export const InspectorControls = deprecateComponent( 'InspectorControls', RootInspectorControls ); +export const PanelColorSettings = deprecateComponent( 'PanelColorSettings', RootPanelColorSettings ); +export const PlainText = deprecateComponent( 'PlainText', RootPlainText ); +export const RichText = deprecateComponent( 'RichText', RootRichText ); +export const RichTextShortcut = deprecateComponent( 'RichTextShortcut', RootRichTextShortcut ); +export const RichTextToolbarButton = deprecateComponent( 'RichTextToolbarButton', RootRichTextToolbarButton ); +export const RichTextInserterItem = deprecateComponent( 'RichTextInserterItem', RootRichTextInserterItem ); +export const __unstableRichTextInputEvent = deprecateComponent( '__unstableRichTextInputEvent', __unstableRootRichTextInputEvent ); +export const MediaPlaceholder = deprecateComponent( 'MediaPlaceholder', RootMediaPlaceholder ); +export const MediaUpload = deprecateComponent( 'MediaUpload', RootMediaUpload ); +export const MediaUploadCheck = deprecateComponent( 'MediaUploadCheck', RootMediaUploadCheck ); +export const MultiBlocksSwitcher = deprecateComponent( 'MultiBlocksSwitcher', RootMultiBlocksSwitcher ); +export const MultiSelectScrollIntoView = deprecateComponent( 'MultiSelectScrollIntoView', RootMultiSelectScrollIntoView ); +export const NavigableToolbar = deprecateComponent( 'NavigableToolbar', RootNavigableToolbar ); +export const ObserveTyping = deprecateComponent( 'ObserveTyping', RootObserveTyping ); +export const PreserveScrollInReorder = deprecateComponent( 'PreserveScrollInReorder', RootPreserveScrollInReorder ); +export const SkipToSelectedBlock = deprecateComponent( 'SkipToSelectedBlock', RootSkipToSelectedBlock ); +export const URLInput = deprecateComponent( 'URLInput', RootURLInput ); +export const URLInputButton = deprecateComponent( 'URLInputButton', RootURLInputButton ); +export const URLPopover = deprecateComponent( 'URLPopover', RootURLPopover ); +export const Warning = deprecateComponent( 'Warning', RootWarning ); +export const WritingFlow = deprecateComponent( 'WritingFlow', RootWritingFlow ); + +export const createCustomColorsHOC = deprecateFunction( 'createCustomColorsHOC', rootCreateCustomColorsHOC ); +export const getColorClassName = deprecateFunction( 'getColorClassName', rootGetColorClassName ); +export const getColorObjectByAttributeValues = deprecateFunction( 'getColorObjectByAttributeValues', rootGetColorObjectByAttributeValues ); +export const getColorObjectByColorValue = deprecateFunction( 'getColorObjectByColorValue', rootGetColorObjectByColorValue ); +export const getFontSize = deprecateFunction( 'getFontSize', rootGetFontSize ); +export const getFontSizeClass = deprecateFunction( 'getFontSizeClass', rootGetFontSizeClass ); +export const withColorContext = deprecateFunction( 'withColorContext', rootWithColorContext ); +export const withColors = deprecateFunction( 'withColors', rootWithColors ); +export const withFontSizes = deprecateFunction( 'withFontSizes', rootWithFontSizes ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 03548de3f93abc..073887663bd1b9 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -757,6 +757,9 @@ export function updateEditorSettings( settings ) { */ const getBlockEditorAction = ( name ) => function* ( ...args ) { + deprecated( '`wp.data.dispatch( \'core/editor\' ).' + name + '`', { + alternative: '`wp.data.dispatch( \'core/block-editor\' ).' + name + '`', + } ); yield dispatch( 'core/block-editor', name, ...args ); }; diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 094b47bc716774..7570e8b7848de6 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1160,6 +1160,10 @@ export function getEditorSettings( state ) { function getBlockEditorSelector( name ) { return createRegistrySelector( ( select ) => ( state, ...args ) => { + deprecated( '`wp.data.select( \'core/editor\' ).' + name + '`', { + alternative: '`wp.data.select( \'core/block-editor\' ).' + name + '`', + } ); + return select( 'core/block-editor' )[ name ]( ...args ); } ); } From 3fbe9f782a09aaf1bbab51c1d8d4d92d1c4c128a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 10 Jun 2019 12:09:13 +0100 Subject: [PATCH 299/664] Update browserslist dependency to fix unit tests (#16066) --- package-lock.json | 50 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8943767a50d28..efb7105ba3676e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5146,44 +5146,36 @@ } }, "browserslist": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz", - "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.2.tgz", + "integrity": "sha512-2neU/V0giQy9h3XMPwLhEY3+Ao0uHSwHvU8Q1Ea6AgLVL1sXbX3dzPrJ8NWe5Hi4PoTkCYXOtVR9rfRLI0J/8Q==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000929", - "electron-to-chromium": "^1.3.103", - "node-releases": "^1.1.3" + "caniuse-lite": "^1.0.30000974", + "electron-to-chromium": "^1.3.150", + "node-releases": "^1.1.23" }, "dependencies": { "caniuse-lite": { - "version": "1.0.30000929", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000929.tgz", - "integrity": "sha512-n2w1gPQSsYyorSVYqPMqbSaz1w7o9ZC8VhOEGI9T5MfGDzp7sbopQxG6GaQmYsaq13Xfx/mkxJUWC1Dz3oZfzw==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.103", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.103.tgz", - "integrity": "sha512-tObPqGmY9X8MUM8i3MEimYmbnLLf05/QV5gPlkR8MQ3Uj8G8B2govE1U4cQcBYtv3ymck9Y8cIOu4waoiykMZQ==", + "version": "1.0.30000974", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000974.tgz", + "integrity": "sha512-xc3rkNS/Zc3CmpMKuczWEdY2sZgx09BkAxfvkxlAEBTqcMHeL8QnPqhKse+5sRTi3nrw2pJwToD2WvKn1Uhvww==", "dev": true }, "node-releases": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", - "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.23.tgz", + "integrity": "sha512-uq1iL79YjfYC0WXoHbC/z28q/9pOl8kSHaXdWmAAc8No+bDwqkZbzIJz55g/MUsPgSGm9LZ7QSUbzTcH5tz47w==", "dev": true, "requires": { "semver": "^5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, @@ -8256,6 +8248,12 @@ "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", "dev": true }, + "electron-to-chromium": { + "version": "1.3.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.155.tgz", + "integrity": "sha512-/ci/XgZG8jkLYOgOe3mpJY1onxPPTDY17y7scldhnSjjZqV6VvREG/LvwhRuV7BJbnENFfuDWZkSqlTh4x9ZjQ==", + "dev": true + }, "elegant-spinner": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", diff --git a/package.json b/package.json index 62de2dba7741d3..eec8121079e2e9 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "@wordpress/scripts": "file:packages/scripts", "babel-plugin-inline-json-import": "0.3.2", "benchmark": "2.1.4", - "browserslist": "4.4.1", + "browserslist": "4.6.2", "chalk": "2.4.1", "commander": "2.20.0", "concurrently": "3.5.0", From 83b8092977185c2c0222fbe450361c14a1dd2ee6 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Mon, 10 Jun 2019 08:44:34 -0400 Subject: [PATCH 300/664] Bump plugin version to 5.9.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 1cef2b3380e4bf..d9c384353ca202 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.8.0 + * Version: 5.9.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index efb7105ba3676e..aa639527442e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.8.0", + "version": "5.9.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index eec8121079e2e9..4ff2130f820687 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.8.0", + "version": "5.9.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 40797c63bbb5f2c28c0b9af17791d7565d181bc5 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 10 Jun 2019 14:14:21 +0100 Subject: [PATCH 301/664] Explicitely push to the release branch (#16070) --- bin/commander.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bin/commander.js b/bin/commander.js index 3412492ce062f4..9eeae01834ac55 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -278,6 +278,7 @@ async function runReleaseBranchCreationStep( abortMessage ) { return { version, versionLabel, + releaseBranch, }; } @@ -320,6 +321,7 @@ async function runReleaseBranchCheckoutStep( abortMessage ) { return { version, versionLabel: version, + releaseBranch, }; } @@ -415,9 +417,10 @@ async function runCreateGitTagStep( version, abortMessage ) { /** * Push the local Git Changes and Tags to the remote repository. * - * @param {string} abortMessage Abort message. + * @param {string} releaseBranch Release branch name. + * @param {string} abortMessage Abort message. */ -async function runPushGitChangesStep( abortMessage ) { +async function runPushGitChangesStep( releaseBranch, abortMessage ) { await runStep( 'Pushing the release branch and the tag', abortMessage, async () => { const simpleGit = SimpleGit( gitWorkingDirectoryPath ); await askForConfirmationToContinue( @@ -425,7 +428,7 @@ async function runPushGitChangesStep( abortMessage ) { true, abortMessage ); - await simpleGit.push( 'origin' ); + await simpleGit.push( 'origin', releaseBranch ); await simpleGit.pushTags( 'origin' ); } ); } @@ -539,7 +542,7 @@ async function releasePlugin( isRC = true ) { await runGitRepositoryCloneStep( abortMessage ); // Creating the release branch - const { version, versionLabel } = isRC ? + const { version, versionLabel, releaseBranch } = isRC ? await runReleaseBranchCreationStep( abortMessage ) : await runReleaseBranchCheckoutStep( abortMessage ); @@ -553,7 +556,7 @@ async function releasePlugin( isRC = true ) { await runCreateGitTagStep( version, abortMessage ); // Push the local changes - await runPushGitChangesStep( abortMessage ); + await runPushGitChangesStep( releaseBranch, abortMessage ); abortMessage = 'Aborting! Make sure to ' + isRC ? 'remove' : 'reset' + ' the remote release branch and remove the git tag.'; // Creating the GitHub Release From f46d6f486f954588d6271086bdaec00ea478c127 Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <vadim.nicolai@moduscreate.com> Date: Mon, 10 Jun 2019 17:53:22 +0300 Subject: [PATCH 302/664] Update for MediaPlaceholder component: don't stack error messages when re-uploading (#14721) * Used removeAllNotices in onUploadError. * Updated GalleryEdit. * Updated MediaTextEdit. * Updated VideoEdit. * Updated CoverEdit. * Updated FileEdit. * Fixed tab spaces. --- packages/block-library/src/audio/edit.js | 11 +++++++++-- packages/block-library/src/cover/edit.js | 10 ++++++++-- packages/block-library/src/file/edit.js | 10 ++++++++-- packages/block-library/src/gallery/edit.js | 11 +++++++++-- packages/block-library/src/image/edit.js | 1 + .../src/media-text/media-container.js | 19 ++++++++++++++++--- packages/block-library/src/video/edit.js | 10 ++++++++-- 7 files changed, 59 insertions(+), 13 deletions(-) diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 1545378ddc0c74..a518f077011e6a 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -45,6 +45,7 @@ class AudioEdit extends Component { this.toggleAttribute = this.toggleAttribute.bind( this ); this.onSelectURL = this.onSelectURL.bind( this ); + this.onUploadError = this.onUploadError.bind( this ); } componentDidMount() { @@ -98,13 +99,19 @@ class AudioEdit extends Component { this.setState( { editing: false } ); } + onUploadError( message ) { + const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + getAutoplayHelp( checked ) { return checked ? __( 'Note: Autoplaying audio may cause usability issues for some visitors.' ) : null; } render() { const { autoplay, caption, loop, preload, src } = this.props.attributes; - const { setAttributes, isSelected, className, noticeOperations, noticeUI } = this.props; + const { setAttributes, isSelected, className, noticeUI } = this.props; const { editing } = this.state; const switchToEditing = () => { this.setState( { editing: true } ); @@ -133,7 +140,7 @@ class AudioEdit extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } value={ this.props.attributes } notices={ noticeUI } - onError={ noticeOperations.createErrorNotice } + onError={ this.onUploadError } /> ); } diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index b7cb4ea6984eb1..05bb57786332df 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -72,6 +72,7 @@ class CoverEdit extends Component { this.imageRef = createRef(); this.videoRef = createRef(); this.changeIsDarkIfRequired = this.changeIsDarkIfRequired.bind( this ); + this.onUploadError = this.onUploadError.bind( this ); } componentDidMount() { @@ -82,12 +83,17 @@ class CoverEdit extends Component { this.handleBackgroundMode( prevProps ); } + onUploadError( message ) { + const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + render() { const { attributes, setAttributes, className, - noticeOperations, noticeUI, overlayColor, setOverlayColor, @@ -243,7 +249,7 @@ class CoverEdit extends Component { accept="image/*,video/*" allowedTypes={ ALLOWED_MEDIA_TYPES } notices={ noticeUI } - onError={ noticeOperations.createErrorNotice } + onError={ this.onUploadError } /> </> ); diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index efa93424a5a9e6..f06d2be38c98a7 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -47,6 +47,7 @@ class FileEdit extends Component { this.changeLinkDestinationOption = this.changeLinkDestinationOption.bind( this ); this.changeOpenInNewWindow = this.changeOpenInNewWindow.bind( this ); this.changeShowDownloadButton = this.changeShowDownloadButton.bind( this ); + this.onUploadError = this.onUploadError.bind( this ); this.state = { hasError: false, @@ -100,6 +101,12 @@ class FileEdit extends Component { } } + onUploadError( message ) { + const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + confirmCopyURL() { this.setState( { showCopyConfirmation: true } ); } @@ -130,7 +137,6 @@ class FileEdit extends Component { attributes, setAttributes, noticeUI, - noticeOperations, media, } = this.props; const { @@ -155,7 +161,7 @@ class FileEdit extends Component { } } onSelect={ this.onSelectFile } notices={ noticeUI } - onError={ noticeOperations.createErrorNotice } + onError={ this.onUploadError } accept="*" /> ); diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index cb1188c010ba1b..c5aebed551849f 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -54,6 +54,7 @@ class GalleryEdit extends Component { this.onMoveForward = this.onMoveForward.bind( this ); this.onMoveBackward = this.onMoveBackward.bind( this ); this.onRemoveImage = this.onRemoveImage.bind( this ); + this.onUploadError = this.onUploadError.bind( this ); this.setImageAttributes = this.setImageAttributes.bind( this ); this.setAttributes = this.setAttributes.bind( this ); @@ -133,6 +134,12 @@ class GalleryEdit extends Component { } ); } + onUploadError( message ) { + const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + setLinkTo( value ) { this.setAttributes( { linkTo: value } ); } @@ -178,7 +185,7 @@ class GalleryEdit extends Component { } render() { - const { attributes, isSelected, className, noticeOperations, noticeUI } = this.props; + const { attributes, isSelected, className, noticeUI } = this.props; const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes; const hasImages = !! images.length; @@ -223,7 +230,7 @@ class GalleryEdit extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } multiple value={ hasImages ? images : undefined } - onError={ noticeOperations.createErrorNotice } + onError={ this.onUploadError } notices={ hasImages ? undefined : noticeUI } /> ); diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 645248bc702414..ecc498e2988fc9 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -165,6 +165,7 @@ class ImageEdit extends Component { onUploadError( message ) { const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); noticeOperations.createErrorNotice( message ); this.setState( { isEditing: true, diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index 9afc28c58b3a7c..74d2062d524d9c 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { IconButton, ResizableBox, Toolbar } from '@wordpress/components'; +import { IconButton, ResizableBox, Toolbar, withNotices } from '@wordpress/components'; import { BlockControls, BlockIcon, @@ -31,6 +31,17 @@ export function imageFillStyles( url, focalPoint ) { } class MediaContainer extends Component { + constructor() { + super( ...arguments ); + this.onUploadError = this.onUploadError.bind( this ); + } + + onUploadError( message ) { + const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + renderToolbarEditButton() { const { mediaId, onSelectMedia } = this.props; return ( @@ -80,7 +91,7 @@ class MediaContainer extends Component { } renderPlaceholder() { - const { onSelectMedia, className } = this.props; + const { onSelectMedia, className, noticeUI } = this.props; return ( <MediaPlaceholder icon={ <BlockIcon icon={ icon } /> } @@ -91,6 +102,8 @@ class MediaContainer extends Component { onSelect={ onSelectMedia } accept="image/*,video/*" allowedTypes={ ALLOWED_MEDIA_TYPES } + notices={ noticeUI } + onError={ this.onUploadError } /> ); } @@ -137,4 +150,4 @@ class MediaContainer extends Component { } } -export default MediaContainer; +export default withNotices( MediaContainer ); diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 0f2939fe1f6a5e..f8380b7edeafc6 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -57,6 +57,7 @@ class VideoEdit extends Component { this.onSelectURL = this.onSelectURL.bind( this ); this.onSelectPoster = this.onSelectPoster.bind( this ); this.onRemovePoster = this.onRemovePoster.bind( this ); + this.onUploadError = this.onUploadError.bind( this ); } componentDidMount() { @@ -126,6 +127,12 @@ class VideoEdit extends Component { this.posterImageButton.current.focus(); } + onUploadError( message ) { + const { noticeOperations } = this.props; + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + } + getAutoplayHelp( checked ) { return checked ? __( 'Note: Autoplaying videos may cause usability issues for some visitors.' ) : null; } @@ -146,7 +153,6 @@ class VideoEdit extends Component { className, instanceId, isSelected, - noticeOperations, noticeUI, setAttributes, } = this.props; @@ -179,7 +185,7 @@ class VideoEdit extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } value={ this.props.attributes } notices={ noticeUI } - onError={ noticeOperations.createErrorNotice } + onError={ this.onUploadError } /> ); } From a3ef9605c74a8f3e6cb57a28fad33e4701585d6a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 10 Jun 2019 11:52:59 -0400 Subject: [PATCH 303/664] Editor: Create own sub-registry in default EditorProvider use (#15989) --- packages/block-editor/README.md | 12 +++++ packages/block-editor/src/index.js | 2 +- packages/block-editor/src/store/index.js | 7 +++ packages/edit-post/src/editor.js | 1 + .../editor/src/components/provider/index.js | 2 + .../provider/with-registry-provider.js | 46 +++++++++++++++++++ packages/editor/src/index.js | 1 + packages/editor/src/store/index.js | 13 +++++- 8 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 packages/editor/src/components/provider/with-registry-provider.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 6467b59284706f..ff1a8579a0b016 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -375,6 +375,18 @@ The default editor settings Undocumented declaration. +<a name="storeConfig" href="#storeConfig">#</a> **storeConfig** + +Block editor data store configuration. + +_Related_ + +- <https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#registerStore> + +_Type_ + +- `Object` + <a name="URLInput" href="#URLInput">#</a> **URLInput** _Related_ diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index 1d8827e2a33b49..d554e6e577a385 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -14,5 +14,5 @@ import './hooks'; export * from './components'; export * from './utils'; - +export { storeConfig } from './store'; export { SETTINGS_DEFAULTS } from './store/defaults'; diff --git a/packages/block-editor/src/store/index.js b/packages/block-editor/src/store/index.js index 485238f46f606d..bfc7766a508762 100644 --- a/packages/block-editor/src/store/index.js +++ b/packages/block-editor/src/store/index.js @@ -17,6 +17,13 @@ import controls from './controls'; */ const MODULE_KEY = 'core/block-editor'; +/** + * Block editor data store configuration. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#registerStore + * + * @type {Object} + */ export const storeConfig = { reducer, selectors, diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 28e55b08d6cbf6..ea48a3a1d8e358 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -91,6 +91,7 @@ class Editor extends Component { settings={ editorSettings } post={ post } initialEdits={ initialEdits } + useSubRegistry={ false } { ...props } > <ErrorBoundary onError={ onError }> diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index e897931daf6c42..0e959f80c7b866 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -19,6 +19,7 @@ import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies */ +import withRegistryProvider from './with-registry-provider'; import { mediaUpload } from '../../utils'; import ReusableBlocksButtons from '../reusable-blocks-buttons'; import ConvertToGroupButtons from '../convert-to-group-buttons'; @@ -167,6 +168,7 @@ class EditorProvider extends Component { } export default compose( [ + withRegistryProvider, withSelect( ( select ) => { const { __unstableIsEditorReady: isEditorReady, diff --git a/packages/editor/src/components/provider/with-registry-provider.js b/packages/editor/src/components/provider/with-registry-provider.js new file mode 100644 index 00000000000000..367782a82b4a42 --- /dev/null +++ b/packages/editor/src/components/provider/with-registry-provider.js @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { useState, useEffect } from '@wordpress/element'; +import { withRegistry, createRegistry, RegistryProvider } from '@wordpress/data'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { storeConfig as blockEditorStoreConfig } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { storeConfig } from '../../store'; +import applyMiddlewares from '../../store/middlewares'; + +const withRegistryProvider = createHigherOrderComponent( + ( WrappedComponent ) => withRegistry( ( props ) => { + const { useSubRegistry = true, registry, ...additionalProps } = props; + if ( ! useSubRegistry ) { + return <WrappedComponent { ...additionalProps } />; + } + + const [ subRegistry, setSubRegistry ] = useState( null ); + useEffect( () => { + const newRegistry = createRegistry( { + 'core/block-editor': blockEditorStoreConfig, + }, registry ); + const store = newRegistry.registerStore( 'core/editor', storeConfig ); + // This should be removed after the refactoring of the effects to controls. + applyMiddlewares( store ); + setSubRegistry( newRegistry ); + }, [ registry ] ); + + if ( ! subRegistry ) { + return null; + } + + return ( + <RegistryProvider value={ subRegistry }> + <WrappedComponent { ...additionalProps } /> + </RegistryProvider> + ); + } ), + 'withRegistryProvider' +); + +export default withRegistryProvider; diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index a55a7b1c0bfc1b..eb54a38859b135 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -17,6 +17,7 @@ import './hooks'; export * from './components'; export * from './utils'; +export { storeConfig } from './store'; /* * Backward compatibility diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index 1ba136aaab7226..33c5686396097e 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -13,11 +13,22 @@ import * as selectors from './selectors'; import * as actions from './actions'; import { STORE_KEY } from './constants'; -const store = registerStore( STORE_KEY, { +/** + * Post editor data store configuration. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#registerStore + * + * @type {Object} + */ +export const storeConfig = { reducer, selectors, actions, controls, +}; + +const store = registerStore( STORE_KEY, { + ...storeConfig, persist: [ 'preferences' ], } ); applyMiddlewares( store ); From a5a3c08dac84545ed1ec367ef8fa53e077a8171c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Mon, 10 Jun 2019 18:33:20 +0200 Subject: [PATCH 304/664] Docs: Change register_meta to register_post_meta (#16032) * Change register_meta to register_post_meta * Change register_meta to register_post_meta --- .../developers/block-api/block-attributes.md | 6 +++--- .../tutorials/metabox/meta-block-2-register-meta.md | 12 ++++++------ .../plugin-sidebar-3-register-meta.md | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/designers-developers/developers/block-api/block-attributes.md index 203113b06f5f97..2355fbc4356558 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/designers-developers/developers/block-api/block-attributes.md @@ -172,7 +172,7 @@ By default, a meta field will be excluded from a post object's meta. This can be ```php function gutenberg_my_block_init() { - register_meta( 'post', 'author', array( + register_post_meta( 'post', 'author', array( 'show_in_rest' => true, ) ); } @@ -184,11 +184,11 @@ Furthermore, be aware that WordPress defaults to: - not treating a meta datum as being unique, instead returning an array of values; - treating that datum as a string. -If either behavior is not desired, the same `register_meta` call can be complemented with the `single` and/or `type` parameters as follows: +If either behavior is not desired, the same `register_post_meta` call can be complemented with the `single` and/or `type` parameters as follows: ```php function gutenberg_my_block_init() { - register_meta( 'post', 'author_count', array( + register_post_meta( 'post', 'author_count', array( 'show_in_rest' => true, 'single' => true, 'type' => 'integer', diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md index 795bd722ece293..744e50dfae5341 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-2-register-meta.md @@ -2,7 +2,7 @@ A post meta field is a WordPress object used to store extra data about a post. You need to first register a new meta field prior to use. See Managing [Post Metadata](https://developer.wordpress.org/plugins/metadata/managing-post-metadata/) to learn more about post meta. -When registering the field, note the `show_in_rest` parameter. This ensures the data will be included in the REST API, which the block editor uses to load and save meta data. See the [`register_meta`](https://developer.wordpress.org/reference/functions/register_meta/) function definition for extra information. +When registering the field, note the `show_in_rest` parameter. This ensures the data will be included in the REST API, which the block editor uses to load and save meta data. See the [`register_post_meta`](https://developer.wordpress.org/reference/functions/register_post_meta/) function definition for extra information. To register the field, create a PHP plugin file called `myguten-meta-block.php` including: @@ -13,20 +13,20 @@ To register the field, create a PHP plugin file called `myguten-meta-block.php` */ // register custom meta tag field -function myguten_register_meta() { - register_meta( 'post', 'myguten_meta_block_field', array( +function myguten_register_post_meta() { + register_post_meta( 'post', 'myguten_meta_block_field', array( 'show_in_rest' => true, 'single' => true, 'type' => 'string', ) ); } -add_action( 'init', 'myguten_register_meta' ); +add_action( 'init', 'myguten_register_post_meta' ); ``` -**Note:** If the meta key name starts with an underscore WordPress considers it a protected field. Editing this field requires passing a permission check, which is set as the `auth_callback` in the `register_meta` function. Here is an example: +**Note:** If the meta key name starts with an underscore WordPress considers it a protected field. Editing this field requires passing a permission check, which is set as the `auth_callback` in the `register_post_meta` function. Here is an example: ```php -register_meta( 'post', '_myguten_protected_key', array( +register_post_meta( 'post', '_myguten_protected_key', array( 'show_in_rest' => true, 'single' => true, 'type' => 'string', diff --git a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md index da1717c8817f2b..999194e972bae7 100644 --- a/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md +++ b/docs/designers-developers/developers/tutorials/sidebar-tutorial/plugin-sidebar-3-register-meta.md @@ -1,11 +1,11 @@ # Register the Meta Field -To work with fields in the `post_meta` table, WordPress has a function called [register_meta](https://developer.wordpress.org/reference/functions/register_meta/). You're going to use it to register a new field called `sidebar_plugin_meta_block_field`, which will be a single string. Note that this field needs to be available through the [REST API](https://developer.wordpress.org/rest-api/) because that's how the block editor access data. +To work with fields in the `post_meta` table, WordPress has a function called [register_post_meta](https://developer.wordpress.org/reference/functions/register_post_meta/). You're going to use it to register a new field called `sidebar_plugin_meta_block_field`, which will be a single string. Note that this field needs to be available through the [REST API](https://developer.wordpress.org/rest-api/) because that's how the block editor access data. Add this to the PHP code, within the `init` callback function: ```php -register_meta( 'post', 'sidebar_plugin_meta_block_field', array( +register_post_meta( 'post', 'sidebar_plugin_meta_block_field', array( 'show_in_rest' => true, 'single' => true, 'type' => 'string', @@ -18,4 +18,4 @@ To make sure the field has been loaded, query the block editor [internal data st wp.data.select( 'core/editor' ).getCurrentPost().meta; ``` -Before adding the `register_meta` function to the plugin, this code returns a void array, because WordPress hasn't been told to load any meta field yet. After registering the field, the same code will return an object containing the registered meta field you registered. +Before adding the `register_post_meta` function to the plugin, this code returns a void array, because WordPress hasn't been told to load any meta field yet. After registering the field, the same code will return an object containing the registered meta field you registered. From 4d08cc5ffd300f1cab70e0a613a997959f39659c Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Mon, 10 Jun 2019 18:57:30 +0200 Subject: [PATCH 305/664] [RNMobile] iOS Image block caption multiline fix (#16071) * Add unit test to cover multiline rich-text onEnter appends br tag * Fix iOS issue where pressing Enter on image caption won't create a new line --- .../src/components/rich-text/index.native.js | 2 +- .../components/rich-text/test/index.native.js | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index d9fc545e8fa9f1..b63d193a962c60 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -365,7 +365,7 @@ export class RichText extends Component { const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; this.onFormatChange( insertedLineSeparator ); } - } else if ( event.shiftKey || ! this.onSplit ) { + } else if ( event.shiftKey || ! onSplit ) { this.needsSelectionUpdate = true; const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; this.onFormatChange( insertedLineBreak ); diff --git a/packages/block-editor/src/components/rich-text/test/index.native.js b/packages/block-editor/src/components/rich-text/test/index.native.js index 22ee6b118bb2d2..399e627308f65e 100644 --- a/packages/block-editor/src/components/rich-text/test/index.native.js +++ b/packages/block-editor/src/components/rich-text/test/index.native.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + /** * Internal dependencies */ @@ -24,4 +29,28 @@ describe( 'RichText Native', () => { expect( richText.willTrimSpaces( html ) ).toBe( false ); } ); } ); + + describe( 'Adds new line on Enter', () => { + let newValue; + const wrapper = shallow( <RichText + rootTagsToEliminate={ [ 'p' ] } + value={ "" } + onChange={ ( value ) => { + newValue = value + } } + formatTypes={ [] } + onSelectionChange={ jest.fn() } + /> ); + + const event = { + nativeEvent: { + eventCount: 0, + } + }; + wrapper.instance().onEnter(event); + + it( ' Adds <br> tag to content after pressing Enter key', () => { + expect( newValue ).toEqual( "<br>" ); + }); + } ); } ); From e9e3565872ff668abe636c3d1c1e2df340701fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Mon, 10 Jun 2019 18:29:27 +0100 Subject: [PATCH 306/664] Make sure no div elements get in the content of quotes. (#16072) * Make sure no div elements get in the content. * Remove tags in content. --- .../src/components/rich-text/index.native.js | 11 +++++++++++ .../src/primitives/block-quotation/index.native.js | 3 +++ 2 files changed, 14 insertions(+) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index b63d193a962c60..f9eeb33caac677 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -299,6 +299,12 @@ export class RichText extends Component { result = this.removeRootTag( element, result ); } ); } + + if ( this.props.tagsToEliminate ) { + this.props.tagsToEliminate.forEach( ( element ) => { + result = this.removeTag( element, result ); + } ); + } return result; } @@ -307,6 +313,11 @@ export class RichText extends Component { const closingTagRegexp = RegExp( '</' + tag + '>$', 'gim' ); return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); } + removeTag( tag, html ) { + const openingTagRegexp = RegExp( '<' + tag + '>', 'gim' ); + const closingTagRegexp = RegExp( '</' + tag + '>', 'gim' ); + return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); + } /* * Handles any case where the content of the AztecRN instance has changed diff --git a/packages/components/src/primitives/block-quotation/index.native.js b/packages/components/src/primitives/block-quotation/index.native.js index dbcc249fe1bff5..abbafa09cae949 100644 --- a/packages/components/src/primitives/block-quotation/index.native.js +++ b/packages/components/src/primitives/block-quotation/index.native.js @@ -16,6 +16,9 @@ export const BlockQuotation = ( props ) => { if ( child && child.props.identifier === 'citation' ) { return cloneElement( child, { style: styles.wpBlockQuoteCitation } ); } + if ( child && child.props.identifier === 'value' ) { + return cloneElement( child, { tagsToEliminate: [ 'div' ] } ); + } return child; } ); return ( From 1f3f7944faf5214c4633144646dda30a256b8c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Mon, 10 Jun 2019 20:50:01 +0100 Subject: [PATCH 307/664] Rnmobile/change colors on rich text mobile (#16016) * Set text in rich text to gray 900 color. * Update links color to be blue 500. * Use specific color for placeholder text. * Use standard property for placeholder colours. --- .../block-editor/src/components/rich-text/index.native.js | 5 +++-- .../src/components/rich-text/style.native.scss | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index f9eeb33caac677..0a33ebd32b491a 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -819,7 +819,7 @@ export class RichText extends Component { } } text={ { text: html, eventCount: this.lastEventCount, selection } } placeholder={ this.props.placeholder } - placeholderTextColor={ this.props.placeholderTextColor || styles[ 'block-editor-rich-text' ].textDecorationColor } + placeholderTextColor={ this.props.placeholderTextColor || styles[ 'block-editor-rich-text-placeholder' ].color } deleteEnter={ this.props.deleteEnter } onChange={ this.onChange } onFocus={ this.onFocus } @@ -832,7 +832,8 @@ export class RichText extends Component { onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } onSelectionChange={ this.onSelectionChangeFromAztec } blockType={ { tag: tagName } } - color={ 'black' } + color={ styles[ 'block-editor-rich-text' ].color } + linkTextColor={ styles[ 'block-editor-rich-text' ].textDecorationColor } maxImagesWidth={ 200 } fontFamily={ this.props.fontFamily || styles[ 'block-editor-rich-text' ].fontFamily } fontSize={ this.props.fontSize || ( style && style.fontSize ) } diff --git a/packages/block-editor/src/components/rich-text/style.native.scss b/packages/block-editor/src/components/rich-text/style.native.scss index ef530f4d3c7817..77413c5be1f9e4 100644 --- a/packages/block-editor/src/components/rich-text/style.native.scss +++ b/packages/block-editor/src/components/rich-text/style.native.scss @@ -1,6 +1,11 @@ .block-editor-rich-text { font-family: $default-regular-font; - text-decoration-color: $gray; min-height: $min-height-paragraph; + color: $gray-900; + text-decoration-color: $blue-500; +} + +.block-editor-rich-text-placeholder { + color: $gray; } From 51d443415848b1c8ad42d47bfc639efc98c34dfd Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 11 Jun 2019 00:41:10 -0400 Subject: [PATCH 308/664] Scripts: Fix default use of lint-js file patterns (#16079) * Scripts: Call hasFileInCliArgs as function * Scripts: Apply default file arguments as spread array * Fix lint issues found --- .../components/media-upload/test/index.native.js | 4 ++-- .../src/components/rich-text/test/index.native.js | 14 +++++++------- packages/block-library/src/heading/edit.native.js | 2 -- .../src/video/video-player.android.js | 2 +- packages/scripts/scripts/lint-js.js | 2 +- packages/scripts/scripts/lint-pkg-json.js | 4 ++-- packages/scripts/scripts/lint-style.js | 2 +- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js index 76acd6132f4dbb..72a2015277e4df 100644 --- a/packages/block-editor/src/components/media-upload/test/index.native.js +++ b/packages/block-editor/src/components/media-upload/test/index.native.js @@ -52,7 +52,7 @@ describe( 'MediaUpload component', () => { ); } } /> ); - expect( wrapper.find( 'Picker' ).length ).toEqual( 1 ); + expect( wrapper.find( 'Picker' ) ).toHaveLength( 1 ); } ); it( 'shows right media capture option for media type', () => { @@ -68,7 +68,7 @@ describe( 'MediaUpload component', () => { ); } } /> ); - expect( wrapper.find( 'Picker' ).props().options.filter( ( item ) => item.label === expectedOption ).length ).toEqual( 1 ); + expect( wrapper.find( 'Picker' ).props().options.filter( ( item ) => item.label === expectedOption ) ).toHaveLength( 1 ); }; expectOptionForMediaType( MEDIA_TYPE_IMAGE, OPTION_TAKE_PHOTO ); expectOptionForMediaType( MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO ); diff --git a/packages/block-editor/src/components/rich-text/test/index.native.js b/packages/block-editor/src/components/rich-text/test/index.native.js index 399e627308f65e..ec0cbb77195244 100644 --- a/packages/block-editor/src/components/rich-text/test/index.native.js +++ b/packages/block-editor/src/components/rich-text/test/index.native.js @@ -32,11 +32,11 @@ describe( 'RichText Native', () => { describe( 'Adds new line on Enter', () => { let newValue; - const wrapper = shallow( <RichText + const wrapper = shallow( <RichText rootTagsToEliminate={ [ 'p' ] } - value={ "" } + value="" onChange={ ( value ) => { - newValue = value + newValue = value; } } formatTypes={ [] } onSelectionChange={ jest.fn() } @@ -45,12 +45,12 @@ describe( 'RichText Native', () => { const event = { nativeEvent: { eventCount: 0, - } + }, }; - wrapper.instance().onEnter(event); + wrapper.instance().onEnter( event ); it( ' Adds <br> tag to content after pressing Enter key', () => { - expect( newValue ).toEqual( "<br>" ); - }); + expect( newValue ).toEqual( '<br>' ); + } ); } ); } ); diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index 1f409356b12a1f..389791663b0541 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -18,9 +18,7 @@ import { createBlock } from '@wordpress/blocks'; const HeadingEdit = ( { attributes, - isSelected, mergeBlocks, - onBlur, onFocus, onReplace, setAttributes, diff --git a/packages/block-library/src/video/video-player.android.js b/packages/block-library/src/video/video-player.android.js index b1ccc057ba03c3..669948bbdd2d3b 100644 --- a/packages/block-library/src/video/video-player.android.js +++ b/packages/block-library/src/video/video-player.android.js @@ -19,7 +19,7 @@ const Video = ( props ) => { // We are using built-in player controls becasue manually // calling presentFullscreenPlayer() is not working for android controls={ isSelected } - muted={ !isSelected } + muted={ ! isSelected } /> </View> ); diff --git a/packages/scripts/scripts/lint-js.js b/packages/scripts/scripts/lint-js.js index 04d24cd538fcb2..af2e450037b397 100644 --- a/packages/scripts/scripts/lint-js.js +++ b/packages/scripts/scripts/lint-js.js @@ -18,7 +18,7 @@ const { const args = getCliArgs(); -const defaultFilesArgs = ! hasFileInCliArgs ? [ '.' ] : []; +const defaultFilesArgs = hasFileInCliArgs() ? [] : [ '.' ]; // See: https://eslint.org/docs/user-guide/configuring#using-configuration-files-1. const hasLintConfig = hasCliArg( '-c' ) || diff --git a/packages/scripts/scripts/lint-pkg-json.js b/packages/scripts/scripts/lint-pkg-json.js index 3d15b2bddb1d07..6c328cb0169bff 100644 --- a/packages/scripts/scripts/lint-pkg-json.js +++ b/packages/scripts/scripts/lint-pkg-json.js @@ -18,7 +18,7 @@ const { const args = getCliArgs(); -const defaultFilesArgs = ! hasFileInCliArgs ? [ '.' ] : []; +const defaultFilesArgs = hasFileInCliArgs() ? [] : [ '.' ]; // See: https://github.com/tclindner/npm-package-json-lint/wiki/configuration#configuration. const hasLintConfig = hasCliArg( '-c' ) || @@ -41,7 +41,7 @@ const defaultIgnoreArgs = ! hasIgnoredFiles ? const result = spawn( resolveBin( 'npm-package-json-lint', { executable: 'npmPkgJsonLint' } ), - [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, defaultFilesArgs ], + [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js index 024e04983ca8b1..9d22a81c96ba25 100644 --- a/packages/scripts/scripts/lint-style.js +++ b/packages/scripts/scripts/lint-style.js @@ -18,7 +18,7 @@ const { const args = getCliArgs(); -const defaultFilesArgs = ! hasFileInCliArgs ? [ '**/*.{css,scss}' ] : []; +const defaultFilesArgs = hasFileInCliArgs() ? [] : [ '**/*.{css,scss}' ]; // See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#loading-the-configuration-object. const hasLintConfig = hasCliArg( '--config' ) || From 6b8eb7ae105b8078f606c2ad368d1c2f49cb7702 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Tue, 11 Jun 2019 00:46:42 -0400 Subject: [PATCH 309/664] fix(components/tab-panel): improve className safety by using classNames (#16081) This commit does two things: 1. Improves safety of the interpolation of className for `tab`s (currently the string `undefined` will show up if the `tab` doesn't have the `className` property defined). 2. Relaxes the strictness and makes the `className` property of `tab` optional. --- packages/components/src/tab-panel/README.md | 12 ++++++------ packages/components/src/tab-panel/index.js | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/components/src/tab-panel/README.md b/packages/components/src/tab-panel/README.md index 89c6b095722505..def626516b3a39 100644 --- a/packages/components/src/tab-panel/README.md +++ b/packages/components/src/tab-panel/README.md @@ -114,15 +114,15 @@ The function called when a tab has been selected. It is passed the `tabName` as #### tabs -A list of tabs where each tab is defined by an object with the following fields: +An array of objects containing the following properties: -1. name: String. Defines the key for the tab -2. title: String. Defines the translated text for the tab -3. className: String. Defines the class to put on the tab. +- `name`: `(string)` Defines the key for the tab. +- `title`:`(string)` Defines the translated text for the tab. +- `className`:`(string)` Optional. Defines the class to put on the tab. -Other fields may be added to the object and accessed from the child function if desired. +>> **Note:** Other fields may be added to the object and accessed from the child function if desired. -- Type: Array +- Type: `Array` - Required: Yes #### activeClass diff --git a/packages/components/src/tab-panel/index.js b/packages/components/src/tab-panel/index.js index 5558272f2ffc4a..71f4683bcbef80 100644 --- a/packages/components/src/tab-panel/index.js +++ b/packages/components/src/tab-panel/index.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import classnames from 'classnames'; import { partial, noop, find } from 'lodash'; /** @@ -74,7 +75,7 @@ class TabPanel extends Component { className="components-tab-panel__tabs" > { tabs.map( ( tab ) => ( - <TabButton className={ `${ tab.className } ${ tab.name === selected ? activeClass : '' }` } + <TabButton className={ classnames( tab.className, { [ activeClass ]: tab.name === selected } ) } tabId={ instanceId + '-' + tab.name } aria-controls={ instanceId + '-' + tab.name + '-view' } selected={ tab.name === selected } From d377dbbeae38fb26466e56a73d78bbb9710401e1 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Tue, 11 Jun 2019 00:49:39 -0400 Subject: [PATCH 310/664] remove defaultProp from required property in components/modal (#16074) closes #16062 --- packages/components/src/modal/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/components/src/modal/index.js b/packages/components/src/modal/index.js index d82c6928a1073c..ff35faf9222f99 100644 --- a/packages/components/src/modal/index.js +++ b/packages/components/src/modal/index.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { noop } from 'lodash'; /** * WordPress dependencies @@ -166,7 +165,6 @@ Modal.defaultProps = { bodyOpenClassName: 'modal-open', role: 'dialog', title: null, - onRequestClose: noop, focusOnMount: true, shouldCloseOnEsc: true, shouldCloseOnClickOutside: true, From 851388f0c8b8fbbf9b8040f261cce9b88a20dd49 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Tue, 11 Jun 2019 01:20:56 -0400 Subject: [PATCH 311/664] docs(components/text-control): Fix incorrectly documented "value" prop (#16083) --- packages/components/src/text-control/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/text-control/README.md b/packages/components/src/text-control/README.md index 49066ad3cf86ba..2fa725b3c3e88b 100644 --- a/packages/components/src/text-control/README.md +++ b/packages/components/src/text-control/README.md @@ -96,7 +96,7 @@ Type of the input element to render. Defaults to "text". #### value The current value of the input. -- Type: `Number` +- Type: `String | Number` - Required: Yes #### className From 8f65b7c3a4ee22bced1b4b81aa92c8954e34da10 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 11 Jun 2019 13:43:38 +0100 Subject: [PATCH 312/664] Fix: Remove editor usage from media blocks. (#15548) --- packages/block-library/src/audio/edit.js | 22 ++++++++++--- packages/block-library/src/file/edit.js | 11 +++++-- packages/block-library/src/gallery/edit.js | 32 +++++++++++++++++-- .../block-library/src/gallery/transforms.js | 18 ++--------- packages/block-library/src/image/edit.js | 16 ++++++++-- packages/block-library/src/video/edit.js | 18 +++++++++-- 6 files changed, 88 insertions(+), 29 deletions(-) diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index a518f077011e6a..164b8c22c9a7ba 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { getBlobByURL, isBlobURL } from '@wordpress/blob'; +import { compose } from '@wordpress/compose'; import { Disabled, IconButton, @@ -18,9 +19,9 @@ import { MediaPlaceholder, RichText, } from '@wordpress/block-editor'; -import { mediaUpload } from '@wordpress/editor'; import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { withSelect } from '@wordpress/data'; /** * Internal dependencies @@ -49,7 +50,12 @@ class AudioEdit extends Component { } componentDidMount() { - const { attributes, noticeOperations, setAttributes } = this.props; + const { + attributes, + mediaUpload, + noticeOperations, + setAttributes, + } = this.props; const { id, src = '' } = attributes; if ( ! id && isBlobURL( src ) ) { @@ -207,5 +213,13 @@ class AudioEdit extends Component { /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ } } - -export default withNotices( AudioEdit ); +export default compose( [ + withSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + const { __experimentalMediaUpload } = getSettings(); + return { + mediaUpload: __experimentalMediaUpload, + }; + } ), + withNotices, +] )( AudioEdit ); diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index f06d2be38c98a7..a27d22a326f474 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -27,7 +27,6 @@ import { MediaPlaceholder, RichText, } from '@wordpress/block-editor'; -import { mediaUpload } from '@wordpress/editor'; import { Component } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; @@ -56,7 +55,12 @@ class FileEdit extends Component { } componentDidMount() { - const { attributes, noticeOperations, setAttributes } = this.props; + const { + attributes, + mediaUpload, + noticeOperations, + setAttributes, + } = this.props; const { downloadButtonText, href } = attributes; // Upload a file drag-and-dropped into the editor @@ -248,9 +252,12 @@ class FileEdit extends Component { export default compose( [ withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); + const { getSettings } = select( 'core/block-editor' ); + const { __experimentalMediaUpload } = getSettings(); const { id } = props.attributes; return { media: id === undefined ? undefined : getMedia( id ), + mediaUpload: __experimentalMediaUpload, }; } ), withNotices, diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index c5aebed551849f..f8d1db665208f0 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -2,11 +2,12 @@ * External dependencies */ import classnames from 'classnames'; -import { filter, map } from 'lodash'; +import { every, filter, forEach, map } from 'lodash'; /** * WordPress dependencies */ +import { compose } from '@wordpress/compose'; import { IconButton, PanelBody, @@ -25,6 +26,8 @@ import { } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; +import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; +import { withSelect } from '@wordpress/data'; /** * Internal dependencies @@ -174,6 +177,20 @@ class GalleryEdit extends Component { } ); } + componentDidMount() { + const { attributes, mediaUpload } = this.props; + const { images } = attributes; + if ( every( images, ( { url } ) => isBlobURL( url ) ) ) { + const filesList = map( images, ( { url } ) => getBlobByURL( url ) ); + forEach( images, ( { url } ) => revokeBlobURL( url ) ); + mediaUpload( { + filesList, + onFileChange: this.onSelectImages, + allowedTypes: [ 'image' ], + } ); + } + } + componentDidUpdate( prevProps ) { // Deselect images when deselecting the block if ( ! this.props.isSelected && prevProps.isSelected ) { @@ -312,5 +329,16 @@ class GalleryEdit extends Component { ); } } +export default compose( [ + withSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + const { + __experimentalMediaUpload, + } = getSettings(); -export default withNotices( GalleryEdit ); + return { + mediaUpload: __experimentalMediaUpload, + }; + } ), + withNotices, +] )( GalleryEdit ); diff --git a/packages/block-library/src/gallery/transforms.js b/packages/block-library/src/gallery/transforms.js index af8d5d164c10e0..ea752fe296bd9e 100644 --- a/packages/block-library/src/gallery/transforms.js +++ b/packages/block-library/src/gallery/transforms.js @@ -1,13 +1,12 @@ /** * External dependencies */ -import { filter, every, map } from 'lodash'; +import { filter, every } from 'lodash'; /** * WordPress dependencies */ import { createBlock } from '@wordpress/blocks'; -import { mediaUpload } from '@wordpress/editor'; import { createBlobURL } from '@wordpress/blob'; /** @@ -89,25 +88,12 @@ const transforms = { isMatch( files ) { return files.length !== 1 && every( files, ( file ) => file.type.indexOf( 'image/' ) === 0 ); }, - transform( files, onChange ) { + transform( files ) { const block = createBlock( 'core/gallery', { images: files.map( ( file ) => pickRelevantMediaFiles( { url: createBlobURL( file ), } ) ), } ); - mediaUpload( { - filesList: files, - onFileChange: ( images ) => { - const imagesAttr = images.map( - pickRelevantMediaFiles, - ); - onChange( block.clientId, { - ids: map( imagesAttr, 'id' ), - images: imagesAttr, - } ); - }, - allowedTypes: [ 'image' ], - } ); return block; }, }, diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index ecc498e2988fc9..42b271903fe049 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -43,7 +43,6 @@ import { MediaPlaceholder, RichText, } from '@wordpress/block-editor'; -import { mediaUpload } from '@wordpress/editor'; import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; @@ -126,7 +125,12 @@ class ImageEdit extends Component { } componentDidMount() { - const { attributes, setAttributes, noticeOperations } = this.props; + const { + attributes, + mediaUpload, + noticeOperations, + setAttributes, + } = this.props; const { id, url = '' } = attributes; if ( isTemporaryImage( id, url ) ) { @@ -721,13 +725,19 @@ export default compose( [ const { getMedia } = select( 'core' ); const { getSettings } = select( 'core/block-editor' ); const { id } = props.attributes; - const { maxWidth, isRTL, imageSizes } = getSettings(); + const { + __experimentalMediaUpload, + imageSizes, + isRTL, + maxWidth, + } = getSettings(); return { image: id ? getMedia( id ) : null, maxWidth, isRTL, imageSizes, + mediaUpload: __experimentalMediaUpload, }; } ), withViewportMatch( { isLargeViewport: 'medium' } ), diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index f8380b7edeafc6..9931508d2c6c3f 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -22,7 +22,6 @@ import { MediaUploadCheck, RichText, } from '@wordpress/block-editor'; -import { mediaUpload } from '@wordpress/editor'; import { Component, createRef } from '@wordpress/element'; import { __, @@ -32,6 +31,9 @@ import { compose, withInstanceId, } from '@wordpress/compose'; +import { + withSelect, +} from '@wordpress/data'; /** * Internal dependencies @@ -61,7 +63,12 @@ class VideoEdit extends Component { } componentDidMount() { - const { attributes, noticeOperations, setAttributes } = this.props; + const { + attributes, + mediaUpload, + noticeOperations, + setAttributes, + } = this.props; const { id, src = '' } = attributes; if ( ! id && isBlobURL( src ) ) { const file = getBlobByURL( src ); @@ -312,6 +319,13 @@ class VideoEdit extends Component { } export default compose( [ + withSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); + const { __experimentalMediaUpload } = getSettings(); + return { + mediaUpload: __experimentalMediaUpload, + }; + } ), withNotices, withInstanceId, ] )( VideoEdit ); From 0563331d1e1911e51f735b89c61c8b3614941375 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 11 Jun 2019 14:42:21 +0100 Subject: [PATCH 313/664] Fix: Playground build; Remove @babel/polyfill import from the playground (#15947) --- playground/src/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/playground/src/index.js b/playground/src/index.js index 0e0bc6a87f8d84..3f125282c49090 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import '@babel/polyfill'; - /** * WordPress dependencies */ From d3c6a27ad620902d3228032154ad8cf15b6f5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 11 Jun 2019 20:58:04 +0200 Subject: [PATCH 314/664] Project management: Fix milestone version selection when RC is included in version (#16084) --- .github/actions/milestone-it/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/milestone-it/entrypoint.sh b/.github/actions/milestone-it/entrypoint.sh index a0b94110f532a2..835875dc61a1b4 100755 --- a/.github/actions/milestone-it/entrypoint.sh +++ b/.github/actions/milestone-it/entrypoint.sh @@ -29,7 +29,7 @@ minor=${parts[1]} # 3. Determine next milestone. -if [ minor == '9' ]; then +if [[ $minor == 9* ]]; then major=$((major+1)) minor="0" else From 6156365ef4266354f62e2af93fe63ac3b6e82467 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Tue, 11 Jun 2019 17:10:04 -0400 Subject: [PATCH 315/664] Update fill-rule and clip-rule to fillRule and clipRule (#16096) --- packages/block-library/src/group/icon.js | 2 +- .../editor/src/components/convert-to-group-buttons/icons.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/group/icon.js b/packages/block-library/src/group/icon.js index e1b36f7f72cadb..f9d9dc7790ba95 100644 --- a/packages/block-library/src/group/icon.js +++ b/packages/block-library/src/group/icon.js @@ -4,5 +4,5 @@ import { Path, SVG } from '@wordpress/components'; export default ( - <SVG width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill-rule="evenodd" clip-rule="evenodd" d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" /><Path fill-rule="evenodd" clip-rule="evenodd" d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" /></SVG> + <SVG width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" /><Path fillRule="evenodd" clipRule="evenodd" d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" /></SVG> ); diff --git a/packages/editor/src/components/convert-to-group-buttons/icons.js b/packages/editor/src/components/convert-to-group-buttons/icons.js index d1ee497662cd49..990d15dd5ac1cd 100644 --- a/packages/editor/src/components/convert-to-group-buttons/icons.js +++ b/packages/editor/src/components/convert-to-group-buttons/icons.js @@ -3,11 +3,11 @@ */ import { Icon, SVG, Path } from '@wordpress/components'; -const GroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fill-rule="evenodd" clip-rule="evenodd" d="M8 5a1 1 0 0 0-1 1v3H6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3h1a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H8zm3 6H7v2h4v-2zM9 9V7h4v2H9z" /><Path fill-rule="evenodd" clip-rule="evenodd" d="M1 3a2 2 0 0 0 1 1.732v10.536A2 2 0 1 0 4.732 18h10.536A2 2 0 1 0 18 15.268V4.732A2 2 0 1 0 15.268 2H4.732A2 2 0 0 0 1 3zm14.268 1H4.732A2.01 2.01 0 0 1 4 4.732v10.536c.304.175.557.428.732.732h10.536a2.01 2.01 0 0 1 .732-.732V4.732A2.01 2.01 0 0 1 15.268 4z" /></SVG>; +const GroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M8 5a1 1 0 0 0-1 1v3H6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3h1a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H8zm3 6H7v2h4v-2zM9 9V7h4v2H9z" /><Path fillRule="evenodd" clipRule="evenodd" d="M1 3a2 2 0 0 0 1 1.732v10.536A2 2 0 1 0 4.732 18h10.536A2 2 0 1 0 18 15.268V4.732A2 2 0 1 0 15.268 2H4.732A2 2 0 0 0 1 3zm14.268 1H4.732A2.01 2.01 0 0 1 4 4.732v10.536c.304.175.557.428.732.732h10.536a2.01 2.01 0 0 1 .732-.732V4.732A2.01 2.01 0 0 1 15.268 4z" /></SVG>; export const Group = <Icon icon={ GroupSVG } />; -const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fill-rule="evenodd" clip-rule="evenodd" d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" /><Path fill-rule="evenodd" clip-rule="evenodd" d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" /></SVG>; +const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" /><Path fillRule="evenodd" clipRule="evenodd" d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" /></SVG>; export const Ungroup = <Icon icon={ UngroupSVG } />; From 164bc758349181a14dcb9c042f858b4ca07c0d11 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Tue, 11 Jun 2019 17:30:00 -0400 Subject: [PATCH 316/664] [docs] fix incorrect prop documentation for components/date-time (#16073) closes #16057 --- packages/components/src/date-time/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md index f0fa63c69b6ce4..530a67e0e8e576 100644 --- a/packages/components/src/date-time/README.md +++ b/packages/components/src/date-time/README.md @@ -52,8 +52,7 @@ The current date and time at initialization. Optionally pass in a `null` value t The function called when a new date or time has been selected. It is passed the `currentDate` as an argument. - Type: `Function` -- Required: No -- Default: `noop` +- Required: Yes ### is12Hour From 79b94d03003f6fe058c2b14f53719d87bc6d7fd6 Mon Sep 17 00:00:00 2001 From: Tammie Lister <tammie@automattic.com> Date: Tue, 11 Jun 2019 23:35:55 +0100 Subject: [PATCH 317/664] Adjust margins on datepicker to balance (#16097) Fixes #15653 --- packages/components/src/date-time/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 02b2686f4f8821..d25d923dd24d94 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -211,7 +211,7 @@ // Hack to center the datepicker component within the popover. // It sets its own styles so centering is tricky. .components-popover .components-datetime__date { - padding-left: 6px; + padding-left: 4px; } // Used to prevent z-index issues on mobile. From 0cc2e02811b3fc64e5365927d16a105debc29895 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Tue, 11 Jun 2019 15:40:27 -0700 Subject: [PATCH 318/664] Update Tag Cloud copy when no terms are found (#16098) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before: "No terms found." After: "Your site doesn’t have any tags, so there’s nothing to display here at the moment." It updates based on the chosen taxonomy. Props @ryelle for writing the logic and @michelleweber for brainstorming copy with me. --- packages/block-library/src/tag-cloud/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/tag-cloud/index.php b/packages/block-library/src/tag-cloud/index.php index 6786e076b80f1d..b5095a2998fab1 100644 --- a/packages/block-library/src/tag-cloud/index.php +++ b/packages/block-library/src/tag-cloud/index.php @@ -30,7 +30,8 @@ function render_block_core_tag_cloud( $attributes ) { $tag_cloud = wp_tag_cloud( $args ); if ( ! $tag_cloud ) { - $tag_cloud = esc_html( __( 'No terms to show.' ) ); + $labels = get_taxonomy_labels( get_taxonomy( $attributes['taxonomy'] ) ); + $tag_cloud = esc_html( sprintf( __( 'Your site doesn&#8217;t have any %s, so there&#8217;s nothing to display here at the moment.' ), strtolower( $labels->name ) ) ); } return sprintf( From 2ab29100b413feb4932cbd576c9e7a9bde31c18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Wed, 12 Jun 2019 08:50:55 +0200 Subject: [PATCH 319/664] Update link to permalink documentation. (#16087) --- packages/edit-post/src/components/sidebar/post-link/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 71f20775034ff5..674c14b8615f9b 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -88,7 +88,7 @@ function PostLink( { /> <p> { __( 'The last part of the URL. ' ) } - <ExternalLink href="https://codex.wordpress.org/Posts_Add_New_Screen"> + <ExternalLink href="https://wordpress.org/support/article/writing-posts/#post-field-descriptions"> { __( 'Read about permalinks' ) } </ExternalLink> </p> From 1b4f4969c67336e729e37d88c1260f03a6d1ab36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Wed, 12 Jun 2019 10:54:14 +0200 Subject: [PATCH 320/664] Fix: For passing the PHP Coding Standards (#16107) * Fix PHP CS * Add translator comment --- packages/block-library/src/rss/index.php | 3 ++- packages/block-library/src/tag-cloud/index.php | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php index 92406d09b6b1d6..1bf217be3a9b59 100644 --- a/packages/block-library/src/rss/index.php +++ b/packages/block-library/src/rss/index.php @@ -109,7 +109,8 @@ function render_block_core_rss( $attributes ) { * Registers the `core/rss` block on server. */ function register_block_core_rss() { - register_block_type( 'core/rss', + register_block_type( + 'core/rss', array( 'attributes' => array( 'align' => array( diff --git a/packages/block-library/src/tag-cloud/index.php b/packages/block-library/src/tag-cloud/index.php index b5095a2998fab1..cf9c4d24537dbd 100644 --- a/packages/block-library/src/tag-cloud/index.php +++ b/packages/block-library/src/tag-cloud/index.php @@ -30,8 +30,14 @@ function render_block_core_tag_cloud( $attributes ) { $tag_cloud = wp_tag_cloud( $args ); if ( ! $tag_cloud ) { - $labels = get_taxonomy_labels( get_taxonomy( $attributes['taxonomy'] ) ); - $tag_cloud = esc_html( sprintf( __( 'Your site doesn&#8217;t have any %s, so there&#8217;s nothing to display here at the moment.' ), strtolower( $labels->name ) ) ); + $labels = get_taxonomy_labels( get_taxonomy( $attributes['taxonomy'] ) ); + $tag_cloud = esc_html( + sprintf( + /* translators: %s: taxonomy name */ + __( 'Your site doesn&#8217;t have any %s, so there&#8217;s nothing to display here at the moment.' ), + strtolower( $labels->name ) + ) + ); } return sprintf( From af57dc9c051c017e5f9ca630c7db887c563c81ee Mon Sep 17 00:00:00 2001 From: Joen Asmussen <asmussen@gmail.com> Date: Wed, 12 Jun 2019 11:10:24 +0200 Subject: [PATCH 321/664] Fix horizontal scrollbar on full-wide blocks with nesting. (#16085) This fixes #15192. This PR fixes two issues: 1. There was an issue, probably rebase related, where the columns block had CSS to prevent horizontal scrollbars when fullwide, but which didn't work anymore. I simply fixed the selector again. 2. The recent merge of the clickthrough PR failed to take into account fullwide blocks with nesting, and caused a horizontal scrollbar due to the overlay extending beyond the canvas. To test this PR, please verify that full wide alignments work as intended. You can test columns, image, embed, media & text. Please also verify that blocks with nesting work as intended, both in fullwide and not wide situations. --- .../block-editor/src/components/inner-blocks/style.scss | 8 ++++++++ packages/block-library/src/columns/editor.scss | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index 61ed91c42ac02f..ff8e4b9adbe96a 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -1,3 +1,5 @@ +// Add clickable overlay to blocks with nesting. +// This makes it easy to select all layers of the block. .block-editor-inner-blocks.has-overlay { &::after { content: ""; @@ -9,3 +11,9 @@ z-index: z-index(".block-editor-inner-blocks.has-overlay::after"); } } + +// On fullwide blocks, don't go beyond the canvas. +[data-align="full"] > .editor-block-list__block-edit > [data-block] .has-overlay::after { + right: 0; + left: 0; +} diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index f8780b91ebab53..6e9e2e60659114 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -21,7 +21,7 @@ // Fullwide: show margin left/right to ensure there's room for the side UI. // This is not a 1:1 preview with the front-end where these margins would presumably be zero. -.editor-block-list__block[data-align="full"] [data-type="core/columns"][data-align="full"] .wp-block-columns > .editor-inner-blocks { +[data-type="core/columns"][data-align="full"] .wp-block-columns > .editor-inner-blocks { padding-left: $block-padding; padding-right: $block-padding; From 59ffb86e9fbd35ac5e84142b1625028f7af03d2e Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Wed, 12 Jun 2019 08:40:59 -0400 Subject: [PATCH 322/664] Bump plugin version to 5.9.0 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index d9c384353ca202..1450aa20c4b750 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.9.0-rc.1 + * Version: 5.9.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index aa639527442e65..c2c500f9773775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.0-rc.1", + "version": "5.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4ff2130f820687..0ce754a7d4579c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.0-rc.1", + "version": "5.9.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 2e8358791fc621a4c580dd91031b8c84d09eb385 Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Wed, 12 Jun 2019 18:37:51 +0200 Subject: [PATCH 323/664] [RNMobile] Fix pasting text on Post Title (#16116) * Add onEnter prop to RichText to allow custom handling of onEnter * Remove reduntant line --- .../block-editor/src/components/rich-text/index.native.js | 5 +++++ packages/editor/src/components/post-title/index.native.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 0a33ebd32b491a..489d0a5fbeaa95 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -358,6 +358,11 @@ export class RichText extends Component { // eslint-disable-next-line no-unused-vars onEnter( event ) { + if ( this.props.onEnter ) { + this.props.onEnter(); + return; + } + this.lastEventCount = event.nativeEvent.eventCount; this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index c895abcbebc108..2c9435e1ef6b36 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -102,7 +102,7 @@ class PostTitle extends Component { placeholder={ decodedPlaceholder } value={ title } onSplit={ () => { } } - onReplace={ this.props.onEnterPress } + onEnter={ this.props.onEnterPress } disableEditingMenu={ true } setRef={ ( ref ) => { this.titleViewRef = ref; From 41a5b4cf054ac13ff434fedbc9c0bd508dcfeeaa Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 12 Jun 2019 18:00:52 +0100 Subject: [PATCH 324/664] chore(release): publish - @wordpress/a11y@2.4.0 - @wordpress/annotations@1.4.0 - @wordpress/api-fetch@3.3.0 - @wordpress/babel-preset-default@4.3.0 - @wordpress/block-editor@2.2.0 - @wordpress/block-library@2.6.0 - @wordpress/blocks@6.4.0 - @wordpress/browserslist-config@2.5.0 - @wordpress/components@8.0.0 - @wordpress/compose@3.4.0 - @wordpress/core-data@2.4.0 - @wordpress/custom-templated-path-webpack-plugin@1.4.0 - @wordpress/data-controls@1.0.0 - @wordpress/data@4.6.0 - @wordpress/deprecated@2.4.0 - @wordpress/dom-ready@2.4.0 - @wordpress/e2e-test-utils@2.1.0 - @wordpress/e2e-tests@1.3.0 - @wordpress/edit-post@3.5.0 - @wordpress/edit-widgets@0.4.0 - @wordpress/editor@9.4.0 - @wordpress/element@2.5.0 - @wordpress/escape-html@1.4.0 - @wordpress/eslint-plugin@2.3.0 - @wordpress/format-library@1.6.0 - @wordpress/hooks@2.4.0 - @wordpress/html-entities@2.4.0 - @wordpress/i18n@3.5.0 - @wordpress/is-shallow-equal@1.4.0 - @wordpress/jest-preset-default@4.2.0 - @wordpress/keycodes@2.4.0 - @wordpress/library-export-default-webpack-plugin@1.3.0 - @wordpress/list-reusable-blocks@1.5.0 - @wordpress/media-utils@0.2.0 - @wordpress/notices@1.5.0 - @wordpress/npm-package-json-lint-config@2.0.0 - @wordpress/nux@3.4.0 - @wordpress/plugins@2.4.0 - @wordpress/redux-routine@3.4.0 - @wordpress/rich-text@3.4.0 - @wordpress/scripts@3.3.0 - @wordpress/server-side-render@1.0.0 - @wordpress/token-list@1.4.0 - @wordpress/viewport@2.5.0 - @wordpress/wordcount@2.4.0 --- packages/a11y/package.json | 2 +- packages/annotations/package.json | 2 +- packages/api-fetch/package.json | 2 +- packages/babel-preset-default/package.json | 2 +- packages/block-editor/package.json | 2 +- packages/block-library/package.json | 2 +- packages/blocks/package.json | 2 +- packages/browserslist-config/package.json | 2 +- packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/package.json | 2 +- packages/custom-templated-path-webpack-plugin/package.json | 2 +- packages/data-controls/package.json | 2 +- packages/data/package.json | 2 +- packages/deprecated/package.json | 2 +- packages/dom-ready/package.json | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-widgets/package.json | 2 +- packages/editor/package.json | 2 +- packages/element/package.json | 2 +- packages/escape-html/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/package.json | 2 +- packages/hooks/package.json | 2 +- packages/html-entities/package.json | 2 +- packages/i18n/package.json | 2 +- packages/is-shallow-equal/package.json | 2 +- packages/jest-preset-default/package.json | 2 +- packages/keycodes/package.json | 2 +- packages/library-export-default-webpack-plugin/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/media-utils/package.json | 2 +- packages/notices/package.json | 2 +- packages/npm-package-json-lint-config/package.json | 2 +- packages/nux/package.json | 2 +- packages/plugins/package.json | 2 +- packages/redux-routine/package.json | 2 +- packages/rich-text/package.json | 2 +- packages/scripts/package.json | 2 +- packages/server-side-render/package.json | 2 +- packages/token-list/package.json | 2 +- packages/viewport/package.json | 2 +- packages/wordcount/package.json | 2 +- 45 files changed, 45 insertions(+), 45 deletions(-) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index fc3561a2bef4b8..1621eb97643289 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "2.3.0", + "version": "2.4.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 4f0c9165ff4edc..16bec17d283ed8 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.3.0", + "version": "1.4.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 1c647d817bbf02..0221ac0a1e8a37 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "3.2.0", + "version": "3.3.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 22c29f779e529e..e0ce0c0f2a3cd1 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "4.2.0", + "version": "4.3.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index d8b9d21b64dd4e..da701fc0c08669 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "2.1.0", + "version": "2.2.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index daa427cffd4a94..f57f2988524cee 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.5.0", + "version": "2.6.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index a00b6b505ab298..72f5fc9e3af87d 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.3.0", + "version": "6.4.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 331b74b323fadd..9e70302edeb817 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "2.4.0", + "version": "2.5.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index df7d8afa76457f..99c44a249b7e42 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "7.4.0", + "version": "8.0.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/compose/package.json b/packages/compose/package.json index f04e6d753ce11b..57eac024c3f47a 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "3.3.0", + "version": "3.4.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 96b18edd614019..55f219865f0e69 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.3.0", + "version": "2.4.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index bf7af1e6a461f3..463e59991aa46b 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/custom-templated-path-webpack-plugin", - "version": "1.3.0", + "version": "1.4.0", "description": "Webpack plugin for creating custom path template tags.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 9ffbf80cd5165e..e54574c9eec93b 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "1.0.0-beta.1", + "version": "1.0.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/package.json b/packages/data/package.json index d0f2d74f354fc6..30d331281569c7 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.5.0", + "version": "4.6.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 033c6ae472cf83..4315c6d81039aa 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "2.3.0", + "version": "2.4.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index d390d8db4ea3b0..827accd9b47dd7 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "2.3.0", + "version": "2.4.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 4ab119dbc0557c..ab69632a58f6ee 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "2.0.0", + "version": "2.1.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 33943fda4cc2fe..d7b75699be7f83 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.2.1", + "version": "1.3.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index a2566dd750a75f..b6c85e26311ece 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.4.0", + "version": "3.5.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index d92d4a66c78068..1acce755f85426 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "0.3.0", + "version": "0.4.0", "private": true, "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", diff --git a/packages/editor/package.json b/packages/editor/package.json index 9578fdf41caf10..ed99bfb65a924e 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.3.0", + "version": "9.4.0", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/element/package.json b/packages/element/package.json index e67347a38cf9a3..4e4a096b31fe24 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "2.4.0", + "version": "2.5.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index a6687ebeb34a5a..4d2b81bd73259b 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "1.3.0", + "version": "1.4.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 9d009fd22b1b3f..9bcd60b15de867 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "2.2.0", + "version": "2.3.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 66969a279a6e45..8b84cae37cf00f 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.5.0", + "version": "1.6.0", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 4f8a280c48dd87..bac2718aceb28a 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "2.3.0", + "version": "2.4.0", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index 85c504daaad1c3..a6f449b3e2aaf0 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "2.3.0", + "version": "2.4.0", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index bdf92ce2757d52..721061946e221b 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "3.4.0", + "version": "3.5.0", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 5e1effc9c00767..3850ff5e8f7d4e 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "1.3.0", + "version": "1.4.0", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index fa4e413adbfeeb..cb5de531080c01 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "4.1.0", + "version": "4.2.0", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 5e6aa798c0aa85..695528df254808 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.3.0", + "version": "2.4.0", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index fcaf8a3de299e5..60daa21a7c288b 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "1.2.0", + "version": "1.3.0", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 97efa2fbdf6552..2810b989b7fd01 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.4.0", + "version": "1.5.0", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index b56795b5d65975..8f6ff15ae9420b 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/media-utils", - "version": "0.1.0", + "version": "0.2.0", "description": "WordPress Media Upload Utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/package.json b/packages/notices/package.json index 237966556163a0..250ffed2398b0c 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.4.0", + "version": "1.5.0", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index e9af2edfa81ce9..c05441fbf88adc 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "1.3.0", + "version": "2.0.0", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index 4102efd88c26b3..9768b542bc534c 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.3.0", + "version": "3.4.0", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index f0859bb278f4fe..86a1f40b8d5658 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "2.3.0", + "version": "2.4.0", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index a8cdace21091e8..3f6fea2c863b62 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "3.3.0", + "version": "3.4.0", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 304d7fdb78b6de..2422f20a872611 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.3.0", + "version": "3.4.0", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index c8d503efb886c6..681b40742999c5 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "3.2.1", + "version": "3.3.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 1f81f0be515781..3cb0ace0109874 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "1.0.0-alpha.1", + "version": "1.0.0", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 206bb71eef2987..d7ca4747b7ad69 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "1.3.0", + "version": "1.4.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 495f6bb81fafc4..334d7a834a112c 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.4.0", + "version": "2.5.0", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index a8f74123873112..f3bac4a49be8ef 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "2.3.0", + "version": "2.4.0", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From b354644d9e9a76a387b1ffca418b836a470aa97e Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 12 Jun 2019 18:11:09 +0100 Subject: [PATCH 325/664] Updating changelogs for the Gutenberg 5.9 packages release --- packages/block-editor/CHANGELOG.md | 2 +- packages/block-library/CHANGELOG.md | 2 +- packages/components/CHANGELOG.md | 2 +- packages/compose/CHANGELOG.md | 2 +- packages/custom-templated-path-webpack-plugin/CHANGELOG.md | 2 +- packages/data/CHANGELOG.md | 2 +- packages/editor/CHANGELOG.md | 2 +- packages/eslint-plugin/CHANGELOG.md | 2 +- packages/hooks/CHANGELOG.md | 2 +- packages/library-export-default-webpack-plugin/CHANGELOG.md | 2 +- packages/notices/CHANGELOG.md | 2 +- packages/npm-package-json-lint-config/CHANGELOG.md | 2 +- packages/scripts/CHANGELOG.md | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index f8f88cb313e74b..15e4a9de6b0a80 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 2.2.0 (2019-06-12) ### Internal diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 4df19d8dee8ca9..d33c0a1ec92767 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## ## 2.6.0 (2019-06-12) - Fixed an issue with creating upgraded embed blocks that are not registered ([#15883](https://github.com/WordPress/gutenberg/issues/15883)). diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c1a4e6fef2b5f7..38e44d1c10cfba 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 8.0.0 (2019-06-12) ### New Feature diff --git a/packages/compose/CHANGELOG.md b/packages/compose/CHANGELOG.md index 30e39e3a50596c..af8f112ac8cdda 100644 --- a/packages/compose/CHANGELOG.md +++ b/packages/compose/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 3.4.0 (2019-06-12) ### New Features diff --git a/packages/custom-templated-path-webpack-plugin/CHANGELOG.md b/packages/custom-templated-path-webpack-plugin/CHANGELOG.md index 7b34a52f1071ad..d5bb664a3bfe58 100644 --- a/packages/custom-templated-path-webpack-plugin/CHANGELOG.md +++ b/packages/custom-templated-path-webpack-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 1.4.0 (2019-06-12) ### Bug Fixes diff --git a/packages/data/CHANGELOG.md b/packages/data/CHANGELOG.md index 6851e89823c6ac..a7a342725906e2 100644 --- a/packages/data/CHANGELOG.md +++ b/packages/data/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 4.6.0 (2019-06-12) ### New Feature diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 2bac55e10ac2e6..dd4389226720c5 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 9.4.0 (2019-06-12) ### Deprecations diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index a88430165222ef..28de7846b5f212 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 2.3.0 (2019-06-12) ### Bug Fix diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md index 0b070a6d3c0dbc..cb1625c912898f 100644 --- a/packages/hooks/CHANGELOG.md +++ b/packages/hooks/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 2.4.0 (2019-06-12) ### New Feature diff --git a/packages/library-export-default-webpack-plugin/CHANGELOG.md b/packages/library-export-default-webpack-plugin/CHANGELOG.md index 66b9261df4a8bc..dec500a334ca23 100644 --- a/packages/library-export-default-webpack-plugin/CHANGELOG.md +++ b/packages/library-export-default-webpack-plugin/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 1.3.0 (2019-06-12) ### Internal diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md index 7a6fde4663c907..16695a45dd0122 100644 --- a/packages/notices/CHANGELOG.md +++ b/packages/notices/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 1.5.0 (2019-06-12) ### New Features diff --git a/packages/npm-package-json-lint-config/CHANGELOG.md b/packages/npm-package-json-lint-config/CHANGELOG.md index 2fbfc480934313..945e31866bbe8e 100644 --- a/packages/npm-package-json-lint-config/CHANGELOG.md +++ b/packages/npm-package-json-lint-config/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 2.0.0 (2019-06-12) ### Braking Change diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 1183fa6bd736d4..239314b3a61415 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 3.3.0 (2019-06-12) ### New Features From 3bba2121dc5232f0136e2bf21a25d537569d5e9e Mon Sep 17 00:00:00 2001 From: Tammie Lister <tammie@automattic.com> Date: Wed, 12 Jun 2019 15:31:24 -0700 Subject: [PATCH 326/664] Update toolbar-text.png (#16102) Fixes #15026 --- .../assets/toolbar-text.png | Bin 22683 -> 2684 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/designers-developers/assets/toolbar-text.png b/docs/designers-developers/assets/toolbar-text.png index 76b18c6b8f368f3abd5d2559e5a6f71c75913ed4..8dbf503d503919ce80af90e3e704d833385c7404 100644 GIT binary patch literal 2684 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 A0{{R3 literal 22683 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 From c6a62d65ea4972da638e02fa9d6ce74edbc2beb8 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Wed, 12 Jun 2019 15:49:55 -0700 Subject: [PATCH 327/664] Add descriptive text and a link to embed documentation in embed blocks (#16101) * Add descriptive text and a link to embed documentation in embed blocks * Restructure placeholder to add instructions * Update tests * Standardize margin sizing --- packages/block-library/src/embed/editor.scss | 4 +++ .../src/embed/embed-placeholder.js | 14 ++++++-- .../embed/test/__snapshots__/index.js.snap | 36 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/embed/editor.scss b/packages/block-library/src/embed/editor.scss index 507d38c256688f..342b4fe1b116b2 100644 --- a/packages/block-library/src/embed/editor.scss +++ b/packages/block-library/src/embed/editor.scss @@ -37,6 +37,10 @@ .components-placeholder__error { word-break: break-word; } + + .components-placeholder__learn-more { + margin-top: 1em; + } } .block-library-embed__interactive-overlay { diff --git a/packages/block-library/src/embed/embed-placeholder.js b/packages/block-library/src/embed/embed-placeholder.js index b532d7b1a84a78..7a2a6f000a01f6 100644 --- a/packages/block-library/src/embed/embed-placeholder.js +++ b/packages/block-library/src/embed/embed-placeholder.js @@ -2,13 +2,18 @@ * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { Button, Placeholder } from '@wordpress/components'; +import { Button, Placeholder, ExternalLink } from '@wordpress/components'; import { BlockIcon } from '@wordpress/block-editor'; const EmbedPlaceholder = ( props ) => { const { icon, label, value, onSubmit, onChange, cannotEmbed, fallback, tryAgain } = props; return ( - <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label } className="wp-block-embed"> + <Placeholder + icon={ <BlockIcon icon={ icon } showColors /> } + label={ label } + className="wp-block-embed" + instructions={ __( 'Paste a link to the content you want to display on your site.' ) } + > <form onSubmit={ onSubmit }> <input type="url" @@ -29,6 +34,11 @@ const EmbedPlaceholder = ( props ) => { </p> } </form> + <div className="components-placeholder__learn-more"> + <ExternalLink href={ __( 'https://wordpress.org/support/article/embeds/' ) }> + { __( 'Learn more about embeds' ) } + </ExternalLink> + </div> </Placeholder> ); }; diff --git a/packages/block-library/src/embed/test/__snapshots__/index.js.snap b/packages/block-library/src/embed/test/__snapshots__/index.js.snap index 4b9c1f4de7f0e3..78923aa24c5f6b 100644 --- a/packages/block-library/src/embed/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/embed/test/__snapshots__/index.js.snap @@ -27,6 +27,11 @@ exports[`core/embed block edit matches snapshot 1`] = ` </span> Embed URL </div> + <div + class="components-placeholder__instructions" + > + Paste a link to the content you want to display on your site. + </div> <div class="components-placeholder__fieldset" > @@ -45,6 +50,37 @@ exports[`core/embed block edit matches snapshot 1`] = ` Embed </button> </form> + <div + class="components-placeholder__learn-more" + > + <a + class="components-external-link" + href="https://wordpress.org/support/article/embeds/" + rel="external noreferrer noopener" + target="_blank" + > + Learn more about embeds + <span + class="screen-reader-text" + > + (opens in a new tab) + </span> + <svg + aria-hidden="true" + class="dashicon dashicons-external components-external-link__icon" + focusable="false" + height="20" + role="img" + viewBox="0 0 20 20" + width="20" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 3h8v8l-2-1V6.92l-5.6 5.59-1.41-1.41L14.08 5H10zm3 12v-3l2-2v7H3V6h8L9 8H5v7h7z" + /> + </svg> + </a> + </div> </div> </div> `; From 84543ff908fab4f88c45e42e656b553afa4fdd48 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Wed, 12 Jun 2019 19:40:53 -0400 Subject: [PATCH 328/664] Bring greater consistency to placeholder text for media blocks. (#16135) --- .../src/components/media-placeholder/index.js | 8 ++++---- packages/block-library/src/cover/edit.js | 2 +- packages/block-library/src/file/edit.js | 2 +- packages/block-library/src/image/edit.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 07daa4d8f813eb..efad860f586e7a 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -175,14 +175,14 @@ export class MediaPlaceholder extends Component { const isVideo = isOneType && 'video' === allowedTypes[ 0 ]; if ( instructions === undefined && mediaUpload ) { - instructions = __( 'Drag a media file, upload a new one or select a file from your library.' ); + instructions = __( 'Upload a media file or pick one from your media library.' ); if ( isAudio ) { - instructions = __( 'Drag an audio, upload a new one or select a file from your library.' ); + instructions = __( 'Upload an audio file, pick one from your media library, or add one with a URL.' ); } else if ( isImage ) { - instructions = __( 'Drag an image, upload a new one or select a file from your library.' ); + instructions = __( 'Upload an image file, pick one from your media library, or add one with a URL.' ); } else if ( isVideo ) { - instructions = __( 'Drag a video, upload a new one or select a file from your library.' ); + instructions = __( 'Upload a video file, pick one from your media library, or add one with a URL.' ); } } diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 05bb57786332df..7de37e53f3fc39 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -243,7 +243,7 @@ class CoverEdit extends Component { className={ className } labels={ { title: label, - instructions: __( 'Drag an image or a video, upload a new one or select a file from your library.' ), + instructions: __( 'Upload an image or video file, or pick one from your media library.' ), } } onSelect={ onSelectMedia } accept="image/*,video/*" diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index a27d22a326f474..2ab0baaa08f8bf 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -161,7 +161,7 @@ class FileEdit extends Component { icon={ <BlockIcon icon={ icon } /> } labels={ { title: __( 'File' ), - instructions: __( 'Drag a file, upload a new one or select a file from your library.' ), + instructions: __( 'Upload a file or pick one from your media library.' ), } } onSelect={ this.onSelectFile } notices={ noticeUI } diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 42b271903fe049..7103c495e7a230 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -408,7 +408,7 @@ class ImageEdit extends Component { const src = isExternal ? url : undefined; const labels = { title: ! url ? __( 'Image' ) : __( 'Edit image' ), - instructions: __( 'Drag an image to upload, select a file from your library or add one from an URL.' ), + instructions: __( 'Upload an image, pick one from your media library, or add one with a URL.' ), }; const mediaPreview = ( !! url && <img alt={ __( 'Edit image' ) } From 329a7dbdbddb27ad77253229a349dea44865c847 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Wed, 12 Jun 2019 22:46:59 -0400 Subject: [PATCH 329/664] Add mention of on Figma to CONTRIBUTING.md (#16140) * Add mention of on Figma to CONTRIBUTING.md * Clearly state that Figma is the "official" design app. * Minor updates. * Clarify the process to request Figma team membership --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 521f17e5b90798..001086875af3c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ Please see the [Developer Contributions section](/docs/contributors/develop.md) ## How Can Designers Contribute? -If you'd like to contribute to the design or front-end, feel free to contribute to tickets labelled [Needs Design](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22Needs+Design%22) or [Needs Design Feedback](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A"Needs+Design+Feedback%22). We could use your thoughtful replies, mockups, animatics, sketches, doodles. Proposed changes are best done as minimal and specific iterations on the work that precedes it so we can compare. If you use <a href="https://www.sketchapp.com/">Sketch</a>, you can grab <a href="https://cloudup.com/cMPXM8Va2cy">the source file for the mockups</a> (updated April 6th). +If you'd like to contribute to the design or front-end, feel free to contribute to tickets labelled [Needs Design](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22Needs+Design%22) or [Needs Design Feedback](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A"Needs+Design+Feedback%22). We could use your thoughtful replies, mockups, animatics, sketches, doodles. Proposed changes are best done as minimal and specific iterations on the work that precedes it so we can compare. The [WordPress Design team](http://make.wordpress.org/design/) uses [Figma](https://www.figma.com/) to collaborate and share work. If you'd like to contribute, join the [#design channel](http://wordpress.slack.com/messages/design/) in [Slack](https://make.wordpress.org/chat/) and ask the team to set you up with a free Figma account. This will give you access to a helpful [library of components](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=0%3A1) used in WordPress. ## Contribute to the Documentation From 9c438f93a7215d50d1efc0492c308e4cbaa59c52 Mon Sep 17 00:00:00 2001 From: Technote <technote.space@gmail.com> Date: Thu, 13 Jun 2019 16:29:26 +0900 Subject: [PATCH 330/664] docs(block-editor/components/inspector-controls): fix image path in README.md (#16145) --- .../block-editor/src/components/inspector-controls/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inspector-controls/README.md b/packages/block-editor/src/components/inspector-controls/README.md index f8b19593fbf976..2c6a21d59dbaf5 100644 --- a/packages/block-editor/src/components/inspector-controls/README.md +++ b/packages/block-editor/src/components/inspector-controls/README.md @@ -1,6 +1,6 @@ # Inspector Controls -<img src="https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/blocks/inspector.png" with="281" height="527" alt="inspector"> +<img src="https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/inspector.png" with="281" height="527" alt="inspector"> Inspector Controls appear in the post settings sidebar when a block is being edited. The controls appear in both HTML and visual editing modes, and thus should contain settings that affect the entire block. From 384902fc13dbff1ba8ae5a6f5db9dc10fc6615b2 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Thu, 13 Jun 2019 04:02:33 -0400 Subject: [PATCH 331/664] docs(components/with-focus-return): fix typo in README.md (#16143) --- .../components/src/higher-order/with-focus-return/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/higher-order/with-focus-return/README.md b/packages/components/src/higher-order/with-focus-return/README.md index 15ec5ef09ef258..f0872f15600f3d 100644 --- a/packages/components/src/higher-order/with-focus-return/README.md +++ b/packages/components/src/higher-order/with-focus-return/README.md @@ -2,7 +2,7 @@ `withFocusReturn` is a higher-order component used typically in scenarios of short-lived elements (modals, dropdowns) where, upon the element's unmounting, focus should be restored to the focused element which had initiated it being rendered. -Optionally, it can be used in combination with a `FocusRenderProvider` which, when rendered toward the top of an application, will remember a history of elements focused during a session. This can provide safeguards for scenarios where one short-lived element triggers the creation of another (e.g. a dropdown menu triggering a modal display). The combined effect of `FocusRenderProvider` and `withFocusReturn` is that focus will be returned to the most recent focused element which is still present in the document. +Optionally, it can be used in combination with a `FocusReturnProvider` which, when rendered toward the top of an application, will remember a history of elements focused during a session. This can provide safeguards for scenarios where one short-lived element triggers the creation of another (e.g. a dropdown menu triggering a modal display). The combined effect of `FocusReturnProvider` and `withFocusReturn` is that focus will be returned to the most recent focused element which is still present in the document. ## Usage From 70c39bcb3f4a2275aae19a6cd705f88ceffc80f8 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Thu, 13 Jun 2019 06:07:01 -0400 Subject: [PATCH 332/664] docs(components/higher-order/with-spoken-messages): fix issue in example code (#16144) --- .../src/higher-order/with-spoken-messages/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/higher-order/with-spoken-messages/README.md b/packages/components/src/higher-order/with-spoken-messages/README.md index 1cb09651b7919d..5e539b3f3d5725 100644 --- a/packages/components/src/higher-order/with-spoken-messages/README.md +++ b/packages/components/src/higher-order/with-spoken-messages/README.md @@ -7,8 +7,8 @@ import { withSpokenMessages, Button } from '@wordpress/components'; const MyComponentWithSpokenMessages = withSpokenMessages( ( { speak, debouncedSpeak } ) => ( <div> - <Button isDefault onClick={ speak( 'Spoken message' ) }>Speak</Button> - <Button isDefault onClick={ debouncedSpeak( 'Delayed message' ) }>Debounced Speak</Button> + <Button isDefault onClick={ () => speak( 'Spoken message' ) }>Speak</Button> + <Button isDefault onClick={ () => debouncedSpeak( 'Delayed message' ) }>Debounced Speak</Button> </div> ) ); ``` From 8589bc1733359aa5cf38dafad195d54c4616b102 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 13 Jun 2019 13:00:53 +0200 Subject: [PATCH 333/664] Add native support for Inserter (Ported from gutenberg-mobile) (#16114) --- .../src/components/index.native.js | 1 + .../src/components/inserter/index.native.js | 96 +++++++++++++++++++ .../src/components/inserter/style.native.scss | 57 +++++++++++ packages/blocks/src/api/index.native.js | 4 + 4 files changed, 158 insertions(+) create mode 100644 packages/block-editor/src/components/inserter/index.native.js create mode 100644 packages/block-editor/src/components/inserter/style.native.scss diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index df605eb54da3f2..5b3249e1f1a8a9 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -20,6 +20,7 @@ export { default as BlockInvalidWarning } from './block-list/block-invalid-warni // Content Related Components export { default as DefaultBlockAppender } from './default-block-appender'; +export { default as Inserter } from './inserter'; // State Related Components export { default as BlockEditorProvider } from './provider'; diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js new file mode 100644 index 00000000000000..2985d61c7811c4 --- /dev/null +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -0,0 +1,96 @@ +/** + * External dependencies + */ +import { FlatList, Text, TouchableHighlight, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { BottomSheet, Icon } from '@wordpress/components'; +import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { getUnregisteredTypeHandlerName } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +class Inserter extends Component { + calculateNumberOfColumns() { + const bottomSheetWidth = BottomSheet.getWidth(); + const { paddingLeft: itemPaddingLeft, paddingRight: itemPaddingRight } = styles.modalItem; + const { paddingLeft: containerPaddingLeft, paddingRight: containerPaddingRight } = styles.content; + const { width: itemWidth } = styles.modalIconWrapper; + const itemTotalWidth = itemWidth + itemPaddingLeft + itemPaddingRight; + const containerTotalWidth = bottomSheetWidth - ( containerPaddingLeft + containerPaddingRight ); + return Math.floor( containerTotalWidth / itemTotalWidth ); + } + + render() { + const numberOfColumns = this.calculateNumberOfColumns(); + const bottomPadding = this.props.addExtraBottomPadding && styles.contentBottomPadding; + + return ( + <BottomSheet + isVisible={ true } + onClose={ this.props.onDismiss } + contentStyle={ [ styles.content, bottomPadding ] } + hideHeader + > + <FlatList + scrollEnabled={ false } + key={ `InserterUI-${ numberOfColumns }` } //re-render when numberOfColumns changes + keyboardShouldPersistTaps="always" + numColumns={ numberOfColumns } + data={ this.props.items } + ItemSeparatorComponent={ () => + <View style={ styles.rowSeparator } /> + } + keyExtractor={ ( item ) => item.name } + renderItem={ ( { item } ) => + <TouchableHighlight + style={ styles.touchableArea } + underlayColor="transparent" + activeOpacity={ .5 } + accessibilityLabel={ item.title } + onPress={ () => this.props.onValueSelected( item.name ) }> + <View style={ styles.modalItem }> + <View style={ styles.modalIconWrapper }> + <View style={ styles.modalIcon }> + <Icon icon={ item.icon.src } fill={ styles.modalIcon.fill } size={ styles.modalIcon.width } /> + </View> + </View> + <Text style={ styles.modalItemLabel }>{ item.title }</Text> + </View> + </TouchableHighlight> + } + /> + </BottomSheet> + ); + } +} + +export default compose( [ + withSelect( ( select, { clientId, isAppender, rootClientId } ) => { + const { + getInserterItems, + getBlockRootClientId, + getBlockSelectionEnd, + } = select( 'core/block-editor' ); + + let destinationRootClientId = rootClientId; + if ( ! destinationRootClientId && ! clientId && ! isAppender ) { + const end = getBlockSelectionEnd(); + if ( end ) { + destinationRootClientId = getBlockRootClientId( end ) || undefined; + } + } + const inserterItems = getInserterItems( destinationRootClientId ); + + return { + items: inserterItems.filter( ( { name } ) => name !== getUnregisteredTypeHandlerName() ), + }; + } ), +] )( Inserter ); diff --git a/packages/block-editor/src/components/inserter/style.native.scss b/packages/block-editor/src/components/inserter/style.native.scss new file mode 100644 index 00000000000000..82d5fa58226504 --- /dev/null +++ b/packages/block-editor/src/components/inserter/style.native.scss @@ -0,0 +1,57 @@ +/** @format */ + +.touchableArea { + border-radius: 8px 8px 8px 8px; +} + +.content { + padding: 0 0 0 0; + align-items: center; + justify-content: space-evenly; +} + +.contentBottomPadding { + padding-bottom: 20px; +} + +.rowSeparator { + height: 12px; +} + +.modalItem { + flex-direction: column; + justify-content: center; + align-items: center; + padding-left: 8; + padding-right: 8; + padding-top: 0; + padding-bottom: 0; +} + +.modalIconWrapper { + width: 104px; + height: 64px; + background-color: $gray-light; //#f3f6f8 + border-radius: 8px 8px 8px 8px; + justify-content: center; + align-items: center; +} + +.modalIcon { + width: 32px; + height: 32px; + justify-content: center; + align-items: center; + fill: $gray-dark; +} + +.modalItemLabel { + background-color: transparent; + padding-left: 2; + padding-right: 2; + padding-top: 4; + padding-bottom: 0; + justify-content: center; + font-size: 12; + color: $gray-dark; +} diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js index 123be1e132a992..b99fccc531c217 100644 --- a/packages/blocks/src/api/index.native.js +++ b/packages/blocks/src/api/index.native.js @@ -21,8 +21,12 @@ export { getUnregisteredTypeHandlerName, getBlockType, getBlockTypes, + getBlockSupport, hasBlockSupport, isReusableBlock, + getChildBlockNames, + hasChildBlocks, + hasChildBlocksWithInserterSupport, setDefaultBlockName, getDefaultBlockName, } from './registration'; From 09f67ea31625d61600fdcfe4ff42b923a606cec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 14 Jun 2019 07:51:09 +0200 Subject: [PATCH 334/664] Scripts: Fix naming conventions for function containing CLI keyword (#16091) --- packages/scripts/bin/wp-scripts.js | 4 ++-- packages/scripts/scripts/check-engines.js | 14 ++++++------- packages/scripts/scripts/check-licenses.js | 12 +++++------ packages/scripts/scripts/lint-js.js | 16 +++++++-------- packages/scripts/scripts/lint-pkg-json.js | 16 +++++++-------- packages/scripts/scripts/lint-style.js | 14 ++++++------- packages/scripts/scripts/test-e2e.js | 20 +++++++++--------- packages/scripts/scripts/test-unit-jest.js | 4 ++-- packages/scripts/utils/cli.js | 18 ++++++++-------- packages/scripts/utils/config.js | 10 ++++----- packages/scripts/utils/index.js | 16 +++++++-------- packages/scripts/utils/process.js | 4 ++-- packages/scripts/utils/test/index.js | 24 +++++++++++----------- 13 files changed, 86 insertions(+), 86 deletions(-) diff --git a/packages/scripts/bin/wp-scripts.js b/packages/scripts/bin/wp-scripts.js index 05e9154da4a9b9..a74076ead7f85f 100755 --- a/packages/scripts/bin/wp-scripts.js +++ b/packages/scripts/bin/wp-scripts.js @@ -3,8 +3,8 @@ /** * Internal dependencies */ -const { getCliArgs, spawnScript } = require( '../utils' ); +const { getArgsFromCLI, spawnScript } = require( '../utils' ); -const [ scriptName, ...nodesArgs ] = getCliArgs(); +const [ scriptName, ...nodesArgs ] = getArgsFromCLI(); spawnScript( scriptName, nodesArgs ); diff --git a/packages/scripts/scripts/check-engines.js b/packages/scripts/scripts/check-engines.js index 5440e27ccaffba..aba446313e5a93 100644 --- a/packages/scripts/scripts/check-engines.js +++ b/packages/scripts/scripts/check-engines.js @@ -8,16 +8,16 @@ const { sync: resolveBin } = require( 'resolve-bin' ); * Internal dependencies */ const { - getCliArgs, - hasCliArg, + getArgsFromCLI, + hasArgInCLI, } = require( '../utils' ); -const args = getCliArgs(); +const args = getArgsFromCLI(); -const hasConfig = hasCliArg( '--package' ) || - hasCliArg( '--node' ) || - hasCliArg( '--npm' ) || - hasCliArg( '--yarn' ); +const hasConfig = hasArgInCLI( '--package' ) || + hasArgInCLI( '--node' ) || + hasArgInCLI( '--npm' ) || + hasArgInCLI( '--yarn' ); const config = ! hasConfig ? [ '--node', '>=10.0.0', diff --git a/packages/scripts/scripts/check-licenses.js b/packages/scripts/scripts/check-licenses.js index 7670ae3b0d2140..1b1d46b9befbce 100644 --- a/packages/scripts/scripts/check-licenses.js +++ b/packages/scripts/scripts/check-licenses.js @@ -9,7 +9,7 @@ const chalk = require( 'chalk' ); /** * Internal dependencies */ -const { getCliArg, hasCliArg } = require( '../utils' ); +const { getArgFromCLI, hasArgInCLI } = require( '../utils' ); /* * WARNING: Changes to this file may inadvertently cause us to distribute code that @@ -22,11 +22,11 @@ const { getCliArg, hasCliArg } = require( '../utils' ); const ERROR = chalk.reset.inverse.bold.red( ' ERROR ' ); -const prod = hasCliArg( '--prod' ) || hasCliArg( '--production' ); -const dev = hasCliArg( '--dev' ) || hasCliArg( '--development' ); -const gpl2 = hasCliArg( '--gpl2' ); -const ignored = hasCliArg( '--ignore' ) ? - getCliArg( '--ignore' ) +const prod = hasArgInCLI( '--prod' ) || hasArgInCLI( '--production' ); +const dev = hasArgInCLI( '--dev' ) || hasArgInCLI( '--development' ); +const gpl2 = hasArgInCLI( '--gpl2' ); +const ignored = hasArgInCLI( '--ignore' ) ? + getArgFromCLI( '--ignore' ) // "--ignore=a, b" -> "[ 'a', ' b' ]" .split( ',' ) // "[ 'a', ' b' ]" -> "[ 'a', 'b' ]" diff --git a/packages/scripts/scripts/lint-js.js b/packages/scripts/scripts/lint-js.js index af2e450037b397..dc55a358c3e158 100644 --- a/packages/scripts/scripts/lint-js.js +++ b/packages/scripts/scripts/lint-js.js @@ -9,20 +9,20 @@ const { sync: resolveBin } = require( 'resolve-bin' ); */ const { fromConfigRoot, - getCliArgs, - hasCliArg, - hasFileInCliArgs, + getArgsFromCLI, + hasArgInCLI, + hasFileArgInCLI, hasPackageProp, hasProjectFile, } = require( '../utils' ); -const args = getCliArgs(); +const args = getArgsFromCLI(); -const defaultFilesArgs = hasFileInCliArgs() ? [] : [ '.' ]; +const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '.' ]; // See: https://eslint.org/docs/user-guide/configuring#using-configuration-files-1. -const hasLintConfig = hasCliArg( '-c' ) || - hasCliArg( '--config' ) || +const hasLintConfig = hasArgInCLI( '-c' ) || + hasArgInCLI( '--config' ) || hasProjectFile( '.eslintrc.js' ) || hasProjectFile( '.eslintrc.yaml' ) || hasProjectFile( '.eslintrc.yml' ) || @@ -38,7 +38,7 @@ const defaultConfigArgs = ! hasLintConfig ? []; // See: https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories. -const hasIgnoredFiles = hasCliArg( '--ignore-path' ) || +const hasIgnoredFiles = hasArgInCLI( '--ignore-path' ) || hasProjectFile( '.eslintignore' ); const defaultIgnoreArgs = ! hasIgnoredFiles ? diff --git a/packages/scripts/scripts/lint-pkg-json.js b/packages/scripts/scripts/lint-pkg-json.js index 6c328cb0169bff..487a8191067dbc 100644 --- a/packages/scripts/scripts/lint-pkg-json.js +++ b/packages/scripts/scripts/lint-pkg-json.js @@ -9,20 +9,20 @@ const { sync: resolveBin } = require( 'resolve-bin' ); */ const { fromConfigRoot, - getCliArgs, - hasCliArg, - hasFileInCliArgs, + getArgsFromCLI, + hasArgInCLI, + hasFileArgInCLI, hasProjectFile, hasPackageProp, } = require( '../utils' ); -const args = getCliArgs(); +const args = getArgsFromCLI(); -const defaultFilesArgs = hasFileInCliArgs() ? [] : [ '.' ]; +const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '.' ]; // See: https://github.com/tclindner/npm-package-json-lint/wiki/configuration#configuration. -const hasLintConfig = hasCliArg( '-c' ) || - hasCliArg( '--configFile' ) || +const hasLintConfig = hasArgInCLI( '-c' ) || + hasArgInCLI( '--configFile' ) || hasProjectFile( '.npmpackagejsonlintrc.json' ) || hasProjectFile( 'npmpackagejsonlint.config.js' ) || hasPackageProp( 'npmPackageJsonLintConfig' ); @@ -32,7 +32,7 @@ const defaultConfigArgs = ! hasLintConfig ? []; // See: https://github.com/tclindner/npm-package-json-lint/#cli-commands-and-configuration. -const hasIgnoredFiles = hasCliArg( '--ignorePath' ) || +const hasIgnoredFiles = hasArgInCLI( '--ignorePath' ) || hasProjectFile( '.npmpackagejsonlintignore' ); const defaultIgnoreArgs = ! hasIgnoredFiles ? diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js index 9d22a81c96ba25..3f7e86fc43277f 100644 --- a/packages/scripts/scripts/lint-style.js +++ b/packages/scripts/scripts/lint-style.js @@ -9,19 +9,19 @@ const { sync: resolveBin } = require( 'resolve-bin' ); */ const { fromConfigRoot, - getCliArgs, - hasCliArg, - hasFileInCliArgs, + getArgsFromCLI, + hasArgInCLI, + hasFileArgInCLI, hasProjectFile, hasPackageProp, } = require( '../utils' ); -const args = getCliArgs(); +const args = getArgsFromCLI(); -const defaultFilesArgs = hasFileInCliArgs() ? [] : [ '**/*.{css,scss}' ]; +const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '**/*.{css,scss}' ]; // See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#loading-the-configuration-object. -const hasLintConfig = hasCliArg( '--config' ) || +const hasLintConfig = hasArgInCLI( '--config' ) || hasProjectFile( '.stylelintrc' ) || hasProjectFile( '.stylelintrc.js' ) || hasProjectFile( '.stylelintrc.json' ) || @@ -35,7 +35,7 @@ const defaultConfigArgs = ! hasLintConfig ? []; // See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#stylelintignore. -const hasIgnoredFiles = hasCliArg( '--ignore-path' ) || +const hasIgnoredFiles = hasArgInCLI( '--ignore-path' ) || hasProjectFile( '.stylelintignore' ); const defaultIgnoreArgs = ! hasIgnoredFiles ? diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js index 374ebc59f6fbe5..efa0c834df2394 100644 --- a/packages/scripts/scripts/test-e2e.js +++ b/packages/scripts/scripts/test-e2e.js @@ -19,9 +19,9 @@ const jest = require( 'jest' ); */ const { fromConfigRoot, - getCliArg, - getCliArgs, - hasCliArg, + getArgFromCLI, + getArgsFromCLI, + hasArgInCLI, hasProjectFile, hasJestConfig, } = require( '../utils' ); @@ -37,15 +37,15 @@ const config = ! hasJestConfig() ? [ '--config', JSON.stringify( require( fromConfigRoot( 'jest-e2e.config.js' ) ) ) ] : []; -const hasRunInBand = hasCliArg( '--runInBand' ) || - hasCliArg( '-i' ); +const hasRunInBand = hasArgInCLI( '--runInBand' ) || + hasArgInCLI( '-i' ); const runInBand = ! hasRunInBand ? [ '--runInBand' ] : []; -if ( hasCliArg( '--puppeteer-interactive' ) ) { +if ( hasArgInCLI( '--puppeteer-interactive' ) ) { process.env.PUPPETEER_HEADLESS = 'false'; - process.env.PUPPETEER_SLOWMO = getCliArg( '--puppeteer-slowmo' ) || 80; + process.env.PUPPETEER_SLOWMO = getArgFromCLI( '--puppeteer-slowmo' ) || 80; } const configsMapping = { @@ -55,11 +55,11 @@ const configsMapping = { }; Object.entries( configsMapping ).forEach( ( [ envKey, argName ] ) => { - if ( hasCliArg( argName ) ) { - process.env[ envKey ] = getCliArg( argName ); + if ( hasArgInCLI( argName ) ) { + process.env[ envKey ] = getArgFromCLI( argName ); } } ); const cleanUpPrefixes = [ '--puppeteer-', '--wordpress-' ]; -jest.run( [ ...config, ...runInBand, ...getCliArgs( cleanUpPrefixes ) ] ); +jest.run( [ ...config, ...runInBand, ...getArgsFromCLI( cleanUpPrefixes ) ] ); diff --git a/packages/scripts/scripts/test-unit-jest.js b/packages/scripts/scripts/test-unit-jest.js index 1fb5743ac861c0..e7edf3c69ccdb4 100644 --- a/packages/scripts/scripts/test-unit-jest.js +++ b/packages/scripts/scripts/test-unit-jest.js @@ -19,7 +19,7 @@ const jest = require( 'jest' ); */ const { fromConfigRoot, - getCliArgs, + getArgsFromCLI, hasJestConfig, } = require( '../utils' ); @@ -27,4 +27,4 @@ const config = ! hasJestConfig() ? [ '--config', JSON.stringify( require( fromConfigRoot( 'jest-unit.config.js' ) ) ) ] : []; -jest.run( [ ...config, ...getCliArgs() ] ); +jest.run( [ ...config, ...getArgsFromCLI() ] ); diff --git a/packages/scripts/utils/cli.js b/packages/scripts/utils/cli.js index db4ce5fcef2d49..ab29ad4fa769e3 100644 --- a/packages/scripts/utils/cli.js +++ b/packages/scripts/utils/cli.js @@ -13,11 +13,11 @@ const { } = require( './file' ); const { exit, - getCliArgs, + getArgsFromCLI, } = require( './process' ); -const getCliArg = ( arg ) => { - for ( const cliArg of getCliArgs() ) { +const getArgFromCLI = ( arg ) => { + for ( const cliArg of getArgsFromCLI() ) { const [ name, value ] = cliArg.split( '=' ); if ( name === arg ) { return value || null; @@ -25,9 +25,9 @@ const getCliArg = ( arg ) => { } }; -const hasCliArg = ( arg ) => getCliArg( arg ) !== undefined; +const hasArgInCLI = ( arg ) => getArgFromCLI( arg ) !== undefined; -const hasFileInCliArgs = () => minimist( getCliArgs() )._.length > 0; +const hasFileArgInCLI = () => minimist( getArgsFromCLI() )._.length > 0; const handleSignal = ( signal ) => { if ( signal === 'SIGKILL' ) { @@ -81,9 +81,9 @@ const spawnScript = ( scriptName, args = [] ) => { }; module.exports = { - getCliArg, - getCliArgs, - hasCliArg, - hasFileInCliArgs, + getArgFromCLI, + getArgsFromCLI, + hasArgInCLI, + hasFileArgInCLI, spawnScript, }; diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 3ae98725e5fbc7..9247f93a941717 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -const { hasCliArg, getCliArgs } = require( './cli' ); +const { hasArgInCLI, getArgsFromCLI } = require( './cli' ); const { fromConfigRoot, hasProjectFile } = require( './file' ); const { hasPackageProp } = require( './package' ); @@ -12,18 +12,18 @@ const hasBabelConfig = () => hasPackageProp( 'babel' ); const hasJestConfig = () => - hasCliArg( '-c' ) || - hasCliArg( '--config' ) || + hasArgInCLI( '-c' ) || + hasArgInCLI( '--config' ) || hasProjectFile( 'jest.config.js' ) || hasProjectFile( 'jest.config.json' ) || hasPackageProp( 'jest' ); -const hasWebpackConfig = () => hasCliArg( '--config' ) || +const hasWebpackConfig = () => hasArgInCLI( '--config' ) || hasProjectFile( 'webpack.config.js' ) || hasProjectFile( 'webpack.config.babel.js' ); const getWebpackArgs = ( additionalArgs = [] ) => { - const webpackArgs = getCliArgs(); + const webpackArgs = getArgsFromCLI(); if ( ! hasWebpackConfig() ) { webpackArgs.push( '--config', fromConfigRoot( 'webpack.config.js' ) ); } diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index 2912c9aee22bbb..f356d401356cd1 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -2,10 +2,10 @@ * Internal dependencies */ const { - getCliArg, - getCliArgs, - hasCliArg, - hasFileInCliArgs, + getArgFromCLI, + getArgsFromCLI, + hasArgInCLI, + hasFileArgInCLI, spawnScript, } = require( './cli' ); const { @@ -27,12 +27,12 @@ const { module.exports = { camelCaseDash, fromConfigRoot, - getCliArg, - getCliArgs, + getArgFromCLI, + getArgsFromCLI, getWebpackArgs, hasBabelConfig, - hasCliArg, - hasFileInCliArgs, + hasArgInCLI, + hasFileArgInCLI, hasJestConfig, hasPackageProp, hasProjectFile, diff --git a/packages/scripts/utils/process.js b/packages/scripts/utils/process.js index cfa38afa26aa94..4996190d21e539 100644 --- a/packages/scripts/utils/process.js +++ b/packages/scripts/utils/process.js @@ -1,4 +1,4 @@ -const getCliArgs = ( excludePrefixes ) => { +const getArgsFromCLI = ( excludePrefixes ) => { const args = process.argv.slice( 2 ); if ( excludePrefixes ) { return args.filter( ( arg ) => { @@ -10,6 +10,6 @@ const getCliArgs = ( excludePrefixes ) => { module.exports = { exit: process.exit, - getCliArgs, + getArgsFromCLI, getCurrentWorkingDirectory: process.cwd, }; diff --git a/packages/scripts/utils/test/index.js b/packages/scripts/utils/test/index.js index d451a7540411dc..778c51dedb6e9f 100644 --- a/packages/scripts/utils/test/index.js +++ b/packages/scripts/utils/test/index.js @@ -7,7 +7,7 @@ import crossSpawn from 'cross-spawn'; * Internal dependencies */ import { - hasCliArg, + hasArgInCLI, hasProjectFile, spawnScript, } from '../'; @@ -16,7 +16,7 @@ import { } from '../package'; import { exit as exitMock, - getCliArgs as getCliArgsMock, + getArgsFromCLI as getArgsFromCLIMock, } from '../process'; jest.mock( '../package', () => { @@ -30,7 +30,7 @@ jest.mock( '../process', () => { const module = require.requireActual( '../process' ); jest.spyOn( module, 'exit' ); - jest.spyOn( module, 'getCliArgs' ); + jest.spyOn( module, 'getArgsFromCLI' ); return module; } ); @@ -38,29 +38,29 @@ jest.mock( '../process', () => { describe( 'utils', () => { const crossSpawnMock = jest.spyOn( crossSpawn, 'sync' ); - describe( 'hasCliArg', () => { + describe( 'hasArgInCLI', () => { beforeAll( () => { - getCliArgsMock.mockReturnValue( [ '-a', '--b', '--config=test' ] ); + getArgsFromCLIMock.mockReturnValue( [ '-a', '--b', '--config=test' ] ); } ); afterAll( () => { - getCliArgsMock.mockReset(); + getArgsFromCLIMock.mockReset(); } ); test( 'should return false when no args passed', () => { - getCliArgsMock.mockReturnValueOnce( [] ); + getArgsFromCLIMock.mockReturnValueOnce( [] ); - expect( hasCliArg( '--no-args' ) ).toBe( false ); + expect( hasArgInCLI( '--no-args' ) ).toBe( false ); } ); test( 'should return false when checking for unrecognized arg', () => { - expect( hasCliArg( '--non-existent' ) ).toBe( false ); + expect( hasArgInCLI( '--non-existent' ) ).toBe( false ); } ); test( 'should return true when CLI arg found', () => { - expect( hasCliArg( '-a' ) ).toBe( true ); - expect( hasCliArg( '--b' ) ).toBe( true ); - expect( hasCliArg( '--config' ) ).toBe( true ); + expect( hasArgInCLI( '-a' ) ).toBe( true ); + expect( hasArgInCLI( '--b' ) ).toBe( true ); + expect( hasArgInCLI( '--config' ) ).toBe( true ); } ); } ); From 4371354d77c8b39c99628c590effd560dbe795a3 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 14 Jun 2019 01:54:17 -0400 Subject: [PATCH 335/664] Build Tooling: Use "full" `npm install` for Build Artifacts Travis task (#16166) --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8d1046f2104d1d..60a40cec454eb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,11 @@ jobs: - name: Build artifacts install: - - npm ci + # A "full" install is executed, since `npm ci` does not always exit + # with an error status code if the lock file is inaccurate. + # + # See: https://github.com/WordPress/gutenberg/issues/16157 + - npm install script: - npm run check-local-changes From 289e2a1ae95ca83e6a23322c89e2fe0ecf9ac42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 14 Jun 2019 08:42:42 +0200 Subject: [PATCH 336/664] Editor: Fix the issue where statics for deprecated components were not hoisted (#16152) --- packages/editor/src/components/deprecated.js | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/components/deprecated.js b/packages/editor/src/components/deprecated.js index 44758b2ea29a4e..022da5c4023fa2 100644 --- a/packages/editor/src/components/deprecated.js +++ b/packages/editor/src/components/deprecated.js @@ -41,7 +41,6 @@ import { RichText as RootRichText, RichTextShortcut as RootRichTextShortcut, RichTextToolbarButton as RootRichTextToolbarButton, - RichTextInserterItem as RootRichTextInserterItem, __unstableRichTextInputEvent as __unstableRootRichTextInputEvent, MediaPlaceholder as RootMediaPlaceholder, MediaUpload as RootMediaUpload, @@ -64,14 +63,23 @@ import { export { default as ServerSideRender } from '@wordpress/server-side-render'; -function deprecateComponent( name, Wrapped ) { - return forwardRef( ( props, ref ) => { +function deprecateComponent( name, Wrapped, staticsToHoist = [] ) { + const Component = forwardRef( ( props, ref ) => { deprecated( 'wp.editor.' + name, { alternative: 'wp.blockEditor.' + name, } ); return <Wrapped ref={ ref }{ ...props } />; } ); + + staticsToHoist.forEach( ( staticName ) => { + Component[ staticName ] = deprecateComponent( + name + '.' + staticName, + Wrapped[ staticName ] + ); + } ); + + return Component; } function deprecateFunction( name, func ) { @@ -84,13 +92,17 @@ function deprecateFunction( name, func ) { }; } +const RichText = deprecateComponent( 'RichText', RootRichText, [ 'Content' ] ); +RichText.isEmpty = deprecateFunction( 'RichText.isEmpty', RootRichText.isEmpty ); + +export { RichText }; export const Autocomplete = deprecateComponent( 'Autocomplete', RootAutocomplete ); export const AlignmentToolbar = deprecateComponent( 'AlignmentToolbar', RootAlignmentToolbar ); export const BlockAlignmentToolbar = deprecateComponent( 'BlockAlignmentToolbar', RootBlockAlignmentToolbar ); -export const BlockControls = deprecateComponent( 'BlockControls', RootBlockControls ); +export const BlockControls = deprecateComponent( 'BlockControls', RootBlockControls, [ 'Slot' ] ); export const BlockEdit = deprecateComponent( 'BlockEdit', RootBlockEdit ); export const BlockEditorKeyboardShortcuts = deprecateComponent( 'BlockEditorKeyboardShortcuts', RootBlockEditorKeyboardShortcuts ); -export const BlockFormatControls = deprecateComponent( 'BlockFormatControls', RootBlockFormatControls ); +export const BlockFormatControls = deprecateComponent( 'BlockFormatControls', RootBlockFormatControls, [ 'Slot' ] ); export const BlockIcon = deprecateComponent( 'BlockIcon', RootBlockIcon ); export const BlockInspector = deprecateComponent( 'BlockInspector', RootBlockInspector ); export const BlockList = deprecateComponent( 'BlockList', RootBlockList ); @@ -106,15 +118,13 @@ export const CopyHandler = deprecateComponent( 'CopyHandler', RootCopyHandler ); export const DefaultBlockAppender = deprecateComponent( 'DefaultBlockAppender', RootDefaultBlockAppender ); export const FontSizePicker = deprecateComponent( 'FontSizePicker', RootFontSizePicker ); export const Inserter = deprecateComponent( 'Inserter', RootInserter ); -export const InnerBlocks = deprecateComponent( 'InnerBlocks', RootInnerBlocks ); -export const InspectorAdvancedControls = deprecateComponent( 'InspectorAdvancedControls', RootInspectorAdvancedControls ); -export const InspectorControls = deprecateComponent( 'InspectorControls', RootInspectorControls ); +export const InnerBlocks = deprecateComponent( 'InnerBlocks', RootInnerBlocks, [ 'ButtonBlockAppender', 'DefaultBlockAppender', 'Content' ] ); +export const InspectorAdvancedControls = deprecateComponent( 'InspectorAdvancedControls', RootInspectorAdvancedControls, [ 'Slot' ] ); +export const InspectorControls = deprecateComponent( 'InspectorControls', RootInspectorControls, [ 'Slot' ] ); export const PanelColorSettings = deprecateComponent( 'PanelColorSettings', RootPanelColorSettings ); export const PlainText = deprecateComponent( 'PlainText', RootPlainText ); -export const RichText = deprecateComponent( 'RichText', RootRichText ); export const RichTextShortcut = deprecateComponent( 'RichTextShortcut', RootRichTextShortcut ); export const RichTextToolbarButton = deprecateComponent( 'RichTextToolbarButton', RootRichTextToolbarButton ); -export const RichTextInserterItem = deprecateComponent( 'RichTextInserterItem', RootRichTextInserterItem ); export const __unstableRichTextInputEvent = deprecateComponent( '__unstableRichTextInputEvent', __unstableRootRichTextInputEvent ); export const MediaPlaceholder = deprecateComponent( 'MediaPlaceholder', RootMediaPlaceholder ); export const MediaUpload = deprecateComponent( 'MediaUpload', RootMediaUpload ); From 171c99e701c4139336eee2bb4ee665529c8f05d2 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Fri, 14 Jun 2019 09:44:58 +0200 Subject: [PATCH 337/664] Bump plugin version to 5.9.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 1450aa20c4b750..e762ee4326aeca 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.9.0 + * Version: 5.9.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index c2c500f9773775..a767b27c99ab50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.0", + "version": "5.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0ce754a7d4579c..2afdd8eccedfd5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.0", + "version": "5.9.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 4f5b47294697fe72074dbf4477df12ed8d9d9cd2 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 14 Jun 2019 11:57:06 +0100 Subject: [PATCH 338/664] Make calendar block resilient against editor module not being present (#16161) --- packages/block-library/src/calendar/edit.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/calendar/edit.js b/packages/block-library/src/calendar/edit.js index f884490ec38c80..19027eecf099f7 100644 --- a/packages/block-library/src/calendar/edit.js +++ b/packages/block-library/src/calendar/edit.js @@ -61,9 +61,13 @@ class CalendarEdit extends Component { } export default withSelect( ( select ) => { + const coreEditorSelect = select( 'core/editor' ); + if ( ! coreEditorSelect ) { + return; + } const { getEditedPostAttribute, - } = select( 'core/editor' ); + } = coreEditorSelect; const postType = getEditedPostAttribute( 'type' ); // Dates are used to overwrite year and month used on the calendar. // This overwrite should only happen for 'post' post types. From 93e31d0a1990c0d1845f8a5ff570473e743fd205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 14 Jun 2019 12:59:22 +0200 Subject: [PATCH 339/664] Block library: Refactor Heading block to use class names for text align (#16035) * Block library: Refactor Heading block to use class names for text align * Fix Heading icon in the collapsed mode * Add missing label for the level dropdown menu * Block library: Revert attribute name change for text align * Remove code which moves text alignment control to the block toolbar --- .../block-library/src/heading/deprecated.js | 79 +++++++++++++++++++ packages/block-library/src/heading/edit.js | 2 +- packages/block-library/src/heading/index.js | 2 + packages/block-library/src/heading/save.js | 6 +- packages/block-library/src/style.scss | 12 +++ .../e2e-tests/fixtures/block-transforms.js | 2 +- .../blocks/core__heading__deprecated-1.html | 3 + .../blocks/core__heading__deprecated-1.json | 14 ++++ .../core__heading__deprecated-1.parsed.json | 23 ++++++ ...ore__heading__deprecated-1.serialized.html | 3 + .../fixtures/blocks/core__heading__h2-em.html | 3 - .../core__heading__h2-em.serialized.html | 3 - .../fixtures/blocks/core__heading__h4-em.html | 3 + ...__h2-em.json => core__heading__h4-em.json} | 4 +- ....json => core__heading__h4-em.parsed.json} | 8 +- .../core__heading__h4-em.serialized.html | 3 + .../block-transforms.test.js.snap | 4 +- 17 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 packages/block-library/src/heading/deprecated.js create mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.serialized.html delete mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__h2-em.html delete mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__h2-em.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__h4-em.html rename packages/e2e-tests/fixtures/blocks/{core__heading__h2-em.json => core__heading__h4-em.json} (72%) rename packages/e2e-tests/fixtures/blocks/{core__heading__h2-em.parsed.json => core__heading__h4-em.parsed.json} (62%) create mode 100644 packages/e2e-tests/fixtures/blocks/core__heading__h4-em.serialized.html diff --git a/packages/block-library/src/heading/deprecated.js b/packages/block-library/src/heading/deprecated.js new file mode 100644 index 00000000000000..4a3a7d2f6d3653 --- /dev/null +++ b/packages/block-library/src/heading/deprecated.js @@ -0,0 +1,79 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { + getColorClassName, + RichText, +} from '@wordpress/block-editor'; + +const blockSupports = { + className: false, + anchor: true, +}; + +const blockAttributes = { + align: { + type: 'string', + }, + content: { + type: 'string', + source: 'html', + selector: 'h1,h2,h3,h4,h5,h6', + default: '', + }, + level: { + type: 'number', + default: 2, + }, + placeholder: { + type: 'string', + }, + textColor: { + type: 'string', + }, + customTextColor: { + type: 'string', + }, +}; + +const deprecated = [ + { + supports: blockSupports, + attributes: blockAttributes, + save( { attributes } ) { + const { + align, + level, + content, + textColor, + customTextColor, + } = attributes; + const tagName = 'h' + level; + + const textClass = getColorClassName( 'color', textColor ); + + const className = classnames( { + [ textClass ]: textClass, + } ); + + return ( + <RichText.Content + className={ className ? className : undefined } + tagName={ tagName } + style={ { + textAlign: align, + color: textClass ? undefined : customTextColor, + } } + value={ content } + /> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index e8a396a147df57..7fc4721c9169ab 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -100,13 +100,13 @@ function HeadingEdit( { onReplace={ onReplace } onRemove={ () => onReplace( [] ) } className={ classnames( className, { + [ `has-text-align-${ align }` ]: align, 'has-text-color': textColor.color, [ textColor.class ]: textColor.class, } ) } placeholder={ placeholder || __( 'Write heading…' ) } style={ { color: textColor.color, - textAlign: align, } } /> </> diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 7706f44bb54ce6..1dc024193e4a76 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import metadata from './block.json'; import save from './save'; @@ -25,6 +26,7 @@ export const settings = { anchor: true, }, transforms, + deprecated, merge( attributes, attributesToMerge ) { return { content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), diff --git a/packages/block-library/src/heading/save.js b/packages/block-library/src/heading/save.js index f554ff815a121a..7cf0de59884828 100644 --- a/packages/block-library/src/heading/save.js +++ b/packages/block-library/src/heading/save.js @@ -14,10 +14,10 @@ import { export default function save( { attributes } ) { const { align, - level, content, - textColor, customTextColor, + level, + textColor, } = attributes; const tagName = 'h' + level; @@ -25,6 +25,7 @@ export default function save( { attributes } ) { const className = classnames( { [ textClass ]: textClass, + [ `has-text-align-${ align }` ]: align, } ); return ( @@ -32,7 +33,6 @@ export default function save( { attributes } ) { className={ className ? className : undefined } tagName={ tagName } style={ { - textAlign: align, color: textClass ? undefined : customTextColor, } } value={ content } diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index d33dd3c393c3b1..f9a2909ff360a8 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -147,6 +147,18 @@ font-size: 42px; } +// Text alignments. +.has-text-align-center { + text-align: center; +} + +.has-text-align-left { + text-align: left; +} + +.has-text-align-right { + text-align: right; +} /** * Vanilla Block Styles diff --git a/packages/e2e-tests/fixtures/block-transforms.js b/packages/e2e-tests/fixtures/block-transforms.js index 913953d69d2b3e..723552c024b545 100644 --- a/packages/e2e-tests/fixtures/block-transforms.js +++ b/packages/e2e-tests/fixtures/block-transforms.js @@ -168,7 +168,7 @@ export const EXPECTED_TRANSFORMS = { originalBlock: 'Group', availableTransforms: [], }, - 'core__heading__h2-em': { + 'core__heading__h4-em': { originalBlock: 'Heading', availableTransforms: [ 'Quote', diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.html new file mode 100644 index 00000000000000..b0fc3807468356 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.html @@ -0,0 +1,3 @@ +<!-- wp:core/heading {"align":"right","level":3} --> +<h3 style="text-align:right">A picture is worth a thousand words, or so the saying goes</h3> +<!-- /wp:core/heading --> diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.json new file mode 100644 index 00000000000000..0553075b8e24d6 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/heading", + "isValid": true, + "attributes": { + "align": "right", + "content": "A picture is worth a thousand words, or so the saying goes", + "level": 3 + }, + "innerBlocks": [], + "originalContent": "<h3 style=\"text-align:right\">A picture is worth a thousand words, or so the saying goes</h3>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.parsed.json new file mode 100644 index 00000000000000..abb13458d09aa7 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/heading", + "attrs": { + "align": "right", + "level": 3 + }, + "innerBlocks": [], + "innerHTML": "\n<h3 style=\"text-align:right\">A picture is worth a thousand words, or so the saying goes</h3>\n", + "innerContent": [ + "\n<h3 style=\"text-align:right\">A picture is worth a thousand words, or so the saying goes</h3>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.serialized.html new file mode 100644 index 00000000000000..140a1929ad38c8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__heading__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:heading {"align":"right","level":3} --> +<h3 class="has-text-align-right">A picture is worth a thousand words, or so the saying goes</h3> +<!-- /wp:heading --> diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.html b/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.html deleted file mode 100644 index 7755d1dcf4eae2..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.html +++ /dev/null @@ -1,3 +0,0 @@ -<!-- wp:core/heading --> -<h2>The <em>Inserter</em> Tool</h2> -<!-- /wp:core/heading --> diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.serialized.html b/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.serialized.html deleted file mode 100644 index 55ce73502b6185..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.serialized.html +++ /dev/null @@ -1,3 +0,0 @@ -<!-- wp:heading --> -<h2>The <em>Inserter</em> Tool</h2> -<!-- /wp:heading --> diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.html b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.html new file mode 100644 index 00000000000000..bd13c30bb968d1 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.html @@ -0,0 +1,3 @@ +<!-- wp:core/heading {"level":4} --> +<h4>The <em>Inserter</em> Tool</h4> +<!-- /wp:core/heading --> diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.json b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.json similarity index 72% rename from packages/e2e-tests/fixtures/blocks/core__heading__h2-em.json rename to packages/e2e-tests/fixtures/blocks/core__heading__h4-em.json index b72785dd6d8c2a..32985e4bb53c85 100644 --- a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.json +++ b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.json @@ -5,9 +5,9 @@ "isValid": true, "attributes": { "content": "The <em>Inserter</em> Tool", - "level": 2 + "level": 4 }, "innerBlocks": [], - "originalContent": "<h2>The <em>Inserter</em> Tool</h2>" + "originalContent": "<h4>The <em>Inserter</em> Tool</h4>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.parsed.json b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.parsed.json similarity index 62% rename from packages/e2e-tests/fixtures/blocks/core__heading__h2-em.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__heading__h4-em.parsed.json index e10209f2270c98..302248c57f6311 100644 --- a/packages/e2e-tests/fixtures/blocks/core__heading__h2-em.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.parsed.json @@ -1,11 +1,13 @@ [ { "blockName": "core/heading", - "attrs": {}, + "attrs": { + "level": 4 + }, "innerBlocks": [], - "innerHTML": "\n<h2>The <em>Inserter</em> Tool</h2>\n", + "innerHTML": "\n<h4>The <em>Inserter</em> Tool</h4>\n", "innerContent": [ - "\n<h2>The <em>Inserter</em> Tool</h2>\n" + "\n<h4>The <em>Inserter</em> Tool</h4>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.serialized.html b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.serialized.html new file mode 100644 index 00000000000000..051e1c9f39b273 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__heading__h4-em.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:heading {"level":4} --> +<h4>The <em>Inserter</em> Tool</h4> +<!-- /wp:heading --> diff --git a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap index 25e50d75416a43..678dc348b496fd 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap @@ -64,13 +64,13 @@ exports[`Block transforms correctly transform block Heading in fixture core__hea <!-- /wp:quote -->" `; -exports[`Block transforms correctly transform block Heading in fixture core__heading__h2-em into the Paragraph block 1`] = ` +exports[`Block transforms correctly transform block Heading in fixture core__heading__h4-em into the Paragraph block 1`] = ` "<!-- wp:paragraph --> <p>The <em>Inserter</em> Tool</p> <!-- /wp:paragraph -->" `; -exports[`Block transforms correctly transform block Heading in fixture core__heading__h2-em into the Quote block 1`] = ` +exports[`Block transforms correctly transform block Heading in fixture core__heading__h4-em into the Quote block 1`] = ` "<!-- wp:quote --> <blockquote class=\\"wp-block-quote\\"><p>The <em>Inserter</em> Tool</p></blockquote> <!-- /wp:quote -->" From dcd0041608f324f42ad9c22099c3274959bdc0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 14 Jun 2019 13:08:38 +0200 Subject: [PATCH 340/664] Scripts: Document and simplify changing file entry and output of build scripts (#15982) --- packages/scripts/CHANGELOG.md | 6 ++++ packages/scripts/README.md | 8 +++-- packages/scripts/utils/cli.js | 5 ++- packages/scripts/utils/config.js | 54 ++++++++++++++++++++++++++++++-- packages/scripts/utils/index.js | 2 ++ 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 239314b3a61415..5fb6c959d8c14c 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Features + +- The `build` and `start` commands supports simplified syntax for multiple entry points: `wp-scripts build entry-one.js entry-two.js` ([15982](https://github.com/WordPress/gutenberg/pull/15982)). + ## 3.3.0 (2019-06-12) ### New Features diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 7ee723ce817047..531def5b59d22e 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -49,7 +49,8 @@ _Example:_ ```json { "scripts": { - "build": "wp-scripts build" + "build": "wp-scripts build", + "build:custom": "wp-scripts build entry-one.js entry-two.js --output-path=custom" } } ``` @@ -57,6 +58,7 @@ _Example:_ This is how you execute the script with presented setup: * `npm run build` - builds the code for production. +* `npm run build:custom` - builds the code for production with two entry points and a custom output folder. Paths for custom entry points are relative to the project root. #### Advanced information @@ -198,7 +200,8 @@ _Example:_ ```json { "scripts": { - "start": "wp-scripts start" + "start": "wp-scripts start", + "start:custom": "wp-scripts start entry-one.js entry-two.js --output-path=custom" } } ``` @@ -206,6 +209,7 @@ _Example:_ This is how you execute the script with presented setup: * `npm start` - starts the build for development. +* `npm run start:custom` - starts the build for development which contains two entry points and a custom output folder. Paths for custom entry points are relative to the project root. #### Advanced information diff --git a/packages/scripts/utils/cli.js b/packages/scripts/utils/cli.js index ab29ad4fa769e3..9b3bbd0dd1ca3d 100644 --- a/packages/scripts/utils/cli.js +++ b/packages/scripts/utils/cli.js @@ -27,7 +27,9 @@ const getArgFromCLI = ( arg ) => { const hasArgInCLI = ( arg ) => getArgFromCLI( arg ) !== undefined; -const hasFileArgInCLI = () => minimist( getArgsFromCLI() )._.length > 0; +const getFileArgsFromCLI = () => minimist( getArgsFromCLI() )._; + +const hasFileArgInCLI = () => getFileArgsFromCLI().length > 0; const handleSignal = ( signal ) => { if ( signal === 'SIGKILL' ) { @@ -83,6 +85,7 @@ const spawnScript = ( scriptName, args = [] ) => { module.exports = { getArgFromCLI, getArgsFromCLI, + getFileArgsFromCLI, hasArgInCLI, hasFileArgInCLI, spawnScript, diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index 9247f93a941717..a34895230af704 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -1,7 +1,12 @@ +/** + * External dependencies + */ +const { basename } = require( 'path' ); + /** * Internal dependencies */ -const { hasArgInCLI, getArgsFromCLI } = require( './cli' ); +const { getArgsFromCLI, getFileArgsFromCLI, hasArgInCLI, hasFileArgInCLI } = require( './cli' ); const { fromConfigRoot, hasProjectFile } = require( './file' ); const { hasPackageProp } = require( './package' ); @@ -22,12 +27,57 @@ const hasWebpackConfig = () => hasArgInCLI( '--config' ) || hasProjectFile( 'webpack.config.js' ) || hasProjectFile( 'webpack.config.babel.js' ); +/** + * Converts CLI arguments to the format which webpack understands. + * It allows to optionally pass some additional webpack CLI arguments. + * + * @see https://webpack.js.org/api/cli/#usage-with-config-file + * + * @param {?Array} additionalArgs The list of additional CLI arguments. + * + * @return {Array} The list of CLI arguments to pass to webpack CLI. + */ const getWebpackArgs = ( additionalArgs = [] ) => { - const webpackArgs = getArgsFromCLI(); + let webpackArgs = getArgsFromCLI(); + + const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); + if ( hasFileArgInCLI() && ! hasWebpackOutputOption ) { + /** + * Converts a path to the entry format supported by webpack, e.g.: + * `./entry-one.js` -> `entry-one=./entry-one.js` + * `entry-two.js` -> `entry-two=./entry-two.js` + * + * @param {string} path The path provided. + * + * @return {string} The entry format supported by webpack. + */ + const pathToEntry = ( path ) => { + const entry = basename( path, '.js' ); + + if ( ! path.startsWith( './' ) ) { + path = './' + path; + } + + return [ entry, path ].join( '=' ); + }; + + // The following handles the support for multiple entry points in webpack, e.g.: + // `wp-scripts build one.js custom=./two.js` -> `webpack one=./one.js custom=./two.js` + webpackArgs = webpackArgs.map( ( cliArg ) => { + if ( getFileArgsFromCLI().includes( cliArg ) && ! cliArg.includes( '=' ) ) { + return pathToEntry( cliArg ); + } + + return cliArg; + } ); + } + if ( ! hasWebpackConfig() ) { webpackArgs.push( '--config', fromConfigRoot( 'webpack.config.js' ) ); } + webpackArgs.push( ...additionalArgs ); + return webpackArgs; }; diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index f356d401356cd1..c45b1b6abbee1e 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -4,6 +4,7 @@ const { getArgFromCLI, getArgsFromCLI, + getFileArgsFromCLI, hasArgInCLI, hasFileArgInCLI, spawnScript, @@ -29,6 +30,7 @@ module.exports = { fromConfigRoot, getArgFromCLI, getArgsFromCLI, + getFileArgsFromCLI, getWebpackArgs, hasBabelConfig, hasArgInCLI, From 714d5615ab30d2dd69e60ec3bc0ac8522d9cf3e9 Mon Sep 17 00:00:00 2001 From: Stefanos Togoulidis <stefanostogoulidis@gmail.com> Date: Fri, 14 Jun 2019 15:12:04 +0300 Subject: [PATCH 341/664] [RNMobile] Native mobile release v1.7.0 (#16156) * [Mobile] Disable Video block on Android for v1.7.0 release (#16149) * Disable Video block on Android for v1.7.0 release * Add import statement to check platform * [RNMobile] Narrow down blur on unmount to replaceable only (#16151) * Remove focus from RichText when unmounting In #15999 this behavior was changed to prevent the keyboard from disappearing when you press Enter to create a new paragraph. This worked in that case, but had the side effect of TextInputState still thinking a dead view had focus, and causing a crash in some scenarios. * Only blur a RichText in replaceable block That RichText won't have the chance to blur earlier so, we need to do it on unmount. But, non replaceable blocks wont need to blur their RichText on unmount because that will happen normally in componentDidUpdate(). * Rename to an unstable, very goal-named name * Compute shouldBlurOnUnmount inside RichText * Fix no-duplicate-imports lint issue * Fix comma-dangle lint error * Remove trailing space from comment --- .../src/components/rich-text/index.native.js | 18 +++++++++++++++--- packages/edit-post/src/index.native.js | 10 ++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 489d0a5fbeaa95..7d8cfe2b737df5 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -34,7 +34,11 @@ import { } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { BACKSPACE } from '@wordpress/keycodes'; -import { pasteHandler, children } from '@wordpress/blocks'; +import { + children, + isUnmodifiedDefaultBlock, + pasteHandler, +} from '@wordpress/blocks'; import { isURL } from '@wordpress/url'; /** @@ -706,8 +710,8 @@ export class RichText extends Component { } componentWillUnmount() { - if ( this._editor.isFocused() ) { - // this._editor.blur(); + if ( this._editor.isFocused() && this.props.shouldBlurOnUnmount ) { + this._editor.blur(); } } @@ -880,6 +884,7 @@ const RichTextContainer = compose( [ const { getSelectionStart, getSelectionEnd, + __unstableGetBlockWithoutInnerBlocks, } = select( 'core/block-editor' ); const selectionStart = getSelectionStart(); @@ -892,12 +897,19 @@ const RichTextContainer = compose( [ ); } + // If the block of this RichText is unmodified then it's a candidate for replacing when adding a new block. + // In order to fix https://github.com/wordpress-mobile/gutenberg-mobile/issues/1126, let's blur on unmount in that case. + // This apparently assumes functionality the BlockHlder actually + const block = clientId && __unstableGetBlockWithoutInnerBlocks( clientId ); + const shouldBlurOnUnmount = block && isSelected && isUnmodifiedDefaultBlock( block ); + return { formatTypes: getFormatTypes(), selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, blockIsSelected, + shouldBlurOnUnmount, }; } ), withDispatch( ( dispatch, { diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 1abd926f26bc8c..5854c991101955 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { Platform } from 'react-native'; + /** * WordPress dependencies */ @@ -22,6 +27,11 @@ export function initializeEditor() { // eslint-disable-next-line no-undef if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); + + // Disable Video block except for iOS for now. + if ( Platform.OS !== 'ios' ) { + unregisterBlockType( 'core/video' ); + } } } From 2a11e472142b03905d9d11abd27e803c2453df0b Mon Sep 17 00:00:00 2001 From: Seghir Nadir <nadir.seghir@gmail.com> Date: Fri, 14 Jun 2019 15:20:55 +0100 Subject: [PATCH 342/664] refactor snackbar to follow spec while keeping compatibility with notice (#16095) --- packages/components/src/snackbar/index.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js index afca0dd162d8df..a62924219a9b5c 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.js @@ -34,6 +34,13 @@ function Snackbar( { }, [] ); const classes = classnames( className, 'components-snackbar' ); + if ( actions && actions.length > 1 ) { + // we need to inform developers that snackbar only accepts 1 action + // eslint-disable-next-line no-console + console.warn( 'Snackbar can only have 1 action, use Notice if your message require many messages' ); + // return first element only while keeping it inside an array + actions = [ actions[ 0 ] ]; + } return ( <div @@ -50,7 +57,6 @@ function Snackbar( { { actions.map( ( { - className: buttonCustomClasses, label, onClick, url, @@ -68,10 +74,8 @@ function Snackbar( { onClick( event ); } } } - className={ classnames( - 'components-snackbar__action', - buttonCustomClasses - ) } + className="components-snackbar__action" + > { label } </Button> From bc7a14bf9b9500e8032e363ca3106d74ee860c94 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Fri, 14 Jun 2019 17:34:08 +0300 Subject: [PATCH 343/664] [Mobile]Update video player to open the URL by browser on Android (#16089) * Update video player to open the URL by browser * Render play button even if video is not ready to play yet * Add/update alert messages * Revert back warn to error * Do not show play button until video player has a height * Resolve merge conflicts that re-introduced a removed file --- .../src/video/video-player.android.js | 28 ----- .../src/video/video-player.ios.js | 82 -------------- .../src/video/video-player.native.js | 106 ++++++++++++++++++ .../src/video/video-player.native.scss | 2 +- 4 files changed, 107 insertions(+), 111 deletions(-) delete mode 100644 packages/block-library/src/video/video-player.android.js delete mode 100644 packages/block-library/src/video/video-player.ios.js create mode 100644 packages/block-library/src/video/video-player.native.js diff --git a/packages/block-library/src/video/video-player.android.js b/packages/block-library/src/video/video-player.android.js deleted file mode 100644 index 669948bbdd2d3b..00000000000000 --- a/packages/block-library/src/video/video-player.android.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * External dependencies - */ -import { View } from 'react-native'; -import { default as VideoPlayer } from 'react-native-video'; - -/** - * Internal dependencies - */ -import styles from './video-player.scss'; - -const Video = ( props ) => { - const { isSelected, ...videoProps } = props; - - return ( - <View style={ styles.videoContainer }> - <VideoPlayer - { ...videoProps } - // We are using built-in player controls becasue manually - // calling presentFullscreenPlayer() is not working for android - controls={ isSelected } - muted={ ! isSelected } - /> - </View> - ); -}; - -export default Video; diff --git a/packages/block-library/src/video/video-player.ios.js b/packages/block-library/src/video/video-player.ios.js deleted file mode 100644 index 53d88380207080..00000000000000 --- a/packages/block-library/src/video/video-player.ios.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; -import { Dashicon } from '@wordpress/components'; - -/** - * External dependencies - */ -import { View, TouchableOpacity } from 'react-native'; -import { default as VideoPlayer } from 'react-native-video'; - -/** - * Internal dependencies - */ -import styles from './video-player.scss'; - -class Video extends Component { - constructor() { - super( ...arguments ); - this.state = { - isLoaded: false, - isFullScreen: false, - }; - this.onPressPlay = this.onPressPlay.bind( this ); - this.onLoad = this.onLoad.bind( this ); - this.onLoadStart = this.onLoadStart.bind( this ); - } - - onLoad() { - this.setState( { isLoaded: true } ); - } - - onLoadStart() { - this.setState( { isLoaded: false } ); - } - - onPressPlay() { - if ( this.player ) { - this.player.presentFullscreenPlayer(); - } - } - - render() { - const { isSelected, style } = this.props; - const { isLoaded, isFullScreen } = this.state; - - return ( - <View style={ styles.videoContainer }> - <VideoPlayer - { ...this.props } - ref={ ( ref ) => { - this.player = ref; - } } - // Using built-in player controls is messing up the layout on iOS. - // So we are setting controls=false and adding a play button that - // will trigger presentFullscreenPlayer() - controls={ false } - onLoad={ this.onLoad } - onLoadStart={ this.onLoadStart } - ignoreSilentSwitch={ 'ignore' } - paused={ ! isFullScreen } - onFullscreenPlayerWillPresent={ () => { - this.setState( { isFullScreen: true } ); - } } - onFullscreenPlayerDidDismiss={ () => { - this.setState( { isFullScreen: false } ); - } } - /> - { isLoaded && - <TouchableOpacity disabled={ ! isSelected } onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> - <View style={ styles.playIcon }> - <Dashicon icon={ 'controls-play' } ariaPressed={ 'dashicon-active' } size={ styles.playIcon.width } /> - </View> - </TouchableOpacity> - } - </View> - ); - } -} - -export default Video; diff --git a/packages/block-library/src/video/video-player.native.js b/packages/block-library/src/video/video-player.native.js new file mode 100644 index 00000000000000..5b67d4673b4192 --- /dev/null +++ b/packages/block-library/src/video/video-player.native.js @@ -0,0 +1,106 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { Dashicon } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * External dependencies + */ +import { View, TouchableOpacity, Platform, Linking, Alert } from 'react-native'; +import { default as VideoPlayer } from 'react-native-video'; + +/** + * Internal dependencies + */ +import styles from './video-player.scss'; + +class Video extends Component { + constructor() { + super( ...arguments ); + this.isIOS = Platform.OS === 'ios'; + this.state = { + isFullScreen: false, + videoContainerHeight: 0, + }; + this.onPressPlay = this.onPressPlay.bind( this ); + this.onVideoLayout = this.onVideoLayout.bind( this ); + } + + onVideoLayout( event ) { + const { height } = event.nativeEvent.layout; + if ( height !== this.state.videoContainerHeight ) { + this.setState( { videoContainerHeight: height } ); + } + } + + onPressPlay() { + if ( this.isIOS ) { + if ( this.player ) { + this.player.presentFullscreenPlayer(); + } + } else { + const { source } = this.props; + if ( source && source.uri ) { + this.openURL( source.uri ); + } + } + } + + // Tries opening the URL outside of the app + openURL( url ) { + Linking.canOpenURL( url ).then( ( supported ) => { + if ( ! supported ) { + Alert.alert( __( 'Problem opening the video' ), __( 'No application can handle this request. Please install a Web browser.' ) ); + window.console.warn( 'No application found that can open the video with URL: ' + url ); + } else { + return Linking.openURL( url ); + } + } ).catch( ( err ) => { + Alert.alert( __( 'Problem opening the video' ), __( 'An unknown error occurred. Please try again.' ) ); + window.console.error( 'An error occurred while opening the video URL: ' + url, err ); + } ); + } + + render() { + const { isSelected, style } = this.props; + const { isFullScreen, videoContainerHeight } = this.state; + const showPlayButton = videoContainerHeight > 0; + + return ( + <View style={ styles.videoContainer }> + <VideoPlayer + { ...this.props } + ref={ ( ref ) => { + this.player = ref; + } } + // Using built-in player controls is messing up the layout on iOS. + // So we are setting controls=false and adding a play button that + // will trigger presentFullscreenPlayer() + controls={ false } + ignoreSilentSwitch={ 'ignore' } + paused={ ! isFullScreen } + onLayout={ this.onVideoLayout } + onFullscreenPlayerWillPresent={ () => { + this.setState( { isFullScreen: true } ); + } } + onFullscreenPlayerDidDismiss={ () => { + this.setState( { isFullScreen: false } ); + } } + /> + { showPlayButton && + // If we add the play icon as a subview to VideoPlayer then react-native-video decides to show control buttons + // even if we set controls={ false }, so we are adding our play button as a sibling overlay view. + <TouchableOpacity disabled={ ! isSelected } onPress={ this.onPressPlay } style={ [ style, styles.overlay ] }> + <View style={ styles.playIcon }> + <Dashicon icon={ 'controls-play' } ariaPressed={ 'dashicon-active' } size={ styles.playIcon.width } /> + </View> + </TouchableOpacity> + } + </View> + ); + } +} + +export default Video; diff --git a/packages/block-library/src/video/video-player.native.scss b/packages/block-library/src/video/video-player.native.scss index ec746de8288916..ae0dc6382f91f9 100644 --- a/packages/block-library/src/video/video-player.native.scss +++ b/packages/block-library/src/video/video-player.native.scss @@ -13,7 +13,7 @@ $play-icon-size: 50; align-items: center; align-self: center; position: absolute; - background-color: #e9eff3; + background-color: transparent; opacity: 0.3; } From 38f3c34e03b8cf62d5ff4f05892f1ded4f3022fb Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 14 Jun 2019 16:24:01 +0100 Subject: [PATCH 344/664] Support forwardRef components in save functions (#16180) --- packages/element/src/serialize.js | 7 +++++++ packages/element/src/test/serialize.js | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/packages/element/src/serialize.js b/packages/element/src/serialize.js index 5c2c2a8011d2a7..b700831f1dccd0 100644 --- a/packages/element/src/serialize.js +++ b/packages/element/src/serialize.js @@ -49,10 +49,14 @@ import { createContext, Fragment, StrictMode, + forwardRef, } from './react'; import RawHTML from './raw-html'; const { Provider, Consumer } = createContext(); +const ForwardRef = forwardRef( () => { + return null; +} ); /** * Valid attribute types. @@ -406,6 +410,9 @@ export function renderElement( element, context, legacyContext = {} ) { case Consumer.$$typeof: return renderElement( props.children( context || type._currentValue ), context, legacyContext ); + + case ForwardRef.$$typeof: + return renderElement( type.render( props ), context, legacyContext ); } return ''; diff --git a/packages/element/src/test/serialize.js b/packages/element/src/test/serialize.js index 7fe4251666decd..b6a8c906751d97 100644 --- a/packages/element/src/test/serialize.js +++ b/packages/element/src/test/serialize.js @@ -12,6 +12,7 @@ import { createElement, Fragment, StrictMode, + forwardRef, } from '../react'; import RawHTML from '../raw-html'; import serialize, { @@ -84,6 +85,20 @@ describe( 'serialize()', () => { ); } ); + it( 'should render with forwardRef', () => { + const ForwardedComponent = forwardRef( () => { + return <div>test</div>; + } ); + + const result = serialize( + <ForwardedComponent /> + ); + + expect( result ).toBe( + '<div>test</div>' + ); + } ); + describe( 'empty attributes', () => { it( 'should not render a null attribute value', () => { const result = serialize( <video src={ undefined } /> ); From 558b96f5c0dfcb500eb680a3eaef427919050851 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 14 Jun 2019 16:32:56 +0100 Subject: [PATCH 345/664] Bump plugin version to 5.9.2 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index e762ee4326aeca..ed0eb8e6c0f508 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.9.1 + * Version: 5.9.2 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index a767b27c99ab50..659cba78d7fb3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.1", + "version": "5.9.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2afdd8eccedfd5..c74000d2a1f86d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.1", + "version": "5.9.2", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From d318b2f658012e264559d1206acfa7e1d3d3019e Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Fri, 14 Jun 2019 15:36:46 -0700 Subject: [PATCH 346/664] Try making the Inserter category icons grayscale. (#16163) * Fixes #14180. Makes the icons grayscale. * Added an important to the style to increase strictness. --- packages/components/src/panel/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/panel/style.scss b/packages/components/src/panel/style.scss index 828af037a724f4..220249d69e942a 100644 --- a/packages/components/src/panel/style.scss +++ b/packages/components/src/panel/style.scss @@ -117,6 +117,7 @@ .components-panel__icon { color: $dark-gray-500; + filter: grayscale(1) !important; margin: -2px 0 -2px 6px; } From 1d653d1694164f51c5bef67afc3bfdb25870eddd Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 17 Jun 2019 09:34:06 +0100 Subject: [PATCH 347/664] Adding snackbar notices support to the new widgets page (#16020) --- packages/edit-widgets/package.json | 3 +- .../src/components/layout/index.js | 2 ++ .../src/components/notices/index.js | 32 +++++++++++++++++++ .../src/components/notices/style.scss | 8 +++++ packages/edit-widgets/src/index.js | 1 + packages/edit-widgets/src/store/actions.js | 13 ++++++++ .../edit-widgets/src/store/test/actions.js | 18 +++++++++++ packages/edit-widgets/src/style.scss | 1 + 8 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 packages/edit-widgets/src/components/notices/index.js create mode 100644 packages/edit-widgets/src/components/notices/style.scss diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 1acce755f85426..bf71d3bdd4e344 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -25,7 +25,8 @@ "@wordpress/block-editor": "file:../block-editor", "@wordpress/components": "file:../components", "@wordpress/element": "file:../element", - "@wordpress/i18n": "file:../i18n" + "@wordpress/i18n": "file:../i18n", + "@wordpress/notices": "file:../notices" }, "publishConfig": { "access": "public" diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 4a9a985e89e477..a20d11e557e6d0 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -10,12 +10,14 @@ import { navigateRegions } from '@wordpress/components'; import Header from '../header'; import Sidebar from '../sidebar'; import WidgetAreas from '../widget-areas'; +import Notices from '../notices'; function Layout( { blockEditorSettings } ) { return ( <> <Header /> <Sidebar /> + <Notices /> <div className="edit-widgets-layout__content" role="region" diff --git a/packages/edit-widgets/src/components/notices/index.js b/packages/edit-widgets/src/components/notices/index.js new file mode 100644 index 00000000000000..fb962df3a6e8a4 --- /dev/null +++ b/packages/edit-widgets/src/components/notices/index.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { filter } from 'lodash'; + +/** + * WordPress dependencies + */ +import { SnackbarList } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; + +function Notices() { + const { notices } = useSelect( ( select ) => { + return { + notices: select( 'core/notices' ).getNotices(), + }; + } ); + const snackbarNotices = filter( notices, { + type: 'snackbar', + } ); + const { removeNotice } = useDispatch( 'core/notices' ); + + return ( + <SnackbarList + notices={ snackbarNotices } + className="edit-widgets-notices__snackbar" + onRemove={ removeNotice } + /> + ); +} + +export default Notices; diff --git a/packages/edit-widgets/src/components/notices/style.scss b/packages/edit-widgets/src/components/notices/style.scss new file mode 100644 index 00000000000000..c71d8a34487cc7 --- /dev/null +++ b/packages/edit-widgets/src/components/notices/style.scss @@ -0,0 +1,8 @@ +.edit-widgets-notices__snackbar { + position: fixed; + right: 0; + bottom: 20px; + padding-left: 16px; + padding-right: 16px; +} +@include editor-left(".edit-widgets-notices__snackbar"); diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 4480c5b0a06ec0..8b4bb8573262de 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import '@wordpress/notices'; import { render } from '@wordpress/element'; import { registerCoreBlocks } from '@wordpress/block-library'; diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index dd87ca78c2e6af..e2bcfe6f69094c 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -8,6 +8,9 @@ import { get, map } from 'lodash'; */ import { parse, serialize } from '@wordpress/blocks'; import { dispatch, select } from '@wordpress/data-controls'; +import { __ } from '@wordpress/i18n'; + +const WIDGET_AREAS_SAVE_NOTICE_ID = 'WIDGET_AREAS_SAVE_NOTICE_ID'; /** * Yields an action object that setups the widget areas. @@ -73,4 +76,14 @@ export function* saveWidgetAreas() { } ); } + + yield dispatch( + 'core/notices', + 'createSuccessNotice', + __( 'Block areas saved succesfully.' ), + { + id: WIDGET_AREAS_SAVE_NOTICE_ID, + type: 'snackbar', + } + ); } diff --git a/packages/edit-widgets/src/store/test/actions.js b/packages/edit-widgets/src/store/test/actions.js index c56793b14be15b..0579d4b452db7c 100644 --- a/packages/edit-widgets/src/store/test/actions.js +++ b/packages/edit-widgets/src/store/test/actions.js @@ -178,6 +178,24 @@ describe( 'actions', () => { }, } ); + expect( + saveWidgetAreasGen.next() + ).toEqual( { + done: false, + value: { + type: 'DISPATCH', + storeKey: 'core/notices', + actionName: 'createSuccessNotice', + args: [ + 'Block areas saved succesfully.', + { + id: 'WIDGET_AREAS_SAVE_NOTICE_ID', + type: 'snackbar', + }, + ], + }, + } ); + expect( saveWidgetAreasGen.next() ).toEqual( { diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index 4be0d14910338a..ab68ef801af1d4 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -1,5 +1,6 @@ @import "./components/header/style.scss"; @import "./components/layout/style.scss"; +@import "./components/notices/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/widget-area/style.scss"; From d846d574ed8283075811b5ec4ffcd0bb04aa6a22 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 17 Jun 2019 12:00:44 +0100 Subject: [PATCH 348/664] Fix package-lock. Notices added to the widgets package --- package-lock.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 659cba78d7fb3c..879de9fcea37e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3547,7 +3547,8 @@ "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/components": "file:packages/components", "@wordpress/element": "file:packages/element", - "@wordpress/i18n": "file:packages/i18n" + "@wordpress/i18n": "file:packages/i18n", + "@wordpress/notices": "file:packages/notices" } }, "@wordpress/editor": { From e13b6392ab2433457529948ed6f52585159394b3 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 17 Jun 2019 07:40:17 -0400 Subject: [PATCH 349/664] Reinstate the hover state on all breakpoints. (#16168) --- packages/components/src/menu-item/style.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/components/src/menu-item/style.scss b/packages/components/src/menu-item/style.scss index 8424da18a6f02d..b854dab40915cb 100644 --- a/packages/components/src/menu-item/style.scss +++ b/packages/components/src/menu-item/style.scss @@ -19,11 +19,7 @@ } &:hover:not(:disabled):not([aria-disabled="true"]) { - // Disable hover style on mobile to prevent odd scroll behaviour. - // See: https://github.com/WordPress/gutenberg/pull/10333 - @include break-medium() { - @include menu-style__hover; - } + @include menu-style__hover; .components-menu-item__shortcut { color: $dark-gray-600; From 06eb744badc991c8ef47b4be7123d7c898f6c66f Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 17 Jun 2019 08:06:23 -0400 Subject: [PATCH 350/664] Update modal overlay color to match WP-Admin (#15974) --- packages/components/src/modal/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index d877fd2bf16dc4..6ff34f69bd0ba1 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -5,7 +5,7 @@ right: 0; bottom: 0; left: 0; - background-color: rgba($white, 0.4); + background-color: rgba($black, 0.7); z-index: z-index(".components-modal__screen-overlay"); // This animates the appearance of the white background. From 763fb652f8f3c9add1d70f44470d2449421158d2 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 17 Jun 2019 08:14:37 -0400 Subject: [PATCH 351/664] Edit Post: Remove SlotFillProvider as rendered by block editor (#15988) --- packages/block-editor/CHANGELOG.md | 6 +++ .../src/components/provider/index.js | 9 +---- packages/edit-post/src/editor.js | 36 +++++++++++------- playground/src/index.js | 38 +++++++++++-------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 15e4a9de6b0a80..2d97dd70be0c25 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Breaking Changes + +- `BlockEditorProvider` no longer renders a wrapping `SlotFillProvider` or `DropZoneProvider` (from `@wordpress/components`). For custom block editors, you should render your own as wrapping the `BlockEditorProvider`. A future release will include a new `BlockEditor` component for simple, standard usage. `BlockEditorProvider` will serve the simple purpose of establishing its own context for block editors. + ## 2.2.0 (2019-06-12) ### Internal diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index 43f336325434db..6d06246ca9170a 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; import { withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -120,13 +119,7 @@ class BlockEditorProvider extends Component { render() { const { children } = this.props; - return ( - <SlotFillProvider> - <DropZoneProvider> - { children } - </DropZoneProvider> - </SlotFillProvider> - ); + return children; } } diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index ea48a3a1d8e358..88eb7c4a6113fd 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -10,7 +10,11 @@ import { size, map, without } from 'lodash'; import { withSelect } from '@wordpress/data'; import { EditorProvider, ErrorBoundary, PostLockedModal } from '@wordpress/editor'; import { StrictMode, Component } from '@wordpress/element'; -import { KeyboardShortcuts } from '@wordpress/components'; +import { + KeyboardShortcuts, + SlotFillProvider, + DropZoneProvider, +} from '@wordpress/components'; /** * Internal dependencies @@ -87,19 +91,23 @@ class Editor extends Component { return ( <StrictMode> - <EditorProvider - settings={ editorSettings } - post={ post } - initialEdits={ initialEdits } - useSubRegistry={ false } - { ...props } - > - <ErrorBoundary onError={ onError }> - <Layout /> - <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> - </ErrorBoundary> - <PostLockedModal /> - </EditorProvider> + <SlotFillProvider> + <DropZoneProvider> + <EditorProvider + settings={ editorSettings } + post={ post } + initialEdits={ initialEdits } + useSubRegistry={ false } + { ...props } + > + <ErrorBoundary onError={ onError }> + <Layout /> + <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> + </ErrorBoundary> + <PostLockedModal /> + </EditorProvider> + </DropZoneProvider> + </SlotFillProvider> </StrictMode> ); } diff --git a/playground/src/index.js b/playground/src/index.js index 3f125282c49090..d230f202243c23 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -10,7 +10,11 @@ import { WritingFlow, ObserveTyping, } from '@wordpress/block-editor'; -import { Popover } from '@wordpress/components'; +import { + Popover, + SlotFillProvider, + DropZoneProvider, +} from '@wordpress/components'; import { registerCoreBlocks } from '@wordpress/block-library'; import '@wordpress/format-library'; @@ -37,20 +41,24 @@ function App() { <h1 className="playground__logo">Gutenberg Playground</h1> </div> <div className="playground__body"> - <BlockEditorProvider - value={ blocks } - onInput={ updateBlocks } - onChange={ updateBlocks } - > - <div className="editor-styles-wrapper"> - <WritingFlow> - <ObserveTyping> - <BlockList /> - </ObserveTyping> - </WritingFlow> - </div> - <Popover.Slot /> - </BlockEditorProvider> + <SlotFillProvider> + <DropZoneProvider> + <BlockEditorProvider + value={ blocks } + onInput={ updateBlocks } + onChange={ updateBlocks } + > + <div className="editor-styles-wrapper"> + <WritingFlow> + <ObserveTyping> + <BlockList /> + </ObserveTyping> + </WritingFlow> + </div> + <Popover.Slot /> + </BlockEditorProvider> + </DropZoneProvider> + </SlotFillProvider> </div> </Fragment> ); From c80a0089cc3d24c4a01e693e2fc209279a74f45a Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 17 Jun 2019 08:55:00 -0400 Subject: [PATCH 352/664] Try adding an inner container to the Group block. (#15210) * Add a inner container to the Group block. To make theme styling easier. * Update test fixtures * Update snapshot. * Add deprecation for group block --- .../block-library/src/group/deprecated.js | 48 +++++++++++++++++++ packages/block-library/src/group/edit.js | 8 ++-- packages/block-library/src/group/editor.scss | 12 ++--- packages/block-library/src/group/index.js | 2 + packages/block-library/src/group/save.js | 4 +- .../fixtures/blocks/core__group.html | 14 +++--- .../fixtures/blocks/core__group.json | 2 +- .../fixtures/blocks/core__group.parsed.json | 16 +++---- .../blocks/core__group.serialized.html | 4 +- .../blocks/core__group__deprecated.html | 7 +++ .../blocks/core__group__deprecated.json | 25 ++++++++++ .../core__group__deprecated.parsed.json | 35 ++++++++++++++ .../core__group__deprecated.serialized.html | 5 ++ .../blocks/__snapshots__/group.test.js.snap | 8 ++-- 14 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 packages/block-library/src/group/deprecated.js create mode 100644 packages/e2e-tests/fixtures/blocks/core__group__deprecated.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__group__deprecated.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html diff --git a/packages/block-library/src/group/deprecated.js b/packages/block-library/src/group/deprecated.js new file mode 100644 index 00000000000000..ced2cfca58b993 --- /dev/null +++ b/packages/block-library/src/group/deprecated.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { InnerBlocks, getColorClassName } from '@wordpress/block-editor'; + +const deprecated = [ + // v1 of group block. Deprecated to add an inner-container div around `InnerBlocks.Content`. + { + attributes: { + backgroundColor: { + type: 'string', + }, + customBackgroundColor: { + type: 'string', + }, + }, + supports: { + align: [ 'wide', 'full' ], + anchor: true, + html: false, + }, + save( { attributes } ) { + const { backgroundColor, customBackgroundColor } = attributes; + + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const className = classnames( backgroundClass, { + 'has-background': backgroundColor || customBackgroundColor, + } ); + + const styles = { + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + }; + + return ( + <div className={ className } style={ styles }> + <InnerBlocks.Content /> + </div> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index f6756a7750c223..1e7445a20cc954 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -45,9 +45,11 @@ function GroupEdit( { /> </InspectorControls> <div className={ classes } style={ styles }> - <InnerBlocks - renderAppender={ ! hasInnerBlocks && InnerBlocks.ButtonBlockAppender } - /> + <div className="wp-block-group__inner-container"> + <InnerBlocks + renderAppender={ ! hasInnerBlocks && InnerBlocks.ButtonBlockAppender } + /> + </div> </div> </> ); diff --git a/packages/block-library/src/group/editor.scss b/packages/block-library/src/group/editor.scss index d3b83213bff0ae..68bfde8888604d 100644 --- a/packages/block-library/src/group/editor.scss +++ b/packages/block-library/src/group/editor.scss @@ -11,14 +11,14 @@ } // Only applied when background is added to cancel out padding - > .editor-block-list__block-edit > div > .wp-block-group.has-background > .editor-inner-blocks { + > .editor-block-list__block-edit > div > .wp-block-group.has-background > .wp-block-group__inner-container > .editor-inner-blocks { margin-top: -#{$block-padding*2 + $block-spacing}; margin-bottom: -#{$block-padding*2 + $block-spacing}; } // Full Width Blocks // specificity required to only target immediate child Blocks of a Group - > .editor-block-list__block-edit > div > .wp-block-group > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { + > .editor-block-list__block-edit > div > .wp-block-group > .wp-block-group__inner-container > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { margin-left: auto; margin-right: auto; padding-left: $block-padding*2; @@ -31,7 +31,7 @@ } // Full Width Blocks with a background (ie: has padding) - > .editor-block-list__block-edit > div > .wp-block-group.has-background > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { + > .editor-block-list__block-edit > div > .wp-block-group.has-background > .wp-block-group__inner-container > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { // note: using position `left` causes hoz scrollbars so // we opt to use margin instead // the 30px matches the hoz padding applied in `theme.scss` @@ -51,7 +51,7 @@ .wp-block[data-type="core/group"][data-align="full"] { // First tier of InnerBlocks must act like the container of the standard canvas - > .editor-block-list__block-edit > div > .wp-block-group > .editor-inner-blocks { + > .editor-block-list__block-edit > div > .wp-block-group > .wp-block-group__inner-container > .editor-inner-blocks { margin-left: auto; margin-right: auto; padding-left: 0; @@ -65,7 +65,7 @@ // Full Width Blocks // specificity required to only target immediate child Blocks of Group - > .editor-block-list__block-edit > div > .wp-block-group > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { + > .editor-block-list__block-edit > div > .wp-block-group > .wp-block-group__inner-container > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { padding-right: 0; padding-left: 0; left: 0; @@ -81,7 +81,7 @@ // Full Width Blocks with a background (ie: has padding) // note: also duplicated above for all Group widths - > .editor-block-list__block-edit > div > .wp-block-group.has-background > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { + > .editor-block-list__block-edit > div > .wp-block-group.has-background > .wp-block-group__inner-container > .editor-inner-blocks > .editor-block-list__layout > .wp-block[data-align="full"] { width: calc(100% + 60px); } } diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index 844ddd8c821600..530dc210865bac 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -7,6 +7,7 @@ import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; @@ -67,4 +68,5 @@ export const settings = { edit, save, + deprecated, }; diff --git a/packages/block-library/src/group/save.js b/packages/block-library/src/group/save.js index a0ee8246c69a8d..c2c56221766b1a 100644 --- a/packages/block-library/src/group/save.js +++ b/packages/block-library/src/group/save.js @@ -22,7 +22,9 @@ export default function save( { attributes } ) { return ( <div className={ className } style={ styles }> - <InnerBlocks.Content /> + <div className="wp-block-group__inner-container"> + <InnerBlocks.Content /> + </div> </div> ); } diff --git a/packages/e2e-tests/fixtures/blocks/core__group.html b/packages/e2e-tests/fixtures/blocks/core__group.html index 2c8055434700de..09fd95cfc9d5a4 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group.html +++ b/packages/e2e-tests/fixtures/blocks/core__group.html @@ -1,10 +1,12 @@ <!-- wp:group {"backgroundColor":"secondary","align":"full"} --> <div class="wp-block-group alignfull has-secondary-background-color has-background"> - <!-- wp:paragraph --> - <p>This is a group block.</p> - <!-- /wp:paragraph --> + <div class="wp-block-group__inner-container"> + <!-- wp:paragraph --> + <p>This is a group block.</p> + <!-- /wp:paragraph --> - <!-- wp:paragraph --> - <p>Group block content.</p> - <!-- /wp:paragraph --></div> + <!-- wp:paragraph --> + <p>Group block content.</p> + <!-- /wp:paragraph --></div> + </div> <!-- /wp:group --> diff --git a/packages/e2e-tests/fixtures/blocks/core__group.json b/packages/e2e-tests/fixtures/blocks/core__group.json index ec8f4ca9ad64da..71d94f926dcccf 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group.json +++ b/packages/e2e-tests/fixtures/blocks/core__group.json @@ -31,6 +31,6 @@ "originalContent": "<p>Group block content.</p>" } ], - "originalContent": "<div class=\"wp-block-group alignfull has-secondary-background-color has-background\">\n\t\n\n\t</div>" + "originalContent": "<div class=\"wp-block-group alignfull has-secondary-background-color has-background\">\n\t<div class=\"wp-block-group__inner-container\">\n\t\t\n\n\t\t</div>\n\t</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__group.parsed.json b/packages/e2e-tests/fixtures/blocks/core__group.parsed.json index 06718c82d85f16..bf2b50feb5fd1f 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__group.parsed.json @@ -10,28 +10,28 @@ "blockName": "core/paragraph", "attrs": {}, "innerBlocks": [], - "innerHTML": "\n\t<p>This is a group block.</p>\n\t", + "innerHTML": "\n\t\t<p>This is a group block.</p>\n\t\t", "innerContent": [ - "\n\t<p>This is a group block.</p>\n\t" + "\n\t\t<p>This is a group block.</p>\n\t\t" ] }, { "blockName": "core/paragraph", "attrs": {}, "innerBlocks": [], - "innerHTML": "\n\t<p>Group block content.</p>\n\t", + "innerHTML": "\n\t\t<p>Group block content.</p>\n\t\t", "innerContent": [ - "\n\t<p>Group block content.</p>\n\t" + "\n\t\t<p>Group block content.</p>\n\t\t" ] } ], - "innerHTML": "\n<div class=\"wp-block-group alignfull has-secondary-background-color has-background\">\n\t\n\n\t</div>\n", + "innerHTML": "\n<div class=\"wp-block-group alignfull has-secondary-background-color has-background\">\n\t<div class=\"wp-block-group__inner-container\">\n\t\t\n\n\t\t</div>\n\t</div>\n", "innerContent": [ - "\n<div class=\"wp-block-group alignfull has-secondary-background-color has-background\">\n\t", + "\n<div class=\"wp-block-group alignfull has-secondary-background-color has-background\">\n\t<div class=\"wp-block-group__inner-container\">\n\t\t", null, - "\n\n\t", + "\n\n\t\t", null, - "</div>\n" + "</div>\n\t</div>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__group.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group.serialized.html index bcc69c0e890098..2c398b530dedc5 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__group.serialized.html @@ -1,9 +1,9 @@ <!-- wp:group {"backgroundColor":"secondary","align":"full"} --> -<div class="wp-block-group alignfull has-secondary-background-color has-background"><!-- wp:paragraph --> +<div class="wp-block-group alignfull has-secondary-background-color has-background"><div class="wp-block-group__inner-container"><!-- wp:paragraph --> <p>This is a group block.</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>Group block content.</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group --> diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html new file mode 100644 index 00000000000000..e57b273d388e51 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html @@ -0,0 +1,7 @@ +<!-- wp:group {"backgroundColor":"lighter-blue","align":"full"} --> +<div class="wp-block-group alignfull has-lighter-blue-background-color has-background"> + <!-- wp:paragraph --> + <p>test</p> + <!-- /wp:paragraph --> +</div> +<!-- /wp:group --> diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json new file mode 100644 index 00000000000000..89116067ef6f6b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json @@ -0,0 +1,25 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/group", + "isValid": true, + "attributes": { + "backgroundColor": "lighter-blue", + "className": "alignfull" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": "test", + "dropCap": false + }, + "innerBlocks": [], + "originalContent": "<p>test</p>" + } + ], + "originalContent": "<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\">\n\t\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json new file mode 100644 index 00000000000000..38723b5e2457a5 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json @@ -0,0 +1,35 @@ +[ + { + "blockName": "core/group", + "attrs": { + "backgroundColor": "lighter-blue", + "align": "full" + }, + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n\t<p>test</p>\n\t", + "innerContent": [ + "\n\t<p>test</p>\n\t" + ] + } + ], + "innerHTML": "\n<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\">\n\t\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\">\n\t", + null, + "\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html new file mode 100644 index 00000000000000..58aca8fd2e3508 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:group {"backgroundColor":"lighter-blue","className":"alignfull"} --> +<div class="wp-block-group has-lighter-blue-background-color has-background alignfull"><div class="wp-block-group__inner-container"><!-- wp:paragraph --> +<p>test</p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:group --> diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/group.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/group.test.js.snap index df7b3f6419f155..49ddf4c41946de 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/group.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/group.test.js.snap @@ -2,20 +2,20 @@ exports[`Group can be created using the block inserter 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"></div> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"></div></div> <!-- /wp:group -->" `; exports[`Group can be created using the slash inserter 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"></div> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"></div></div> <!-- /wp:group -->" `; exports[`Group can have other blocks appended to it using the button appender 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:paragraph --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:paragraph --> <p>Group Block with a Paragraph</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; From 00c82b4809e9b8b9d8e90b7220ceb4c6371a0ff1 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell@kjellr.com> Date: Mon, 17 Jun 2019 15:08:13 -0400 Subject: [PATCH 353/664] Update group block snapshots. (#16202) --- .../__snapshots__/block-grouping.test.js.snap | 20 +++++++++---------- .../block-transforms.test.js.snap | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap index af571b2010ed25..e41b27cdf3aa1a 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap @@ -2,7 +2,7 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of different types via block transforms 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:heading --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:heading --> <h2>Group Heading</h2> <!-- /wp:heading --> @@ -12,13 +12,13 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of d <!-- wp:paragraph --> <p>Some paragraph</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; exports[`Block Grouping Group creation creates a group from multiple blocks of the same type via block transforms 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:paragraph --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:paragraph --> <p>First Paragraph</p> <!-- /wp:paragraph --> @@ -28,13 +28,13 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of t <!-- wp:paragraph --> <p>Third Paragraph</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; exports[`Block Grouping Group creation creates a group from multiple blocks of the same type via options toolbar 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:paragraph --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:paragraph --> <p>First Paragraph</p> <!-- /wp:paragraph --> @@ -44,13 +44,13 @@ exports[`Block Grouping Group creation creates a group from multiple blocks of t <!-- wp:paragraph --> <p>Third Paragraph</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; exports[`Block Grouping Group creation groups and ungroups multiple blocks of different types via options toolbar 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:heading --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:heading --> <h2>Group Heading</h2> <!-- /wp:heading --> @@ -60,7 +60,7 @@ exports[`Block Grouping Group creation groups and ungroups multiple blocks of di <!-- wp:paragraph --> <p>Some paragraph</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; @@ -80,7 +80,7 @@ exports[`Block Grouping Group creation groups and ungroups multiple blocks of di exports[`Block Grouping Preserving selected blocks attributes preserves width alignment settings of selected blocks 1`] = ` "<!-- wp:group {\\"align\\":\\"full\\"} --> -<div class=\\"wp-block-group alignfull\\"><!-- wp:heading --> +<div class=\\"wp-block-group alignfull\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:heading --> <h2>Group Heading</h2> <!-- /wp:heading --> @@ -94,6 +94,6 @@ exports[`Block Grouping Preserving selected blocks attributes preserves width al <!-- wp:paragraph --> <p>Some paragraph</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; diff --git a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap index 678dc348b496fd..f1d778e920aeba 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap @@ -98,9 +98,9 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image into the Group block 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:image --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:image --> <figure class=\\"wp-block-image\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure> -<!-- /wp:image --></div> +<!-- /wp:image --></div></div> <!-- /wp:group -->" `; @@ -362,9 +362,9 @@ exports[`Block transforms correctly transform block Media & Text in fixture core exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Group block 1`] = ` "<!-- wp:group --> -<div class=\\"wp-block-group\\"><!-- wp:paragraph {\\"align\\":\\"right\\"} --> +<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"right\\"} --> <p style=\\"text-align:right\\">... like this one, which is separate from the above and right aligned.</p> -<!-- /wp:paragraph --></div> +<!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; From 43730e88ac819ee1b500cc3ab1f22cb173d99a34 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 18 Jun 2019 10:22:45 +0100 Subject: [PATCH 354/664] Add the block registration RFC (#13693) * Add the block registration RFC * Update docs/rfc/block-registration.md Co-Authored-By: youknowriad <benguella@gmail.com> * Fix typo in WordPress name * Clarify some points in requirements * Update the note about REST API * Removed comments from json based examples * Fix icon examples * Add missing comma in JSON example * A few smaller tweaks * Fix to deprecated versions example * Update block-registration.md * Update block-registration.md * Update block-registration.md * Update block-registration.md * Update block-registration.md * Apply suggestions from code review Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Update docs/rfc/block-registration.md Co-Authored-By: gziolo <grzegorz@gziolo.pl> * Update block-registration.md * Add missing `parent` attribute * Update block-registration.md * Add backward compatibility section * Apply suggestions from code review * Align the name of style variations with the existing usage * Update RFC with all details about handling scripts and styles * Apply suggestions from code review Co-Authored-By: Jon Surrell <jon.surrell@automattic.com> Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> * Replace spaces with tabs for identation * Update docs/rfc/block-registration.md Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> * Remove debugging code from test plugin * Try simplify the way WPDefinedAsset is defined * Docs: Remove `render_callback` property from the initial version of RFC * Further simplify the definition of assets and clarify how it works with WordPress * Apply suggestions from code review Co-Authored-By: Jorge Bernal <jbernal@gmail.com> Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> * Remove the section about PHP runtime --- docs/rfc/block-registration.md | 439 +++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100644 docs/rfc/block-registration.md diff --git a/docs/rfc/block-registration.md b/docs/rfc/block-registration.md new file mode 100644 index 00000000000000..6c5f3ce2dfe0d9 --- /dev/null +++ b/docs/rfc/block-registration.md @@ -0,0 +1,439 @@ +# Block Type Registration RFC + +This RFC is intended to serve both as a specification and as documentation for the implementation of runtime-agnostic block type registration. + +## Requirements + +Behind any block type registration is some abstract concept of a unit of content. This content type can be described without consideration of any particular technology. In much the same way, we should be able to describe the core constructs of a block type in a way which can be interpreted in any runtime. + +In more practical terms, an implementation should fulfill requirements that... + +* A block type registration should be declarative and context-agnostic. Any runtime (PHP, JS, or other) should be able to interpret the basics of a block type (see "Block API" in the sections below) and should be able to fetch or retrieve the definitions of the context-specific implementation details. The following things should be made possible: + * Fetching the available block types through REST APIs. + * Fetching block objects from posts through REST APIs. +* This API should be backward compatible with what we have at the moment. +* It should be possible to statically analyze a block type in order to support advanced use-cases required by one of the [9 projects](https://make.wordpress.org/core/2018/12/08/9-priorities-for-2019/) for 2019 in WordPress: "Building a WordPress.org directory for discovering blocks, and a way to seamlessly install them.". The block directory should not need to parse JavaScript or PHP files to retrieve their definitions similar to how it happens for plugins as of today. + +It can statically analyze the files of any plugin to retrieve blocks and their properties. +* It should not require a build tool compilation step (e.g. Babel, Webpack) to author code which would be referenced in a block type definition. +* There should allow the potential to dynamically load ("lazy-load") block types, or parts of block type definitions. It practical terms, it means that the editor should be able to be loaded without enqueuing all the assets (scripts and styles) of all block types. What it needs is the basic metadata (`title`, `description`, `category`, `icon`, etc...) to start with. It should be fine to defer loading all other code (`edit`, `save`, `transforms`, and other JavaScript implementations) until it is explicitly used (inserted into the post content). + +## References + +* Issue: [Block API: Server-side awareness of block types](https://github.com/WordPress/gutenberg/issues/2751) +* Follow-up issue: [Expose available blocks via an API](https://github.com/WordPress/gutenberg/issues/4116) +* Current documentation: [/docs/designers-developers/developers/block-api/block-registration.md](/docs/designers-developers/developers/block-api/block-registration.md) +* Make WordPress.org post: [The Block Directory, and a new type of plugin](https://make.wordpress.org/meta/2019/03/08/the-block-directory-and-a-new-type-of-plugin/) + + +## Previous attempts + +Initial support for server-defined block attributes was merged as part of [#2529](https://github.com/WordPress/gutenberg/pull/2529). PHP block type registrations are merged with those defined in the JavaScript runtime. While this enabled blocks to be defined within PHP, the majority of block types continue to be defined within JavaScript alone. The support was reserved for the exclusive use of [dynamic block types](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md), in large part because [`edit` and `save` behaviors](/docs/designers-developers/developers/block-api/block-edit-save.md) must still be implemented in JavaScript, and because a solution hadn't been considered for how to create individual block bundles during the build process, nor how to load such bundles efficiently if it were to come to be implemented. + +A demonstration for how block registration could be made filterable in PHP was explored in [#5802](https://github.com/WordPress/gutenberg/pull/5802). The purpose here was to explore how plugins could have better control over the registration. + +Another exploration in [#5652](https://github.com/WordPress/gutenberg/pull/5652) considered using JSON as a file format to share block type definitions between JavaScript and PHP. + +### Conclusions + +* The current approaches to client-side block type registration cannot support the proposed requirement to have all block types known outside the browser context. +* Using a statically-defined, JSON-formatted block type definition enables easy integration in both JavaScript and PHP runtimes. +* Registering a block type in PHP would allow for attribute default values to be assigned as dynamically generated from some external state (e.g. a database value, or localized string). +* By default, JSON does not support localization or dynamic values. +* On the server, a block type `icon` property can only be assigned as a string and thus cannot support SVGs and component-based icons. + +--- + +## Introduction + +Blocks are the fundamental elements of the editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor. + +## Registering a block type + +To register a new block type, start by creating a `block.json` file. This file: + +* Gives a name to the block type. +* Defines some important metadata about the registered block type (title, category, icon, description, keywords). +* Defines the attributes of the block type. +* Registers all the scripts and styles for your block type. + +**Example:** + + +```json +{ + "name": "my-plugin/notice", + "title": "Notice", + "category": "common", + "parent": [ "core/group" ], + "icon": "star", + "description": "Shows warning, error or success notices ...", + "keywords": [ "alert", "message" ], + "textDomain": "my-plugin", + "attributes": { + "message": { + "type": "string", + "source": "html", + "selector": ".message" + } + }, + "styleVariations": [ + { "name": "default", "label": "Default", "isDefault": true }, + { "name": "other", "label": "Other" } + ], + "editorScript": "build/editor.js", + "script": "build/main.js", + "editorStyle": "build/editor.css", + "style": "build/style.css" +} +``` + +## Block API + +This section describes all the properties that can be added to the `block.json` file to define the behavior and metadata of block types. + +### Name + +* Type: `string` +* Required +* Localized: No +* Property: `name` + +```json +{ "name": "core/heading" } +``` + +The name for a block is a unique string that identifies a block. Names have to be structured as `namespace/block-name`, where namespace is the name of your plugin or theme. + +**Note:** A block name can only contain lowercase alphanumeric characters, dashes, and at most one forward slash to designate the plugin-unique namespace prefix. It must begin with a letter. + +**Note:** This name is used on the comment delimiters as `<!-- wp:my-plugin/book -->`. Block types in the `core` namespace do not include a namespace when serialized. + +### Title + +* Type: `string` +* Required +* Localized: Yes +* Property: `title` + +```json +{ "title": "Heading" } +``` + +This is the display title for your block, which can be translated with our translation functions. The block inserter will show this name. + +### Category + +* Type: `string` +* Required +* Localized: No +* Property: `category` + +```json +{ "category": "common" } +``` + +Blocks are grouped into categories to help users browse and discover them. + +The core provided categories are: + +* common +* formatting +* layout +* widgets +* embed + +Plugins and Themes can also register [custom block categories](/docs/designers-developers/developers/filters/block-filters.md#managing-block-categories). + +An implementation should expect and tolerate unknown categories, providing some reasonable fallback behavior (e.g. a "common" category). + +### Parent + +* Type: `string[]` +* Optional +* Localized: No +* Property: `parent` + +```json +{ "parent": [ "my-block/product" ] } +``` + +Setting `parent` lets a block require that it is only available when nested within the specified blocks. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. + +### Icon + +* Type: `string` +* Optional +* Localized: No +* Property: `icon` + +```json +{ "icon": "smile" } +``` + +An icon property should be specified to make it easier to identify a block. These can be any of WordPress' Dashicons (slug serving also as a fallback in non-js contexts). + +**Note:** It's also possible to override this property on the client-side with the source of the SVG element. In addition, this property can be defined with JavaScript as an object containing background and foreground colors. This colors will appear with the icon when they are applicable e.g.: in the inserter. Custom SVG icons are automatically wrapped in the [wp.components.SVG](/packages/components/src/primitives/svg/README.md) component to add accessibility attributes (aria-hidden, role, and focusable). + +### Description + +* Type: `string` +* Optional +* Localized: Yes +* Property: `description` + +```json + { "description": "Introduce new sections and organize content to help visitors" } +``` + +This is a short description for your block, which can be translated with our translation functions. This will be shown in the block inspector. + +### Keywords + +* Type: `string[]` +* Optional +* Localized: Yes +* Property: `keywords` + +```json +{ "keywords": [ "keyword1", "keyword2" ] } +``` + +Sometimes a block could have aliases that help users discover it while searching. For example, an image block could also want to be discovered by photo. You can do so by providing an array of unlimited terms (which are translated). + +### Text Domain + +* Type: `string` +* Optional +* Localized: No +* Property: `textDomain` + +```json +{ "textDomain": "my-plugin" } +``` + +The [gettext](https://www.gnu.org/software/gettext/) text domain of the plugin/block. More information can be found in the [Text Domain](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#text-domains) section of the [How to Internationalize your Plugin](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/) page. + +### Attributes + +* Type: `object` +* Optional +* Localized: No +* Property: `attributes` + +```json +{ + "attributes": { + "cover": { + "type": "string", + "source": "attribute", + "selector": "img", + "attribute": "src" + }, + "author": { + "type": "string", + "source": "html", + "selector": ".book-author" + } + } +} +``` + +Attributes provide the structured data needs of a block. They can exist in different forms when they are serialized, but they are declared together under a common interface. + +See the [the attributes documentation](/docs/designers-developers/developers/block-api/block-attributes.md) for more details. + +### Style Variations + +* Type: `array` +* Optional +* Localized: Yes (`label`) +* Property: `styles` +* Alias: `styleVariations` + +```json +{ + "styleVariations": [ + { "name": "default", "label": "Default", "isDefault": true }, + { "name": "other", "label": "Other" } + ] +} +``` + +Block styles can be used to provide alternative styles to block. It works by adding a class name to the block's wrapper. Using CSS, a theme developer can target the class name for the style variation if it is selected. + +Plugins and Themes can also register [custom block style](/docs/designers-developers/developers/filters/block-filters.md#block-style-variations) for existing blocks. + +### Editor Script + +* Type: `string` ([WPDefinedAsset](#WPDefinedAsset)) +* Optional +* Localized: No +* Property: `editorScript` + +```json +{ "editorScript": "build/editor.js" } +``` + +Block type editor script definition. It will only be enqueued in the context of the editor. + +### Script + +* Type: `string` ([WPDefinedAsset](#WPDefinedAsset)) +* Optional +* Localized: No +* Property: `script` + +```json +{ "script": "build/main.js" } +``` + +Block type frontend script definition. It will be enqueued both in the editor and when viewing the content on the front of the site. + +### Editor Style + +* Type: `string` ([WPDefinedAsset](#WPDefinedAsset)) +* Optional +* Localized: No +* Property: `editorStyle` + +```json +{ "editorStyle": "build/editor.css" } +``` + +Block type editor style definition. It will only be enqueued in the context of the editor. + +### Style + +* Type: `string` ([WPDefinedAsset](#WPDefinedAsset)) +* Optional +* Localized: No +* Property: `style` + +```json +{ "style": "build/style.css" } +``` + +Block type frontend style definition. It will be enqueued both in the editor and when viewing the content on the front of the site. + +## Backward compatibility + +The following properties are going to be supported for backward compatibility reasons on the client-side only. Some of them might be replaced with alternative APIs in the future: + - `edit` - see the [Edit and Save](/docs/designers-developers/developers/block-api/block-edit-save.md) documentation for more details. + - `save` - see the [Edit and Save](/docs/designers-developers/developers/block-api/block-edit-save.md) documentation for more details. + - `transforms` - see the [Transforms](/docs/designers-developers/developers/block-api/block-registration.md#transforms-optional) documentation for more details. + - `deprecated` - see the [Deprecated Blocks](/docs/designers-developers/developers/block-api/block-deprecation.md) documentation for more details. + - `supports` - see the [block supports](/docs/designers-developers/developers/block-api/block-registration.md#supports-optional) documentation page for more details. + - `merge` - undocumented as of today. Its role is to handle merging multiple blocks into one. + - `getEditWrapperProps` - undocumented as well. Its role is to inject additional props to the block edit's component wrapper. + +**Example**: +```js +wp.blocks.registerBlockType( 'my-block/name', { + edit: function() { + // Edit definition goes here. + }, + save: function() { + // Save definition goes here. + }, + supports: { + html: false + } +} ); +``` + +In the case of [dynamic blocks](/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md) supported by WordPress, it should be still possible to register `render_callback` property using [`register_block_type`](https://developer.wordpress.org/reference/functions/register_block_type/) function on the server. + +## Assets + +### `WPDefinedAsset` + +The `WPDefinedAsset` type is a subtype of string, where the value must represent an absolute or relative path to a JavaScript or CSS file. + +**Example:** + +In `block.json`: +```json +{ "editorScript": "build/editor.js" } +``` + +#### WordPress context + +In the context of WordPress, when a block is registered with PHP, it will automatically register all scripts and styles that are found in the `block.json` file. + +That's why, the `WPDefinedAsset` type has to offer a way to mirror also the shape of params necessary to register scripts and styles using [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/) and [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/), and then assign these as handles associated with your block using the `script`, `style`, `editor_script`, and `editor_style` block type registration settings. + +It's possible to provide an object which takes the following shape: +- `handle` (`string`) - the name of the script. If omitted, it will be auto-generated. +- `dependencies` (`string[]`) - an array of registered script handles this script depends on. Default value: `[]`. +- `version` (`string`|`false`|`null`) - string specifying the script version number, if it has one, which is added to the URL as a query string for cache busting purposes. If the version is set to `false`, a version number is automatically added equal to current installed WordPress version. If set to `null`, no version is added. Default value: `false`. + +The definition is stored inside separate JSON file which ends with `.asset.json` and is located next to the JS/CSS file listed in `block.json`. WordPress will automatically detect this file through pattern matching. This option is the preferred one as it is expected it will become an option to auto-generate those asset files with `@wordpress/scripts` package. + +**Example:** + +``` +build/ +├─ editor.js +└─ editor.asset.json +``` + +In `block.json`: +```json +{ "editorScript": "build/editor.js" } +``` + +In `build/editor.asset.json`: +```json +{ + "handle": "my-plugin-notice-editor", + "dependencies": [ "wp-blocks","wp-element", "wp-i18n" ], + "version": "3.0.0" +} +``` + +## Internationalization + +Localized properties are automatically wrapped in `_x` function calls on the backend and the frontend of WordPress. These translations are added as an inline script to the `wp-block-library` script handle in WordPress core or to the plugin's script handle when it defines metadata definition. + +WordPress string discovery automatically translates these strings using the `textDomain` property specified in the `block.json` file. + +**Example:** + +```json +{ + "title": "My block", + "description": "My block is fantastic", + "keywords": [ "fantastic" ], + "textDomain": "my-plugin" +} +``` + +In JavaScript, with the help of a Babel plugin, this becomes: + +```js +const metadata = { + title: _x( 'My block', 'block title', 'my-plugin' ), + description: _x( 'My block is fantastic', 'block description', 'my-plugin' ), + keywords: [ _x( 'fantastic', 'block keywords', 'my-plugin' ) ], +} +``` + +In PHP, it is transformed at runtime to code roughly equivalent to: + +```php +<?php +$metadata = array( + 'title' => _x( 'My block', 'block title', 'my-plugin' ), + 'description' => _x( 'My block is fantastic!', 'block description', 'my-plugin' ), + 'keywords' => array( _x( 'fantastic', 'block keywords', 'my-plugin' ) ), +); +``` + +Implementation should follow the existing [get_plugin_data](https://codex.wordpress.org/Function_Reference/get_plugin_data) function which parses the plugin contents to retrieve the plugin’s metadata, and it applies translations dynamically. + +## Backward Compatibility + +The existing registration mechanism (both server side and frontend) will continue to work, it will serve as low-level implementation detail for the `block.json` based registration. + +Core Blocks will be migrated iteratively and third-party blocks will see warnings appearing in the console to encourage them to refactor the block registration API used. From 8f8776f98f882d9bd1ffd03e465310d99406a63e Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Tue, 18 Jun 2019 15:08:35 +0200 Subject: [PATCH 355/664] Add native support for BlockToolbar to @wordpress/block-editor (Ported from gutenberg mobile) (#16213) --- .../components/block-toolbar/index.native.js | 105 ++++++++++++++++++ .../block-toolbar/style.native.scss | 27 +++++ .../src/components/index.native.js | 1 + 3 files changed, 133 insertions(+) create mode 100644 packages/block-editor/src/components/block-toolbar/index.native.js create mode 100644 packages/block-editor/src/components/block-toolbar/style.native.scss diff --git a/packages/block-editor/src/components/block-toolbar/index.native.js b/packages/block-editor/src/components/block-toolbar/index.native.js new file mode 100644 index 00000000000000..3d12312673bf87 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/index.native.js @@ -0,0 +1,105 @@ +/** + * External dependencies + */ +import { View, ScrollView, Keyboard, Platform } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { Toolbar, ToolbarButton, Dashicon } from '@wordpress/components'; +import { BlockFormatControls, BlockControls } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +export class BlockToolbar extends Component { + constructor() { + super( ...arguments ); + + this.onKeyboardHide = this.onKeyboardHide.bind( this ); + } + + onKeyboardHide() { + this.props.clearSelectedBlock(); + if ( Platform.OS === 'android' ) { + // Avoiding extra blur calls on iOS but still needed for android. + Keyboard.dismiss(); + } + } + + render() { + const { + hasRedo, + hasUndo, + redo, + undo, + onInsertClick, + showKeyboardHideButton, + } = this.props; + + return ( + <View style={ styles.container } > + <ScrollView + horizontal={ true } + showsHorizontalScrollIndicator={ false } + keyboardShouldPersistTaps="always" + alwaysBounceHorizontal={ false } + contentContainerStyle={ styles.scrollableContent } + > + <Toolbar accessible={ false }> + <ToolbarButton + title={ __( 'Add block' ) } + icon={ ( <Dashicon icon="plus-alt" style={ styles.addBlockButton } color={ styles.addBlockButton.color } /> ) } + onClick={ onInsertClick } + extraProps={ { hint: __( 'Double tap to add a block' ) } } + /> + <ToolbarButton + title={ __( 'Undo' ) } + icon="undo" + isDisabled={ ! hasUndo } + onClick={ undo } + extraProps={ { hint: __( 'Double tap to undo last change' ) } } + /> + <ToolbarButton + title={ __( 'Redo' ) } + icon="redo" + isDisabled={ ! hasRedo } + onClick={ redo } + extraProps={ { hint: __( 'Double tap to redo last change' ) } } + /> + </Toolbar> + <BlockControls.Slot /> + <BlockFormatControls.Slot /> + </ScrollView> + { showKeyboardHideButton && + <Toolbar passedStyle={ styles.keyboardHideContainer }> + <ToolbarButton + title={ __( 'Hide keyboard' ) } + icon="keyboard-hide" + onClick={ this.onKeyboardHide } + extraProps={ { hint: __( 'Tap to hide the keyboard' ) } } + /> + </Toolbar> + } + </View> + ); + } +} + +export default compose( [ + withSelect( ( select ) => ( { + hasRedo: select( 'core/editor' ).hasEditorRedo(), + hasUndo: select( 'core/editor' ).hasEditorUndo(), + } ) ), + withDispatch( ( dispatch ) => ( { + redo: dispatch( 'core/editor' ).redo, + undo: dispatch( 'core/editor' ).undo, + clearSelectedBlock: dispatch( 'core/editor' ).clearSelectedBlock, + } ) ), +] )( BlockToolbar ); diff --git a/packages/block-editor/src/components/block-toolbar/style.native.scss b/packages/block-editor/src/components/block-toolbar/style.native.scss new file mode 100644 index 00000000000000..43e996cd8e0755 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/style.native.scss @@ -0,0 +1,27 @@ +.container { + height: 44px; + flex-direction: row; + background-color: #fff; + border-top-color: #e9eff3; + border-top-width: 1px; +} + +.scrollableContent { + flex-grow: 1; // Fixes RTL issue on Android. +} + +.keyboardHideContainer { + padding-right: 0; + padding-left: 0; + padding-top: 4px; + width: 44px; + justify-content: center; + align-items: center; +} + +.addBlockButton { + color: $blue-wordpress; + border: 2px; + border-radius: 10px; + border-color: $blue-wordpress; +} diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 5b3249e1f1a8a9..b0e5f34e3e76d6 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -19,6 +19,7 @@ export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; // Content Related Components +export { default as BlockToolbar } from './block-toolbar'; export { default as DefaultBlockAppender } from './default-block-appender'; export { default as Inserter } from './inserter'; From 3c153f14ce43553cb012040dba41a365ea75e3ca Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 18 Jun 2019 14:32:00 +0100 Subject: [PATCH 356/664] Add popoverProps property to DropDown component (#14867) --- packages/components/CHANGELOG.md | 7 +++++++ packages/components/src/dropdown/README.md | 8 ++++++++ packages/components/src/dropdown/index.js | 2 ++ 3 files changed, 17 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 38e44d1c10cfba..b54dcec4b412bc 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,10 @@ +## Next release + +### New Features + +- Added a new `popoverProps` prop to the `Dropdown` component which allows users of the `Dropdown` component to pass props directly to the `PopOver` component. + + ## 8.0.0 (2019-06-12) ### New Feature diff --git a/packages/components/src/dropdown/README.md b/packages/components/src/dropdown/README.md index 0e09a6651dcd92..31f5de665bea34 100644 --- a/packages/components/src/dropdown/README.md +++ b/packages/components/src/dropdown/README.md @@ -100,3 +100,11 @@ Opt-in prop to show popovers fullscreen on mobile, pass `false` in this prop to - Type: `String` or `Boolean` - Required: No - Default: `"firstElement"` + + ### popoverProps + +Properties of popoverProps object will be passed as props to the `Popover` component. +Use this o object to access properties/feature if the `Popover` component that are not already exposed in the `Dropdown`component, e.g.: the hability to have the popover without an arrow. + + - Type: `Object` + - Required: No diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index b4d9082af3cdc0..bff463f6655f9c 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -74,6 +74,7 @@ class Dropdown extends Component { expandOnMobile, headerTitle, focusOnMount, + popoverProps, } = this.props; const args = { isOpen, onToggle: this.toggle, onClose: this.close }; @@ -90,6 +91,7 @@ class Dropdown extends Component { expandOnMobile={ expandOnMobile } headerTitle={ headerTitle } focusOnMount={ focusOnMount } + { ...popoverProps } > { renderContent( args ) } </Popover> From 4d6a2a51ea084c92a9043cc9d93138ffa0f9bc6b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 18 Jun 2019 14:32:59 +0100 Subject: [PATCH 357/664] Add navigation menu blocks (#14856) * Add popoverProps property to DropDown component * Add navigation menu blocks --- packages/block-library/src/editor.scss | 2 + packages/block-library/src/index.js | 4 + .../src/navigation-menu-item/block.json | 26 ++++ .../src/navigation-menu-item/edit.js | 145 ++++++++++++++++++ .../src/navigation-menu-item/editor.scss | 72 +++++++++ .../src/navigation-menu-item/index.js | 28 ++++ .../navigation-menu-item/menu-item-actions.js | 111 ++++++++++++++ .../src/navigation-menu-item/save.js | 12 ++ .../src/navigation-menu/block.json | 10 ++ .../block-library/src/navigation-menu/edit.js | 58 +++++++ .../src/navigation-menu/editor.scss | 23 +++ .../src/navigation-menu/index.js | 36 +++++ .../src/navigation-menu/menu-item-inserter.js | 85 ++++++++++ .../block-library/src/navigation-menu/save.js | 12 ++ .../e2e-tests/fixtures/block-transforms.js | 12 ++ .../blocks/core__navigation-menu-item.html | 3 + .../blocks/core__navigation-menu-item.json | 15 ++ .../core__navigation-menu-item.parsed.json | 23 +++ ...core__navigation-menu-item.serialized.html | 3 + .../blocks/core__navigation-menu.html | 7 + .../blocks/core__navigation-menu.json | 26 ++++ .../blocks/core__navigation-menu.parsed.json | 35 +++++ .../core__navigation-menu.serialized.html | 5 + 23 files changed, 753 insertions(+) create mode 100644 packages/block-library/src/navigation-menu-item/block.json create mode 100644 packages/block-library/src/navigation-menu-item/edit.js create mode 100644 packages/block-library/src/navigation-menu-item/editor.scss create mode 100644 packages/block-library/src/navigation-menu-item/index.js create mode 100644 packages/block-library/src/navigation-menu-item/menu-item-actions.js create mode 100644 packages/block-library/src/navigation-menu-item/save.js create mode 100644 packages/block-library/src/navigation-menu/block.json create mode 100644 packages/block-library/src/navigation-menu/edit.js create mode 100644 packages/block-library/src/navigation-menu/editor.scss create mode 100644 packages/block-library/src/navigation-menu/index.js create mode 100644 packages/block-library/src/navigation-menu/menu-item-inserter.js create mode 100644 packages/block-library/src/navigation-menu/save.js create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 99493394ed43f4..784850095f97d2 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -18,6 +18,8 @@ @import "./media-text/editor.scss"; @import "./list/editor.scss"; @import "./more/editor.scss"; +@import "./navigation-menu/editor.scss"; +@import "./navigation-menu-item/editor.scss"; @import "./nextpage/editor.scss"; @import "./paragraph/editor.scss"; @import "./preformatted/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 38e6b272309625..9724f9d9293e33 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -33,6 +33,8 @@ import * as embed from './embed'; import * as file from './file'; import * as html from './html'; import * as mediaText from './media-text'; +import * as navigationMenu from './navigation-menu'; +import * as navigationMenuItem from './navigation-menu-item'; import * as latestComments from './latest-comments'; import * as latestPosts from './latest-posts'; import * as legacyWidget from './legacy-widget'; @@ -104,6 +106,8 @@ export const registerCoreBlocks = () => { process.env.GUTENBERG_PHASE === 2 ? legacyWidget : null, missing, more, + process.env.GUTENBERG_PHASE === 2 ? navigationMenu : null, + process.env.GUTENBERG_PHASE === 2 ? navigationMenuItem : null, nextpage, preformatted, pullquote, diff --git a/packages/block-library/src/navigation-menu-item/block.json b/packages/block-library/src/navigation-menu-item/block.json new file mode 100644 index 00000000000000..915c63931cb2be --- /dev/null +++ b/packages/block-library/src/navigation-menu-item/block.json @@ -0,0 +1,26 @@ +{ + "name": "core/navigation-menu-item", + "category": "layout", + "attributes": { + "label": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "nofollow": { + "type": "boolean", + "default": false + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "opensInNewTab": { + "type": "boolean", + "default": false + } + } +} diff --git a/packages/block-library/src/navigation-menu-item/edit.js b/packages/block-library/src/navigation-menu-item/edit.js new file mode 100644 index 00000000000000..ce5db4b33d0ce1 --- /dev/null +++ b/packages/block-library/src/navigation-menu-item/edit.js @@ -0,0 +1,145 @@ +/** + * External dependencies + */ +import { invoke } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + Dropdown, + ExternalLink, + IconButton, + PanelBody, + TextareaControl, + TextControl, + ToggleControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + InspectorControls, + PlainText, +} from '@wordpress/block-editor'; +import { + Fragment, + useCallback, + useRef, +} from '@wordpress/element'; + +/** + * Internal dependencies + */ +import MenuItemActions from './menu-item-actions'; +const POPOVER_PROPS = { noArrow: true }; + +function NavigationMenuItemEdit( { + attributes, + clientId, + isSelected, + setAttributes, +} ) { + const plainTextRef = useRef( null ); + const onEditLableClicked = useCallback( + ( onClose ) => () => { + onClose(); + invoke( plainTextRef, [ 'current', 'textarea', 'focus' ] ); + }, + [ plainTextRef ] + ); + let content; + if ( isSelected ) { + content = ( + <div className="wp-block-navigation-menu-item__edit-container"> + <PlainText + ref={ plainTextRef } + className="wp-block-navigation-menu-item__field" + value={ attributes.label } + onChange={ ( label ) => setAttributes( { label } ) } + aria-label={ __( 'Navigation Label' ) } + maxRows={ 1 } + /> + <Dropdown + contentClassName="wp-block-navigation-menu-item__dropdown-content" + position="bottom left" + popoverProps={ POPOVER_PROPS } + renderToggle={ ( { isOpen, onToggle } ) => ( + <IconButton + icon={ isOpen ? 'arrow-up-alt2' : 'arrow-down-alt2' } + label={ __( 'More options' ) } + onClick={ onToggle } + aria-expanded={ isOpen } + /> + ) } + renderContent={ ( { onClose } ) => ( + <MenuItemActions + clientId={ clientId } + destination={ attributes.destination } + onEditLableClicked={ onEditLableClicked( onClose ) } + /> + ) } + /> + </div> + ); + } else { + content = attributes.label; + } + return ( + <Fragment> + <InspectorControls> + <PanelBody + title={ __( 'Menu Settings' ) } + > + <ToggleControl + checked={ attributes.opensInNewTab } + onChange={ ( opensInNewTab ) => { + setAttributes( { opensInNewTab } ); + } } + label={ __( 'Open in new tab' ) } + /> + <TextareaControl + value={ attributes.description || '' } + onChange={ ( description ) => { + setAttributes( { description } ); + } } + label={ __( 'Description' ) } + /> + </PanelBody> + <PanelBody + title={ __( 'SEO Settings' ) } + > + <TextControl + value={ attributes.title || '' } + onChange={ ( title ) => { + setAttributes( { title } ); + } } + label={ __( 'Title Attribute' ) } + help={ __( 'Provide more context about where the link goes.' ) } + /> + <ToggleControl + checked={ attributes.nofollow } + onChange={ ( nofollow ) => { + setAttributes( { nofollow } ); + } } + label={ __( 'Add nofollow to menu item' ) } + help={ ( + <Fragment> + { __( 'Don\'t let search engines follow this link.' ) } + <ExternalLink + className="wp-block-navigation-menu-item__nofollow-external-link" + href={ __( 'https://codex.wordpress.org/Nofollow' ) } + > + { __( 'What\'s this?' ) } + </ExternalLink> + </Fragment> + ) } + /> + </PanelBody> + </InspectorControls> + <div className="wp-block-navigation-menu-item"> + { content } + </div> + </Fragment> + ); +} + +export default NavigationMenuItemEdit; diff --git a/packages/block-library/src/navigation-menu-item/editor.scss b/packages/block-library/src/navigation-menu-item/editor.scss new file mode 100644 index 00000000000000..51bd5b7ceae45c --- /dev/null +++ b/packages/block-library/src/navigation-menu-item/editor.scss @@ -0,0 +1,72 @@ +.wp-block-navigation-menu-item__edit-container { + display: grid; + grid-auto-columns: min-content; + grid-auto-flow: column; + align-items: center; + white-space: nowrap; +} + +$menu-label-field-width: 140px; +.wp-block-navigation-menu-item__edit-container { + border: 1px solid $light-gray-500; + // two pixes comes from two times one pixel border + width: $menu-label-field-width + $icon-button-size + 2px; + padding-left: 1px; +} + +.wp-block-navigation-menu-item__edit-container .wp-block-navigation-menu-item__field { + border-right: 1px solid $light-gray-500 !important; + width: $menu-label-field-width; + border: none; + border-radius: 0; + padding-left: $grid-size-large; + + min-height: $icon-button-size - 1px; + line-height: $icon-button-size - 1px; + + &, + &:focus { + color: $dark-gray-500; + } +} + + +.wp-block-navigation-menu-item { + font-family: $editor-font; + color: #0073af; + font-weight: bold; + font-size: $text-editor-font-size; +} + +.wp-block-navigation-menu-item__nofollow-external-link { + display: block; +} + +// Separator +.wp-block-navigation-menu-item__separator { + margin-top: $grid-size; + margin-bottom: $grid-size; + margin-left: 0; + margin-right: 0; + border-top: $border-width solid $light-gray-500; +} + +// Popover styles +.components-popover:not(.is-mobile).wp-block-navigation-menu-item__dropdown-content { + margin-top: -1px; + margin-left: -4px; +} + +.wp-block-navigation-menu-item__dropdown-content .components-popover__content { + padding: $grid-size 0; +} + +.wp-block-navigation-menu .block-editor-block-list__block[data-type="core/navigation-menu-item"] { + & > .block-editor-block-list__block-edit > div[role="toolbar"] { + display: none; + } + + & > .block-editor-block-list__insertion-point { + display: none; + } +} diff --git a/packages/block-library/src/navigation-menu-item/index.js b/packages/block-library/src/navigation-menu-item/index.js new file mode 100644 index 00000000000000..496ef92070428e --- /dev/null +++ b/packages/block-library/src/navigation-menu-item/index.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import save from './save'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Menu Item (Experimental)' ), + + parent: [ 'core/navigation-menu' ], + + icon: 'admin-links', + + description: __( 'Add a page, link, or other item to your Navigation Menu.' ), + + edit, + save, +}; + diff --git a/packages/block-library/src/navigation-menu-item/menu-item-actions.js b/packages/block-library/src/navigation-menu-item/menu-item-actions.js new file mode 100644 index 00000000000000..6e39036bab408a --- /dev/null +++ b/packages/block-library/src/navigation-menu-item/menu-item-actions.js @@ -0,0 +1,111 @@ +/** + * WordPress dependencies + */ +import { + MenuItem, + NavigableMenu, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +function MenuItemActions( { + destination, + moveLeft, + moveRight, + moveToEnd, + moveToStart, + onEditLableClicked, + remove, +} ) { + return ( + <NavigableMenu> + <MenuItem + icon="admin-links" + > + { destination } + </MenuItem> + <MenuItem + onClick={ onEditLableClicked } + icon="edit" + > + { __( 'Edit label text' ) } + </MenuItem> + <div className="wp-block-navigation-menu-item__separator" /> + <MenuItem + onClick={ moveToStart } + icon="arrow-up-alt2" + > + { __( 'Move to start' ) } + </MenuItem> + <MenuItem + onClick={ moveLeft } + icon="arrow-left-alt2" + > + { __( 'Move left' ) } + </MenuItem> + <MenuItem + onClick={ moveRight } + icon="arrow-right-alt2" + > + { __( 'Move right' ) } + </MenuItem> + <MenuItem + onClick={ moveToEnd } + icon="arrow-down-alt2" + > + { __( 'Move to end' ) } + </MenuItem> + <MenuItem + icon="arrow-left-alt2" + > + { __( 'Nest underneath…' ) } + </MenuItem> + <div className="navigation-menu-item__separator" /> + <MenuItem + onClick={ remove } + icon="trash" + > + { __( 'Remove from menu' ) } + </MenuItem> + </NavigableMenu> + ); +} + +export default compose( [ + withDispatch( ( dispatch, { clientId }, { select } ) => { + const { + getBlockOrder, + getBlockRootClientId, + } = select( 'core/block-editor' ); + const parentID = getBlockRootClientId( clientId ); + const { + moveBlocksDown, + moveBlocksUp, + moveBlockToPosition, + removeBlocks, + } = dispatch( 'core/block-editor' ); + return { + moveToStart() { + moveBlockToPosition( clientId, parentID, parentID, 0 ); + }, + moveRight() { + moveBlocksDown( clientId, parentID ); + }, + moveLeft() { + moveBlocksUp( clientId, parentID ); + }, + moveToEnd() { + moveBlockToPosition( + clientId, + parentID, + parentID, + getBlockOrder( parentID ).length - 1 + ); + }, + remove() { + removeBlocks( clientId ); + }, + }; + } ), +] )( MenuItemActions ); diff --git a/packages/block-library/src/navigation-menu-item/save.js b/packages/block-library/src/navigation-menu-item/save.js new file mode 100644 index 00000000000000..66649929c4ac30 --- /dev/null +++ b/packages/block-library/src/navigation-menu-item/save.js @@ -0,0 +1,12 @@ +export default function save( { attributes } ) { + return ( + <a + href={ attributes.destination } + rel={ attributes.nofollow && 'nofollow' } + title={ attributes.title } + target={ attributes.opensInNewTab && '_blank' } + > + { attributes.label } + </a> + ); +} diff --git a/packages/block-library/src/navigation-menu/block.json b/packages/block-library/src/navigation-menu/block.json new file mode 100644 index 00000000000000..ee204cfeb890d4 --- /dev/null +++ b/packages/block-library/src/navigation-menu/block.json @@ -0,0 +1,10 @@ +{ + "name": "core/navigation-menu", + "category": "layout", + "attributes": { + "automaticallyAdd": { + "type": "boolean", + "default": false + } + } +} diff --git a/packages/block-library/src/navigation-menu/edit.js b/packages/block-library/src/navigation-menu/edit.js new file mode 100644 index 00000000000000..0818144c71ea17 --- /dev/null +++ b/packages/block-library/src/navigation-menu/edit.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { + Fragment, +} from '@wordpress/element'; +import { + InnerBlocks, + InspectorControls, +} from '@wordpress/block-editor'; +import { + CheckboxControl, + PanelBody, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import MenuItemInserter from './menu-item-inserter'; +import { __ } from '@wordpress/i18n'; + +function NavigationMenu( { + attributes, + clientId, + isSelected, + setAttributes, +} ) { + return ( + <Fragment> + <InspectorControls> + <PanelBody + title={ __( 'Menu Settings' ) } + > + <CheckboxControl + value={ attributes.automaticallyAdd } + onChange={ ( automaticallyAdd ) => { + setAttributes( { automaticallyAdd } ); + } } + label={ __( 'Automatically add new pages' ) } + help={ __( 'Automatically add new top level pages to this menu.' ) } + /> + </PanelBody> + </InspectorControls> + <div className="wp-block-navigation-menu"> + <InnerBlocks + allowedBlocks={ [ 'core/navigation-menu-item' ] } + /> + { isSelected && ( + <MenuItemInserter + rootClientId={ clientId } + /> + ) } + </div> + </Fragment> + ); +} + +export default NavigationMenu; diff --git a/packages/block-library/src/navigation-menu/editor.scss b/packages/block-library/src/navigation-menu/editor.scss new file mode 100644 index 00000000000000..e52ad5b6ec2833 --- /dev/null +++ b/packages/block-library/src/navigation-menu/editor.scss @@ -0,0 +1,23 @@ +.wp-block-navigation-menu .block-editor-block-list__layout, +.wp-block-navigation-menu { + display: grid; + grid-auto-columns: min-content; + grid-auto-flow: column; + align-items: center; + white-space: nowrap; +} + +// Todo: corerctly allow custom inserters and remove the need for this display none. +.wp-block-navigation-menu .block-editor-block-list__layout .block-list-appender { + display: none; +} + +// Custom inserter +.wp-block-navigation-menu__inserter { + display: inline-block; +} + +.wp-block-navigation-menu__inserter-content { + width: 350px; + padding: $grid-size-large; +} diff --git a/packages/block-library/src/navigation-menu/index.js b/packages/block-library/src/navigation-menu/index.js new file mode 100644 index 00000000000000..8ca8e0c3fe560d --- /dev/null +++ b/packages/block-library/src/navigation-menu/index.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import save from './save'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Navigation Menu (Experimental)' ), + + icon: 'menu', + + description: __( 'Add a navigation menu to your site.' ), + + keywords: [ __( 'menu' ), __( 'navigation' ), __( 'links' ) ], + + supports: { + align: [ 'wide', 'full' ], + anchor: true, + html: false, + inserter: false, + }, + + edit, + + save, + +}; diff --git a/packages/block-library/src/navigation-menu/menu-item-inserter.js b/packages/block-library/src/navigation-menu/menu-item-inserter.js new file mode 100644 index 00000000000000..34a506e448e692 --- /dev/null +++ b/packages/block-library/src/navigation-menu/menu-item-inserter.js @@ -0,0 +1,85 @@ +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { withDispatch } from '@wordpress/data'; +import { + Dropdown, + IconButton, + MenuItem, + NavigableMenu, + TextControl, +} from '@wordpress/components'; +import { useState, useMemo, useCallback } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { createBlock } from '@wordpress/blocks'; +import { isURL } from '@wordpress/url'; + +function MenuItemInserter( { insertMenuItem } ) { + const [ searchInput, setSearchInput ] = useState( '' ); + const isUrlInput = useMemo( () => isURL( searchInput ), [ searchInput ] ); + const onMenuItemClick = useCallback( () => { + insertMenuItem( { + destination: searchInput, + } ); + }, [ insertMenuItem, searchInput ] ); + + return ( + <Dropdown + className="wp-block-navigation-menu__inserter" + position="bottom center" + renderToggle={ ( { isOpen, onToggle } ) => ( + <IconButton + icon="insert" + label={ __( 'Insert a new menu item' ) } + onClick={ onToggle } + aria-expanded={ isOpen } + /> + ) } + renderContent={ () => ( + <div className="wp-block-navigation-menu__inserter-content"> + <TextControl + value={ searchInput } + label={ __( 'Search or paste a link' ) } + onChange={ setSearchInput } + /> + { isUrlInput && ( + <NavigableMenu> + <MenuItem + onClick={ onMenuItemClick } + icon="admin-links" + > + { searchInput } + </MenuItem> + </NavigableMenu> + ) } + </div> + ) } + /> + ); +} + +export default compose( [ + withDispatch( ( dispatch, props, { select } ) => { + return { + insertMenuItem( attributes ) { + const { + getBlockOrder, + } = select( 'core/block-editor' ); + const { + insertBlock, + } = dispatch( 'core/block-editor' ); + const index = getBlockOrder( props.rootClientId ).length; + const insertedBlock = createBlock( + 'core/navigation-menu-item', + attributes + ); + insertBlock( + insertedBlock, + index, + props.rootClientId + ); + }, + }; + } ), +] )( MenuItemInserter ); diff --git a/packages/block-library/src/navigation-menu/save.js b/packages/block-library/src/navigation-menu/save.js new file mode 100644 index 00000000000000..9d40874cdffd9f --- /dev/null +++ b/packages/block-library/src/navigation-menu/save.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function save() { + return ( + <nav> + <InnerBlocks.Content /> + </nav> + ); +} diff --git a/packages/e2e-tests/fixtures/block-transforms.js b/packages/e2e-tests/fixtures/block-transforms.js index 723552c024b545..7391c1bc7adb90 100644 --- a/packages/e2e-tests/fixtures/block-transforms.js +++ b/packages/e2e-tests/fixtures/block-transforms.js @@ -304,6 +304,18 @@ export const EXPECTED_TRANSFORMS = { 'Group', ], }, + 'core__navigation-menu': { + originalBlock: 'Navigation Menu (Experimental)', + availableTransforms: [ + 'Group', + ], + }, + 'core__navigation-menu-item': { + originalBlock: 'Menu Item (Experimental)', + availableTransforms: [ + 'Group', + ], + }, core__nextpage: { originalBlock: 'Page Break', availableTransforms: [ diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html new file mode 100644 index 00000000000000..fe1d420b45f933 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html @@ -0,0 +1,3 @@ +<!-- wp:navigation-menu-item {"label":"WordPress","destination":"https://wordpress.org/"} --> +<a class="wp-block-navigation-menu-item" href="https://wordpress.org/">WordPress</a> +<!-- /wp:navigation-menu-item --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json new file mode 100644 index 00000000000000..8672293bfb6a08 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json @@ -0,0 +1,15 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/navigation-menu-item", + "isValid": true, + "attributes": { + "label": "WordPress", + "destination": "https://wordpress.org/", + "nofollow": false, + "opensInNewTab": false + }, + "innerBlocks": [], + "originalContent": "<a class=\"wp-block-navigation-menu-item\" href=\"https://wordpress.org/\">WordPress</a>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json new file mode 100644 index 00000000000000..543509ab24b447 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/navigation-menu-item", + "attrs": { + "label": "WordPress", + "destination": "https://wordpress.org/" + }, + "innerBlocks": [], + "innerHTML": "\n<a class=\"wp-block-navigation-menu-item\" href=\"https://wordpress.org/\">WordPress</a>\n", + "innerContent": [ + "\n<a class=\"wp-block-navigation-menu-item\" href=\"https://wordpress.org/\">WordPress</a>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html new file mode 100644 index 00000000000000..23da06ad987cec --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:navigation-menu-item {"label":"WordPress","destination":"https://wordpress.org/"} --> +<a href="https://wordpress.org/" class="wp-block-navigation-menu-item">WordPress</a> +<!-- /wp:navigation-menu-item --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.html new file mode 100644 index 00000000000000..fcd6c46d6bff6d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.html @@ -0,0 +1,7 @@ +<!-- wp:navigation-menu --> +<nav class="wp-block-navigation-menu"> + <!-- wp:navigation-menu-item {"label":"WordPress","destination":"https://wordpress.org/"} --> + <a class="wp-block-navigation-menu-item" href="https://wordpress.org/">WordPress</a> + <!-- /wp:navigation-menu-item --> +</nav> +<!-- /wp:navigation-menu --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.json b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.json new file mode 100644 index 00000000000000..6776ac5dad7280 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.json @@ -0,0 +1,26 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/navigation-menu", + "isValid": true, + "attributes": { + "automaticallyAdd": false + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/navigation-menu-item", + "isValid": true, + "attributes": { + "label": "WordPress", + "destination": "https://wordpress.org/", + "nofollow": false, + "opensInNewTab": false + }, + "innerBlocks": [], + "originalContent": "<a class=\"wp-block-navigation-menu-item\" href=\"https://wordpress.org/\">WordPress</a>" + } + ], + "originalContent": "<nav class=\"wp-block-navigation-menu\">\n\t\n</nav>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json new file mode 100644 index 00000000000000..65c2aecba3e278 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json @@ -0,0 +1,35 @@ +[ + { + "blockName": "core/navigation-menu", + "attrs": {}, + "innerBlocks": [ + { + "blockName": "core/navigation-menu-item", + "attrs": { + "label": "WordPress", + "destination": "https://wordpress.org/" + }, + "innerBlocks": [], + "innerHTML": "\n\t<a class=\"wp-block-navigation-menu-item\" href=\"https://wordpress.org/\">WordPress</a>\n\t", + "innerContent": [ + "\n\t<a class=\"wp-block-navigation-menu-item\" href=\"https://wordpress.org/\">WordPress</a>\n\t" + ] + } + ], + "innerHTML": "\n<nav class=\"wp-block-navigation-menu\">\n\t\n</nav>\n", + "innerContent": [ + "\n<nav class=\"wp-block-navigation-menu\">\n\t", + null, + "\n</nav>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html new file mode 100644 index 00000000000000..0d1fd66c6a2a7a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:navigation-menu --> +<nav class="wp-block-navigation-menu"><!-- wp:navigation-menu-item {"label":"WordPress","destination":"https://wordpress.org/"} --> +<a href="https://wordpress.org/" class="wp-block-navigation-menu-item">WordPress</a> +<!-- /wp:navigation-menu-item --></nav> +<!-- /wp:navigation-menu --> From c0fc6f5ba29409fa21b63e9b260e1dfad5f935f2 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 18 Jun 2019 14:35:30 +0100 Subject: [PATCH 358/664] Fix: Popover positions on the widget screen (#15949) --- packages/edit-widgets/src/components/layout/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index a20d11e557e6d0..44b6d1d64896ea 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { navigateRegions } from '@wordpress/components'; +import { navigateRegions, Popover, SlotFillProvider } from '@wordpress/components'; /** * Internal dependencies @@ -14,7 +14,7 @@ import Notices from '../notices'; function Layout( { blockEditorSettings } ) { return ( - <> + <SlotFillProvider> <Header /> <Sidebar /> <Notices /> @@ -28,7 +28,8 @@ function Layout( { blockEditorSettings } ) { blockEditorSettings={ blockEditorSettings } /> </div> - </> + <Popover.Slot /> + </SlotFillProvider> ); } From 9d59ef15ca7074223c71b744825bd476a02231d3 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 18 Jun 2019 14:36:16 +0100 Subject: [PATCH 359/664] Fix: Block toolbar does not appear and movers can not be used on block editor without editor module (#15470) --- packages/edit-widgets/src/components/widget-area/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 0397a77c02957f..4e3048e43ec6e6 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -13,6 +13,8 @@ import { Panel, PanelBody } from '@wordpress/components'; import { BlockEditorProvider, BlockList, + ObserveTyping, + WritingFlow, } from '@wordpress/block-editor'; import { withDispatch, withSelect } from '@wordpress/data'; @@ -57,7 +59,11 @@ function WidgetArea( { onChange={ updateBlocks } settings={ settings } > - <BlockList /> + <WritingFlow> + <ObserveTyping> + <BlockList /> + </ObserveTyping> + </WritingFlow> </BlockEditorProvider> </PanelBody> </Panel> From f7bbf58bcc9d968f5112a7383c04b3b0aabe189e Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Tue, 18 Jun 2019 15:53:45 +0200 Subject: [PATCH 360/664] Add native version of BlockMobileToolbar (Ported from gutenberg-mobile) (#16177) * Add native version of BlockMobileToolbar (Ported from gutenberg-mobile) * Call requestImageUploadCancel directly instead of running hook * Update move block and delete block button labels * Use the right property title instead of label for ToolbarButton * Fix Move block down accessibility text --- .../block-list/block-mobile-toolbar.native.js | 70 ++++++++++++++++ .../block-mobile-toolbar.native.scss | 11 +++ .../components/block-mover/index.native.js | 80 +++++++++++++++++++ .../src/components/index.native.js | 2 + .../block-library/src/image/edit.native.js | 7 +- .../block-library/src/video/edit.native.js | 7 +- 6 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js create mode 100644 packages/block-editor/src/components/block-list/block-mobile-toolbar.native.scss create mode 100644 packages/block-editor/src/components/block-mover/index.native.js diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js new file mode 100644 index 00000000000000..e5ac71ee27ed34 --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js @@ -0,0 +1,70 @@ +/** + * External dependencies + */ +import { Keyboard, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { ToolbarButton } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './block-mobile-toolbar.scss'; +import BlockMover from '../block-mover'; +import InspectorControls from '../inspector-controls'; + +const BlockMobileToolbar = ( { + clientId, + onDelete, + order, +} ) => ( + <View style={ styles.toolbar }> + <BlockMover clientIds={ [ clientId ] } /> + + <View style={ styles.spacer } /> + + <InspectorControls.Slot /> + + <ToolbarButton + title={ + sprintf( + /* translators: accessibility text. %s: current block position (number). */ + __( 'Remove block at row %s' ), + order + 1 + ) + } + onClick={ onDelete } + icon="trash" + extraProps={ { hint: __( 'Double tap to remove the block' ) } } + /> + </View> +); + +export default compose( + withSelect( ( select, { clientId } ) => { + const { + getBlockIndex, + } = select( 'core/block-editor' ); + + return { + order: getBlockIndex( clientId ), + }; + } ), + withDispatch( ( dispatch, { clientId, rootClientId, onDelete } ) => { + const { removeBlock } = dispatch( 'core/block-editor' ); + return { + onDelete() { + Keyboard.dismiss(); + removeBlock( clientId, rootClientId ); + if ( onDelete ) { + onDelete( clientId ); + } + }, + }; + } ), +)( BlockMobileToolbar ); diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.scss b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.scss new file mode 100644 index 00000000000000..2633fc1f1c2fcc --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.scss @@ -0,0 +1,11 @@ +.toolbar { + flex-direction: row; + height: 44px; + align-items: flex-start; + margin-left: 2px; + margin-right: 2px; +} + +.spacer { + flex-grow: 1; +} diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js new file mode 100644 index 00000000000000..8962dc22f49e45 --- /dev/null +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import { first, last, partial, castArray } from 'lodash'; + +/** + * WordPress dependencies + */ +import { ToolbarButton } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { withInstanceId, compose } from '@wordpress/compose'; + +const BlockMover = ( { + isFirst, + isLast, + onMoveDown, + onMoveUp, + firstIndex, +} ) => ( + <> + <ToolbarButton + title={ ! isFirst ? + sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block up from row %1$s to row %2$s' ), + firstIndex + 1, + firstIndex + ) : + __( 'Move block up' ) + } + isDisabled={ isFirst } + onClick={ onMoveUp } + icon="arrow-up-alt" + extraProps={ { hint: __( 'Double tap to move the block up' ) } } + /> + + <ToolbarButton + title={ ! isLast ? + sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block down from row %1$s to row %2$s' ), + firstIndex + 1, + firstIndex + 2 + ) : + __( 'Move block down' ) + } + isDisabled={ isLast } + onClick={ onMoveDown } + icon="arrow-down-alt" + extraProps={ { hint: __( 'Double tap to move the block down' ) } } + /> + </> +); + +export default compose( + withSelect( ( select, { clientIds } ) => { + const { getBlockIndex, getBlockRootClientId, getBlockOrder } = select( 'core/block-editor' ); + const normalizedClientIds = castArray( clientIds ); + const firstClientId = first( normalizedClientIds ); + const rootClientId = getBlockRootClientId( first( normalizedClientIds ) ); + const blockOrder = getBlockOrder( rootClientId ); + const firstIndex = getBlockIndex( firstClientId, rootClientId ); + const lastIndex = getBlockIndex( last( normalizedClientIds ), rootClientId ); + + return { + firstIndex, + isFirst: firstIndex === 0, + isLast: lastIndex === blockOrder.length - 1, + }; + } ), + withDispatch( ( dispatch, { clientIds, rootClientId } ) => { + const { moveBlocksDown, moveBlocksUp } = dispatch( 'core/block-editor' ); + return { + onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ), + onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ), + }; + } ), + withInstanceId, +)( BlockMover ); diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index b0e5f34e3e76d6..cd3a53b3f12998 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -19,6 +19,8 @@ export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; // Content Related Components +export { default as BlockMobileToolbar } from './block-list/block-mobile-toolbar'; +export { default as BlockMover } from './block-mover'; export { default as BlockToolbar } from './block-toolbar'; export { default as DefaultBlockAppender } from './default-block-appender'; export { default as Inserter } from './inserter'; diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index cb0cc5a9302630..d7ade08d4f5c94 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -8,6 +8,7 @@ import { mediaUploadSync, requestImageFailedRetryDialog, requestImageUploadCancelDialog, + requestImageUploadCancel, } from 'react-native-gutenberg-bridge'; import { isEmpty } from 'lodash'; @@ -30,7 +31,6 @@ import { } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; -import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -92,9 +92,8 @@ class ImageEdit extends React.Component { } componentWillUnmount() { - // this action will only exist if the user pressed the trash button on the block holder - if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { - doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); + if ( this.state.isUploadInProgress ) { + requestImageUploadCancel( this.props.attributes.id ); } } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 1b7fc518313440..c7d041cd725552 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -11,6 +11,7 @@ import { mediaUploadSync, requestImageFailedRetryDialog, requestImageUploadCancelDialog, + requestImageUploadCancel, } from 'react-native-gutenberg-bridge'; /** @@ -31,7 +32,6 @@ import { } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; -import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -70,9 +70,8 @@ class VideoEdit extends React.Component { } componentWillUnmount() { - // this action will only exist if the user pressed the trash button on the block holder - if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { - doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); + if ( this.state.isUploadInProgress ) { + requestImageUploadCancel( this.props.attributes.id ); } } From 7568e9c0faad01008650e7c709824f46a6b7af1b Mon Sep 17 00:00:00 2001 From: andrei draganescu <andrei.draganescu@automattic.com> Date: Tue, 18 Jun 2019 17:35:03 +0300 Subject: [PATCH 361/664] Update Image Block's image classes with dimensions (#15464) * WIP: adds size classes but duplicates them on edit * tries to get the class name right - fail * assigns on component update the class name based on URL. still duplicates on front end * poor mans solution * fixes image duplication with a more complex regexp * refactores out the select control edit * updates cpt lock snapshot * updates new snapshots * removes useless replace of old classes * adds the new attribute to block.json * refactored class management * refactored slug retrieval * reverts snapshots updated previously by the same tool - magic --- packages/block-library/src/image/block.json | 3 ++ packages/block-library/src/image/edit.js | 44 ++++++++++++--------- packages/block-library/src/image/save.js | 2 + 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 4abbee34871a84..68d2559ae4f480 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -50,6 +50,9 @@ "height": { "type": "number" }, + "sizeSlug": { + "type": "string" + }, "linkDestination": { "type": "string", "default": "none" diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 7103c495e7a230..1d4d27a7db29e1 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -3,7 +3,6 @@ */ import classnames from 'classnames'; import { - compact, get, isEmpty, map, @@ -66,6 +65,7 @@ const LINK_DESTINATION_ATTACHMENT = 'attachment'; const LINK_DESTINATION_CUSTOM = 'custom'; const NEW_TAB_REL = 'noreferrer noopener'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; +const DEFAULT_SIZE_SLUG = 'large'; export const pickRelevantMediaFiles = ( image ) => { const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); @@ -104,7 +104,7 @@ class ImageEdit extends Component { this.onImageClick = this.onImageClick.bind( this ); this.onSelectImage = this.onSelectImage.bind( this ); this.onSelectURL = this.onSelectURL.bind( this ); - this.updateImageURL = this.updateImageURL.bind( this ); + this.updateImage = this.updateImage.bind( this ); this.updateWidth = this.updateWidth.bind( this ); this.updateHeight = this.updateHeight.bind( this ); this.updateDimensions = this.updateDimensions.bind( this ); @@ -195,6 +195,7 @@ class ImageEdit extends Component { ...pickRelevantMediaFiles( media ), width: undefined, height: undefined, + sizeSlug: DEFAULT_SIZE_SLUG, } ); } @@ -224,6 +225,7 @@ class ImageEdit extends Component { this.props.setAttributes( { url: newURL, id: undefined, + sizeSlug: DEFAULT_SIZE_SLUG, } ); } @@ -298,8 +300,20 @@ class ImageEdit extends Component { this.props.setAttributes( { ...extraUpdatedAttributes, align: nextAlign } ); } - updateImageURL( url ) { - this.props.setAttributes( { url, width: undefined, height: undefined } ); + updateImage( sizeSlug ) { + const { image } = this.props; + + const url = get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ); + if ( ! url ) { + return null; + } + + this.props.setAttributes( { + url, + width: undefined, + height: undefined, + sizeSlug, + } ); } updateWidth( width ) { @@ -344,17 +358,8 @@ class ImageEdit extends Component { } getImageSizeOptions() { - const { imageSizes, image } = this.props; - return compact( map( imageSizes, ( { name, slug } ) => { - const sizeUrl = get( image, [ 'media_details', 'sizes', slug, 'source_url' ] ); - if ( ! sizeUrl ) { - return null; - } - return { - value: sizeUrl, - label: name, - }; - } ) ); + const { imageSizes } = this.props; + return map( imageSizes, ( { name, slug } ) => ( { value: slug, label: name } ) ); } render() { @@ -383,7 +388,9 @@ class ImageEdit extends Component { width, height, linkTarget, + sizeSlug, } = attributes; + const isExternal = isExternalImage( id, url ); const editImageIcon = ( <SVG width={ 20 } height={ 20 } viewBox="0 0 20 20"><Rect x={ 11 } y={ 3 } width={ 7 } height={ 5 } rx={ 1 } /><Rect x={ 2 } y={ 12 } width={ 7 } height={ 5 } rx={ 1 } /><Path d="M13,12h1a3,3,0,0,1-3,3v2a5,5,0,0,0,5-5h1L15,9Z" /><Path d="M4,8H3l2,3L7,8H6A3,3,0,0,1,9,5V3A5,5,0,0,0,4,8Z" /></SVG> ); const controls = ( @@ -443,10 +450,11 @@ class ImageEdit extends Component { ); } - const classes = classnames( className, { + const classes = classnames( { 'is-transient': isBlobURL( url ), 'is-resized': !! width || !! height, 'is-focused': isSelected, + [ `size-${ sizeSlug }` ]: sizeSlug, } ); const isResizable = [ 'wide', 'full' ].indexOf( align ) === -1 && isLargeViewport; @@ -472,9 +480,9 @@ class ImageEdit extends Component { { ! isEmpty( imageSizeOptions ) && ( <SelectControl label={ __( 'Image Size' ) } - value={ url } + value={ sizeSlug } options={ imageSizeOptions } - onChange={ this.updateImageURL } + onChange={ this.updateImage } /> ) } { isResizable && ( diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index ae023d3399b02b..01aff39769152b 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -21,10 +21,12 @@ export default function save( { attributes } ) { height, id, linkTarget, + sizeSlug, } = attributes; const classes = classnames( { [ `align${ align }` ]: align, + [ `size-${ sizeSlug }` ]: sizeSlug, 'is-resized': width || height, } ); From 53118e1afae9827d516d628470d25726af603a75 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Wed, 19 Jun 2019 08:02:49 +1000 Subject: [PATCH 362/664] Add alias for react-spring in webpack config (#16196) * Add alias for react-spring in webpack config * Add explanatory comment. * Move to direct imports and linting rule. --- .eslintrc.js | 4 ++++ packages/components/src/snackbar/list.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 464b6e57cf3eb9..64ce20d480d398 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -33,6 +33,10 @@ module.exports = { selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', message: 'Path access on WordPress dependencies is not allowed.', }, + { + selector: 'ImportDeclaration[source.value=/^react-spring(?!\\u002Fweb\.cjs)/]', + message: 'The react-spring dependency must specify CommonJS bundle: react-spring/web.cjs', + }, { selector: 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + majorMinorRegExp + '/]', message: 'Deprecated functions must be removed before releasing this version.', diff --git a/packages/components/src/snackbar/list.js b/packages/components/src/snackbar/list.js index b44aa8d24c93a3..ca9060560ad0a2 100644 --- a/packages/components/src/snackbar/list.js +++ b/packages/components/src/snackbar/list.js @@ -3,7 +3,7 @@ */ import classnames from 'classnames'; import { omit, noop } from 'lodash'; -import { useTransition, animated } from 'react-spring'; +import { useTransition, animated } from 'react-spring/web.cjs'; /** * WordPress dependencies From 4efbda6bb5f24d9d55cb8e3eea36294c147d7eaf Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Wed, 19 Jun 2019 10:25:58 +0100 Subject: [PATCH 363/664] Registers the core/group Block as the default block for handling "grouping" interactions (#15774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds grouping interaction to state Enables the ability register a given block as the block which handles “grouping” interactions within the editor. Related https://github.com/WordPress/gutenberg/pull/14908 * Registers grouping block * Auto updated README * Adds test for get/set Grouping block * Update documentation with clearer descriptions * Adds reducer and selector tests * Updates “Blog” for correct “Block” in docs --- .../developers/data/data-core-blocks.md | 26 +++++++++++++++++++ packages/block-library/src/index.js | 5 ++++ packages/blocks/README.md | 20 ++++++++++++-- packages/blocks/src/api/index.js | 2 ++ packages/blocks/src/api/registration.js | 22 ++++++++++++++-- packages/blocks/src/api/test/registration.js | 16 ++++++++++++ packages/blocks/src/store/actions.js | 16 ++++++++++++ packages/blocks/src/store/reducer.js | 2 ++ packages/blocks/src/store/selectors.js | 11 ++++++++ packages/blocks/src/store/test/reducer.js | 25 ++++++++++++++++++ packages/blocks/src/store/test/selectors.js | 11 ++++++++ 11 files changed, 152 insertions(+), 4 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index 5256587043602d..b1d377ed11a6fe 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -108,6 +108,18 @@ _Returns_ - `?string`: Name of the block for handling non-block content. +<a name="getGroupingBlockName" href="#getGroupingBlockName">#</a> **getGroupingBlockName** + +Returns the name of the block for handling unregistered blocks. + +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `?string`: Name of the block for handling unregistered blocks. + <a name="getUnregisteredFallbackBlockName" href="#getUnregisteredFallbackBlockName">#</a> **getUnregisteredFallbackBlockName** Returns the name of the block for handling unregistered blocks. @@ -270,6 +282,20 @@ _Returns_ - `Object`: Action object. +<a name="setGroupingBlockName" href="#setGroupingBlockName">#</a> **setGroupingBlockName** + +Returns an action object used to set the name of the block used +when grouping other blocks +eg: in "Group/Ungroup" interactions + +_Parameters_ + +- _name_ `string`: Block name. + +_Returns_ + +- `Object`: Action object. + <a name="setUnregisteredFallbackBlockName" href="#setUnregisteredFallbackBlockName">#</a> **setUnregisteredFallbackBlockName** Returns an action object used to set the name of the block used as a fallback diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 9724f9d9293e33..a9a9b78f34ca92 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -9,6 +9,7 @@ import { setDefaultBlockName, setFreeformContentHandlerName, setUnregisteredTypeHandlerName, + setGroupingBlockName, unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from '@wordpress/blocks'; @@ -139,4 +140,8 @@ export const registerCoreBlocks = () => { setFreeformContentHandlerName( classic.name ); } setUnregisteredTypeHandlerName( missing.name ); + + if ( group ) { + setGroupingBlockName( group.name ); + } }; diff --git a/packages/blocks/README.md b/packages/blocks/README.md index b7a8e5fd70c9dd..cf83bc0e3b0870 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -370,7 +370,15 @@ handler has been defined. _Returns_ -- `?string`: Blog name. +- `?string`: Block name. + +<a name="getGroupingBlockName" href="#getGroupingBlockName">#</a> **getGroupingBlockName** + +Retrieves name of block used for handling grouping interactions. + +_Returns_ + +- `?string`: Block name. <a name="getPhrasingContentSchema" href="#getPhrasingContentSchema">#</a> **getPhrasingContentSchema** @@ -434,7 +442,7 @@ handler has been defined. _Returns_ -- `?string`: Blog name. +- `?string`: Block name. <a name="hasBlockSupport" href="#hasBlockSupport">#</a> **hasBlockSupport** @@ -660,6 +668,14 @@ _Parameters_ - _blockName_ `string`: Block name. +<a name="setGroupingBlockName" href="#setGroupingBlockName">#</a> **setGroupingBlockName** + +Assigns name of block for handling block grouping interactions. + +_Parameters_ + +- _name_ `string`: Block name. + <a name="setUnregisteredTypeHandlerName" href="#setUnregisteredTypeHandlerName">#</a> **setUnregisteredTypeHandlerName** Assigns name of block handling unregistered block types. diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index c13b28f4903ca0..68e60ed43952b1 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -35,6 +35,8 @@ export { getUnregisteredTypeHandlerName, setDefaultBlockName, getDefaultBlockName, + setGroupingBlockName, + getGroupingBlockName, getBlockType, getBlockTypes, getBlockSupport, diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 8f1155fbce3400..978cac89d52f63 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -193,12 +193,21 @@ export function setFreeformContentHandlerName( blockName ) { * Retrieves name of block handling non-block content, or undefined if no * handler has been defined. * - * @return {?string} Blog name. + * @return {?string} Block name. */ export function getFreeformContentHandlerName() { return select( 'core/blocks' ).getFreeformFallbackBlockName(); } +/** + * Retrieves name of block used for handling grouping interactions. + * + * @return {?string} Block name. + */ +export function getGroupingBlockName() { + return select( 'core/blocks' ).getGroupingBlockName(); +} + /** * Assigns name of block handling unregistered block types. * @@ -212,7 +221,7 @@ export function setUnregisteredTypeHandlerName( blockName ) { * Retrieves name of block handling unregistered block types, or undefined if no * handler has been defined. * - * @return {?string} Blog name. + * @return {?string} Block name. */ export function getUnregisteredTypeHandlerName() { return select( 'core/blocks' ).getUnregisteredFallbackBlockName(); @@ -227,6 +236,15 @@ export function setDefaultBlockName( name ) { dispatch( 'core/blocks' ).setDefaultBlockName( name ); } +/** + * Assigns name of block for handling block grouping interactions. + * + * @param {string} name Block name. + */ +export function setGroupingBlockName( name ) { + dispatch( 'core/blocks' ).setGroupingBlockName( name ); +} + /** * Retrieves the default block name. * diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 05cc3140779eaa..90d1c4760d1ee4 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -22,6 +22,8 @@ import { getUnregisteredTypeHandlerName, setDefaultBlockName, getDefaultBlockName, + getGroupingBlockName, + setGroupingBlockName, getBlockType, getBlockTypes, getBlockSupport, @@ -415,6 +417,20 @@ describe( 'blocks', () => { } ); } ); + describe( 'getGroupingBlockName()', () => { + it( 'defaults to undefined', () => { + expect( getGroupingBlockName() ).toBeNull(); + } ); + } ); + + describe( 'setGroupingBlockName()', () => { + it( 'assigns default block name', () => { + setGroupingBlockName( 'core/test-block' ); + + expect( getGroupingBlockName() ).toBe( 'core/test-block' ); + } ); + } ); + describe( 'getBlockType()', () => { it( 'should return { name, save } for blocks with minimum settings', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js index 8f2d9c834d26dc..220ae9be0ea0d3 100644 --- a/packages/blocks/src/store/actions.js +++ b/packages/blocks/src/store/actions.js @@ -107,6 +107,22 @@ export function setUnregisteredFallbackBlockName( name ) { }; } +/** + * Returns an action object used to set the name of the block used + * when grouping other blocks + * eg: in "Group/Ungroup" interactions + * + * @param {string} name Block name. + * + * @return {Object} Action object. + */ +export function setGroupingBlockName( name ) { + return { + type: 'SET_GROUPING_BLOCK_NAME', + name, + }; +} + /** * Returns an action object used to set block categories. * diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index bfeeec10ec1e10..4e3131ca7b62e0 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -124,6 +124,7 @@ export function createBlockNameSetterReducer( setActionType ) { export const defaultBlockName = createBlockNameSetterReducer( 'SET_DEFAULT_BLOCK_NAME' ); export const freeformFallbackBlockName = createBlockNameSetterReducer( 'SET_FREEFORM_FALLBACK_BLOCK_NAME' ); export const unregisteredFallbackBlockName = createBlockNameSetterReducer( 'SET_UNREGISTERED_FALLBACK_BLOCK_NAME' ); +export const groupingBlockName = createBlockNameSetterReducer( 'SET_GROUPING_BLOCK_NAME' ); /** * Reducer managing the categories @@ -164,5 +165,6 @@ export default combineReducers( { defaultBlockName, freeformFallbackBlockName, unregisteredFallbackBlockName, + groupingBlockName, categories, } ); diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 97d323c65fb168..25fe40ca9f6111 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -101,6 +101,17 @@ export function getUnregisteredFallbackBlockName( state ) { return state.unregisteredFallbackBlockName; } +/** + * Returns the name of the block for handling unregistered blocks. + * + * @param {Object} state Data state. + * + * @return {string?} Name of the block for handling unregistered blocks. + */ +export function getGroupingBlockName( state ) { + return state.groupingBlockName; +} + /** * Returns an array with the child blocks of a given block. * diff --git a/packages/blocks/src/store/test/reducer.js b/packages/blocks/src/store/test/reducer.js index 249b86ea6f197a..346bb2e9aab1bf 100644 --- a/packages/blocks/src/store/test/reducer.js +++ b/packages/blocks/src/store/test/reducer.js @@ -13,6 +13,7 @@ import { defaultBlockName, freeformFallbackBlockName, unregisteredFallbackBlockName, + groupingBlockName, DEFAULT_CATEGORIES, } from '../reducer'; @@ -183,6 +184,30 @@ describe( 'freeformFallbackBlockName', () => { } ); } ); +describe( 'groupingBlockName', () => { + it( 'should return null as default state', () => { + expect( groupingBlockName( undefined, {} ) ).toBeNull(); + } ); + + it( 'should set the grouping block name', () => { + const state = groupingBlockName( null, { + type: 'SET_GROUPING_BLOCK_NAME', + name: 'core/group', + } ); + + expect( state ).toBe( 'core/group' ); + } ); + + it( 'should reset the group fallback block name', () => { + const state = groupingBlockName( 'core/group', { + type: 'REMOVE_BLOCK_TYPES', + names: [ 'core/group' ], + } ); + + expect( state ).toBeNull(); + } ); +} ); + describe( 'unregisteredFallbackBlockName', () => { it( 'should return null as default state', () => { expect( unregisteredFallbackBlockName( undefined, {} ) ).toBeNull(); diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 2f37ed006042c0..05fc5a23040a85 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -4,6 +4,7 @@ import { getChildBlockNames, isMatchingSearchTerm, + getGroupingBlockName, } from '../selectors'; describe( 'selectors', () => { @@ -199,4 +200,14 @@ describe( 'selectors', () => { } ); } ); } ); + + describe( 'getGroupingBlockName', () => { + it( 'returns the grouping block name from state', () => { + const state = { + groupingBlockName: 'core/group', + }; + + expect( getGroupingBlockName( state ) ).toEqual( 'core/group' ); + } ); + } ); } ); From 651110b704c92c97a76ea25a08256a437c2b25aa Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Wed, 19 Jun 2019 16:21:28 +0200 Subject: [PATCH 364/664] Update type specifiers in validation logging (#16173) --- packages/blocks/src/api/validation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index 7105b5bba1890f..ff37b5a73872ca 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -431,7 +431,7 @@ export function isEqualTagAttributePairs( actual, expected ) { // avoids us needing to check both attributes sets, since if A has any keys // which do not exist in B, we know the sets to be different. if ( actual.length !== expected.length ) { - log.warning( 'Expected attributes %o, instead saw %o.', expected, actual ); + log.warning( 'Expected attributes %j, instead saw %j.', expected, actual ); return false; } @@ -573,13 +573,13 @@ export function isEquivalentHTML( actual, expected ) { // Inequal if exhausted all expected tokens if ( ! expectedToken ) { - log.warning( 'Expected end of content, instead saw %o.', actualToken ); + log.warning( 'Expected end of content, instead saw %j.', actualToken ); return false; } // Inequal if next non-whitespace token of each set are not same type if ( actualToken.type !== expectedToken.type ) { - log.warning( 'Expected token of type `%s` (%o), instead saw `%s` (%o).', expectedToken.type, expectedToken, actualToken.type, actualToken ); + log.warning( 'Expected token of type `%s` (%j), instead saw `%s` (%j).', expectedToken.type, expectedToken, actualToken.type, actualToken ); return false; } @@ -606,7 +606,7 @@ export function isEquivalentHTML( actual, expected ) { if ( ( expectedToken = getNextNonWhitespaceToken( expectedTokens ) ) ) { // If any non-whitespace tokens remain in expected token set, this // indicates inequality - log.warning( 'Expected %o, instead saw end of content.', expectedToken ); + log.warning( 'Expected %j, instead saw end of content.', expectedToken ); return false; } From e60fffb5348655f40fecb9cc2dce51ce5c7e722e Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 20 Jun 2019 09:11:41 +0200 Subject: [PATCH 365/664] Add native support for BlockListBlock (Ported from Gutenberg mobile) (#16223) * Add native support for BlockListBlock (Ported from Gutenberg mobile) * Remove Flow syntax in withDispatch --- .../src/components/block-list/block.native.js | 221 ++++++++++++++++++ .../components/block-list/block.native.scss | 38 +++ .../src/components/index.native.js | 1 + .../block-library/src/missing/index.native.js | 26 +++ 4 files changed, 286 insertions(+) create mode 100644 packages/block-editor/src/components/block-list/block.native.js create mode 100644 packages/block-editor/src/components/block-list/block.native.scss create mode 100644 packages/block-library/src/missing/index.native.js diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js new file mode 100644 index 00000000000000..3d93500ce9d888 --- /dev/null +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -0,0 +1,221 @@ +/** + * External dependencies + */ +import { + View, + Text, + TouchableWithoutFeedback, +} from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { getBlockType } from '@wordpress/blocks'; +import { BlockEdit, BlockInvalidWarning, BlockMobileToolbar } from '@wordpress/block-editor'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import styles from './block.scss'; + +class BlockListBlock extends Component { + constructor() { + super( ...arguments ); + + this.insertBlocksAfter = this.insertBlocksAfter.bind( this ); + this.onFocus = this.onFocus.bind( this ); + + this.state = { + isFullyBordered: false, + }; + } + + onFocus() { + if ( ! this.props.isSelected ) { + this.props.onSelect(); + } + } + + insertBlocksAfter( blocks ) { + this.props.onInsertBlocks( blocks, this.props.order + 1 ); + + if ( blocks[ 0 ] ) { + // focus on the first block inserted + this.props.onSelect( blocks[ 0 ].clientId ); + } + } + + getBlockForType() { + return ( + <BlockEdit + name={ this.props.name } + isSelected={ this.props.isSelected } + attributes={ this.props.attributes } + setAttributes={ this.props.onChange } + onFocus={ this.onFocus } + onReplace={ this.props.onReplace } + insertBlocksAfter={ this.insertBlocksAfter } + mergeBlocks={ this.props.mergeBlocks } + onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } + clientId={ this.props.clientId } + /> + ); + } + + renderBlockTitle() { + return ( + <View style={ styles.blockTitle }> + <Text>BlockType: { this.props.name }</Text> + </View> + ); + } + + getAccessibilityLabel() { + const { attributes, name, order, title, getAccessibilityLabelExtra } = this.props; + + let blockName = ''; + + if ( name === 'core/missing' ) { // is the block unrecognized? + blockName = title; + } else { + blockName = sprintf( + /* translators: accessibility text. %s: block name. */ + __( '%s Block' ), + title, //already localized + ); + } + + blockName += '. ' + sprintf( __( 'Row %d.' ), order + 1 ); + + if ( getAccessibilityLabelExtra ) { + const blockAccessibilityLabel = getAccessibilityLabelExtra( attributes ); + blockName += blockAccessibilityLabel ? ' ' + blockAccessibilityLabel : ''; + } + + return blockName; + } + + render() { + const { + borderStyle, + clientId, + focusedBorderColor, + icon, + isSelected, + isValid, + showTitle, + title, + } = this.props; + + const borderColor = isSelected ? focusedBorderColor : 'transparent'; + + const accessibilityLabel = this.getAccessibilityLabel(); + + return ( + // accessible prop needs to be false to access children + // https://facebook.github.io/react-native/docs/accessibility#accessible-ios-android + <TouchableWithoutFeedback + onPress={ this.onFocus } + accessible={ ! isSelected } + accessibilityRole={ 'button' } + > + <View style={ [ styles.blockHolder, borderStyle, { borderColor } ] }> + { showTitle && this.renderBlockTitle() } + <View + accessibilityLabel={ accessibilityLabel } + style={ [ ! isSelected && styles.blockContainer, isSelected && styles.blockContainerFocused ] } + > + { isValid && this.getBlockForType() } + { ! isValid && + <BlockInvalidWarning blockTitle={ title } icon={ icon } /> + } + </View> + { isSelected && <BlockMobileToolbar clientId={ clientId } /> } + </View> + + </TouchableWithoutFeedback> + ); + } +} + +export default compose( [ + withSelect( ( select, { clientId, rootClientId } ) => { + const { + getBlockIndex, + getBlocks, + isBlockSelected, + __unstableGetBlockWithoutInnerBlocks, + } = select( 'core/block-editor' ); + const order = getBlockIndex( clientId, rootClientId ); + const isSelected = isBlockSelected( clientId ); + const isFirstBlock = order === 0; + const isLastBlock = order === getBlocks().length - 1; + const block = __unstableGetBlockWithoutInnerBlocks( clientId ); + const { name, attributes, isValid } = block || {}; + const blockType = getBlockType( name || 'core/missing' ); + const title = blockType.title; + const icon = blockType.icon; + const getAccessibilityLabelExtra = blockType.__experimentalGetAccessibilityLabel; + + return { + icon, + name: name || 'core/missing', + order, + title, + attributes, + blockType, + isFirstBlock, + isLastBlock, + isSelected, + isValid, + getAccessibilityLabelExtra, + }; + } ), + withDispatch( ( dispatch, ownProps, { select } ) => { + const { + insertBlocks, + mergeBlocks, + replaceBlocks, + selectBlock, + updateBlockAttributes, + } = dispatch( 'core/block-editor' ); + + return { + mergeBlocks( forward ) { + const { clientId } = ownProps; + const { + getPreviousBlockClientId, + getNextBlockClientId, + } = select( 'core/block-editor' ); + + if ( forward ) { + const nextBlockClientId = getNextBlockClientId( clientId ); + if ( nextBlockClientId ) { + mergeBlocks( clientId, nextBlockClientId ); + } + } else { + const previousBlockClientId = getPreviousBlockClientId( clientId ); + if ( previousBlockClientId ) { + mergeBlocks( previousBlockClientId, clientId ); + } + } + }, + onInsertBlocks( blocks, index ) { + insertBlocks( blocks, index, ownProps.rootClientId ); + }, + onSelect( clientId = ownProps.clientId, initialPosition ) { + selectBlock( clientId, initialPosition ); + }, + onChange: ( attributes ) => { + updateBlockAttributes( ownProps.clientId, attributes ); + }, + onReplace( blocks, indexToSelect ) { + replaceBlocks( [ ownProps.clientId ], blocks, indexToSelect ); + }, + }; + } ), +] )( BlockListBlock ); diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss new file mode 100644 index 00000000000000..b997cddc723073 --- /dev/null +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -0,0 +1,38 @@ +.blockHolder { + flex: 1 1 auto; +} + +.blockContainer { + background-color: $white; + padding-left: 16px; + padding-right: 16px; + padding-top: 12px; + padding-bottom: 12px; +} + +.blockContainerFocused { + background-color: $white; + padding-left: 16px; + padding-right: 16px; + padding-top: 12px; + padding-bottom: 0; // will be flushed into inline toolbar height +} + +.blockTitle { + background-color: $gray; + padding-left: 8px; + padding-top: 4px; + padding-bottom: 4px; +} + +.aztec_container { + flex: 1; +} + +.blockCode { + font-family: $default-monospace-font; +} + +.blockText { + min-height: 50px; +} diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index cd3a53b3f12998..49038ecd6ef2f4 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -19,6 +19,7 @@ export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; // Content Related Components +export { default as BlockListBlock } from './block-list/block'; export { default as BlockMobileToolbar } from './block-list/block-mobile-toolbar'; export { default as BlockMover } from './block-mover'; export { default as BlockToolbar } from './block-toolbar'; diff --git a/packages/block-library/src/missing/index.native.js b/packages/block-library/src/missing/index.native.js new file mode 100644 index 00000000000000..e902faef9b403d --- /dev/null +++ b/packages/block-library/src/missing/index.native.js @@ -0,0 +1,26 @@ +/** + * WordPress dependencies + */ +import { coreBlocks } from '@wordpress/block-library'; + +/** + * Internal dependencies + */ +import { settings as webSettings } from './index.js'; + +export { metadata, name } from './index.js'; + +export const settings = { + ...webSettings, + __experimentalGetAccessibilityLabel( attributes ) { + const { originalName } = attributes; + + const originalBlockType = originalName && coreBlocks[ originalName ]; + + if ( originalBlockType ) { + return originalBlockType.settings.title || originalName; + } + + return ''; + }, +}; From 268b95dcb7218f59b9703104dc35a275680cd87c Mon Sep 17 00:00:00 2001 From: Adam Tak <adam.tak2112@gmail.com> Date: Thu, 20 Jun 2019 02:26:08 -0600 Subject: [PATCH 366/664] Docs: add attribute types documentation (#16220) * Add attribute validation info to documentation Added valid inputs to the type field in attributions on block-attributes.md * added link to WP REST API docs * Update docs/designers-developers/developers/block-api/block-attributes.md Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> * Updated to delete integer description I was advised by the core contributors to either add a definition for number, or delete the definition for integer. After noting that a definition of number (either an integer or floating point number) could create more confusion, I opted for the latter choice. * Update docs/designers-developers/developers/block-api/block-attributes.md Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> --- .../developers/block-api/block-attributes.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/designers-developers/developers/block-api/block-attributes.md b/docs/designers-developers/developers/block-api/block-attributes.md index 2355fbc4356558..65386bba141c0a 100644 --- a/docs/designers-developers/developers/block-api/block-attributes.md +++ b/docs/designers-developers/developers/block-api/block-attributes.md @@ -33,6 +33,20 @@ _Example_: Extract the `src` attribute from an image found in the block's markup // { "url": "https://lorempixel.com/1200/800/" } ``` +#### `attribute` Type Validation + +Accepted values in the `type` field of an `attribute` MUST be one of the following: + +* null +* boolean +* object +* array +* number +* string +* integer + +See [WordPress's REST API documentation](https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/) for additional details. + ### `text` Use `text` to extract the inner text from markup. From 43662f3f9a28bf288d293daf488a17bad54a70d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Thu, 20 Jun 2019 12:15:32 +0200 Subject: [PATCH 367/664] Legacy widget: Remove custom class (#16231) --- packages/block-library/src/legacy-widget/index.js | 1 + packages/block-library/src/legacy-widget/index.php | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/block-library/src/legacy-widget/index.js b/packages/block-library/src/legacy-widget/index.js index 51d00539b73275..67d52f8a7cedb2 100644 --- a/packages/block-library/src/legacy-widget/index.js +++ b/packages/block-library/src/legacy-widget/index.js @@ -18,6 +18,7 @@ export const settings = { category: 'widgets', supports: { html: false, + customClassName: false, }, edit, }; diff --git a/packages/block-library/src/legacy-widget/index.php b/packages/block-library/src/legacy-widget/index.php index 7f06f086aa7295..b812f8e3c710ff 100644 --- a/packages/block-library/src/legacy-widget/index.php +++ b/packages/block-library/src/legacy-widget/index.php @@ -60,9 +60,6 @@ function register_block_core_legacy_widget() { 'core/legacy-widget', array( 'attributes' => array( - 'className' => array( - 'type' => 'string', - ), 'identifier' => array( 'type' => 'string', ), From b6ab1cf2c9e8e26c14a19ab4cb6d709c67c48c6a Mon Sep 17 00:00:00 2001 From: Anja Deubzer <info@anjadeubzer.de> Date: Thu, 20 Jun 2019 12:22:39 +0200 Subject: [PATCH 368/664] Docs: Update block-design.md (#16232) Correcting a typo. --- docs/designers-developers/designers/block-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index 3840c863dd4875..9bf9c0fefb841e 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -110,7 +110,7 @@ The “Block” tab of the Settings Sidebar can contain additional block options ![A screenshot of the paragraph block's advanced settings in the sidebar](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/assets/advanced-settings-do.png) **Do:** -Because the Drop Cap feature is not necessary for the basic operation of the block, you can put it ub the Block tab as optional configuration. +Because the Drop Cap feature is not necessary for the basic operation of the block, you can put it to the Block tab as optional configuration. ### Consider mobile From 03473a9839db2e6ca3ead1068f63c93da33c8d62 Mon Sep 17 00:00:00 2001 From: Matthew Haines-Young <matthewhainesyoung@gmail.com> Date: Thu, 20 Jun 2019 14:06:04 +0100 Subject: [PATCH 369/664] Update usage documentation for Annotations API (#16233) * Updated the annotations documentation to describe the richTextIdentifier property in more detail. * Link to developer.wordpress.org in the package README. --- .../developers/block-api/block-annotations.md | 6 +++++- packages/annotations/README.md | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-annotations.md b/docs/designers-developers/developers/block-api/block-annotations.md index 85a523904fd495..962571b60b8f5a 100644 --- a/docs/designers-developers/developers/block-api/block-annotations.md +++ b/docs/designers-developers/developers/block-api/block-annotations.md @@ -28,7 +28,11 @@ The start and the end of the range should be calculated based only on the text o To help with determining the correct positions, the `wp.richText.create` method can be used. This will split a piece of HTML into text and formats. -All available properties can be found in the API documentation of the `addAnnotation` action. +All available properties can be found in the API documentation of the `addAnnotation` action. + +The property `richTextIdentifier` is the identifier of the RichText instance the annotation applies to. This is necessary because blocks may have multiple rich text instances that are used to manage data for different attributes, so you need to pass this in order to highlight text within the correct one. + +For example the paragraph block only has a single RichText instance, with the identifer `content`. The quote block type has 2 RichText instances, so if you wish to highlight text in the citation, you need to pass `citation` as the `richTextIdentifier` when adding an annotation. To target the quote content, you need to use the identifier `value`. Refer to the source code of the block type to find the correct identifier. ## Block annotation diff --git a/packages/annotations/README.md b/packages/annotations/README.md index 1c20f8956e5871..af3a2263d1175e 100644 --- a/packages/annotations/README.md +++ b/packages/annotations/README.md @@ -12,6 +12,10 @@ npm install @wordpress/annotations --save _This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods. Learn more about it in [Babel docs](https://babeljs.io/docs/en/next/caveats)._ +## Getting Started + +You need to include `wp-annotations` as a dependency of the JavaScript file in which you wish to use the Annotations API. + ## Usage -<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> +[See this page for more detailed usage instructions](https://developer.wordpress.org/block-editor/developers/block-api/block-annotations). From fee6a1fee94c80d4453c5b8d1852543d967fda94 Mon Sep 17 00:00:00 2001 From: Dominik Schilling <dominikschilling+git@gmail.com> Date: Thu, 20 Jun 2019 15:57:18 +0200 Subject: [PATCH 370/664] Remove deprecated note about controls being an opt-in feature (#16121) --- packages/data/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/data/README.md b/packages/data/README.md index e126a85781e4a6..cee8f5c9733d63 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -131,8 +131,6 @@ The `resolvers` option should be passed as an object where each key is the name #### `controls` -_**Note:** Controls are an opt-in feature, enabled via `use` (the [Plugins API](/packages/data/src/plugins/README.md))._ - A **control** defines the execution flow behavior associated with a specific action type. This can be particularly useful in implementing asynchronous data flows for your store. By defining your action creator or resolvers as a generator which yields specific controlled action types, the execution will proceed as defined by the control handler. The `controls` option should be passed as an object where each key is the name of the action type to act upon, the value a function which receives the original action object. It should returns either a promise which is to resolve when evaluation of the action should continue, or a value. The value or resolved promise value is assigned on the return value of the yield assignment. If the control handler returns undefined, the execution is not continued. From 3744aedfd9613a7889e28b8e436c6525e32e0648 Mon Sep 17 00:00:00 2001 From: Reiko <reiko.weiss@posteo.de> Date: Thu, 20 Jun 2019 17:38:21 +0200 Subject: [PATCH 371/664] Docs: Update faq.md (#16235) change were to was in first section --- docs/designers-developers/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/faq.md b/docs/designers-developers/faq.md index 9bd9c8fae2530a..a59f5b3e4e18be 100644 --- a/docs/designers-developers/faq.md +++ b/docs/designers-developers/faq.md @@ -1,6 +1,6 @@ # Frequently Asked Questions -This initial set of questions were created when the Gutenberg project was relatively new, for a more recent set of questions, please see Matt's November 2018 post [WordPress 5.0: A Gutenberg FAQ](https://ma.tt/2018/11/a-gutenberg-faq/). +This initial set of questions was created when the Gutenberg project was relatively new, for a more recent set of questions, please see Matt's November 2018 post [WordPress 5.0: A Gutenberg FAQ](https://ma.tt/2018/11/a-gutenberg-faq/). ## What is Gutenberg? From 705d7446332eddd5b6d508a6b204056bd9e1d3d6 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Fri, 21 Jun 2019 11:04:15 +0200 Subject: [PATCH 372/664] Add native component HTMLTextInput (#16226) * Add native component HTMLTextInput * Move HTMLTextInput test to gutenberg in @wordpress/component * Fix import in test --- packages/components/src/index.native.js | 3 +- .../html-text-input/container.android.js | 22 ++++ .../mobile/html-text-input/container.ios.js | 50 ++++++++ .../mobile/html-text-input/index.native.js | 115 +++++++++++++++++ .../html-text-input/style-common.native.scss | 15 +++ .../mobile/html-text-input/style.android.scss | 23 ++++ .../src/mobile/html-text-input/style.ios.scss | 21 ++++ .../html-text-input/test/index.native.js | 116 ++++++++++++++++++ 8 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 packages/components/src/mobile/html-text-input/container.android.js create mode 100644 packages/components/src/mobile/html-text-input/container.ios.js create mode 100644 packages/components/src/mobile/html-text-input/index.native.js create mode 100644 packages/components/src/mobile/html-text-input/style-common.native.scss create mode 100644 packages/components/src/mobile/html-text-input/style.android.scss create mode 100644 packages/components/src/mobile/html-text-input/style.ios.scss create mode 100644 packages/components/src/mobile/html-text-input/test/index.native.js diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 881b011c8a08ab..5965c13e26eaeb 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -21,7 +21,8 @@ export { default as withSpokenMessages } from './higher-order/with-spoken-messag // Mobile Components export { default as BottomSheet } from './mobile/bottom-sheet'; -export { default as Picker } from './mobile/picker'; +export { default as HTMLTextInput } from './mobile/html-text-input'; export { default as KeyboardAvoidingView } from './mobile/keyboard-avoiding-view'; export { default as KeyboardAwareFlatList } from './mobile/keyboard-aware-flat-list'; +export { default as Picker } from './mobile/picker'; export { default as ReadableContentView } from './mobile/readable-content-view'; diff --git a/packages/components/src/mobile/html-text-input/container.android.js b/packages/components/src/mobile/html-text-input/container.android.js new file mode 100644 index 00000000000000..ef62fc0178a3d6 --- /dev/null +++ b/packages/components/src/mobile/html-text-input/container.android.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { ScrollView } from 'react-native'; + +/** + * Internal dependencies + */ +import KeyboardAvoidingView from '../keyboard-avoiding-view'; +import styles from './style.android.scss'; + +const HTMLInputContainer = ( { children, parentHeight } ) => ( + <KeyboardAvoidingView style={ styles.keyboardAvoidingView } parentHeight={ parentHeight }> + <ScrollView style={ styles.scrollView } > + { children } + </ScrollView> + </KeyboardAvoidingView> +); + +HTMLInputContainer.scrollEnabled = false; + +export default HTMLInputContainer; diff --git a/packages/components/src/mobile/html-text-input/container.ios.js b/packages/components/src/mobile/html-text-input/container.ios.js new file mode 100644 index 00000000000000..a9074311f473b3 --- /dev/null +++ b/packages/components/src/mobile/html-text-input/container.ios.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { UIManager, PanResponder } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import KeyboardAvoidingView from '../keyboard-avoiding-view'; +import styles from './style.ios.scss'; + +class HTMLInputContainer extends Component { + constructor() { + super( ...arguments ); + + this.panResponder = PanResponder.create( { + onStartShouldSetPanResponderCapture: () => true, + + onPanResponderMove: ( e, gestureState ) => { + if ( gestureState.dy > 100 && gestureState.dy < 110 ) { + //Keyboard.dismiss() and this.textInput.blur() are not working here + //They require to know the currentlyFocusedID under the hood but + //during this gesture there's no currentlyFocusedID + UIManager.blur( e.target ); + } + }, + } ); + } + + render() { + return ( + <KeyboardAvoidingView + style={ styles.keyboardAvoidingView } + { ...this.panResponder.panHandlers } + parentHeight={ this.props.parentHeight } + > + { this.props.children } + </KeyboardAvoidingView> + ); + } +} + +HTMLInputContainer.scrollEnabled = true; + +export default HTMLInputContainer; diff --git a/packages/components/src/mobile/html-text-input/index.native.js b/packages/components/src/mobile/html-text-input/index.native.js new file mode 100644 index 00000000000000..3811085213dd12 --- /dev/null +++ b/packages/components/src/mobile/html-text-input/index.native.js @@ -0,0 +1,115 @@ +/** + * External dependencies + */ +import { TextInput } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { parse } from '@wordpress/blocks'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { withInstanceId, compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import HTMLInputContainer from './container'; +import styles from './style.scss'; + +export class HTMLTextInput extends Component { + constructor() { + super( ...arguments ); + + this.edit = this.edit.bind( this ); + this.stopEditing = this.stopEditing.bind( this ); + + this.state = { + isDirty: false, + value: '', + }; + } + + static getDerivedStateFromProps( props, state ) { + if ( state.isDirty ) { + return null; + } + + return { + value: props.value, + isDirty: false, + }; + } + + componentWillUnmount() { + //TODO: Blocking main thread + this.stopEditing(); + } + + edit( html ) { + this.props.onChange( html ); + this.setState( { value: html, isDirty: true } ); + } + + stopEditing() { + if ( this.state.isDirty ) { + this.props.onPersist( this.state.value ); + this.setState( { isDirty: false } ); + } + } + + render() { + return ( + <HTMLInputContainer parentHeight={ this.props.parentHeight }> + <TextInput + autoCorrect={ false } + accessibilityLabel="html-view-title" + textAlignVertical="center" + numberOfLines={ 1 } + style={ styles.htmlViewTitle } + value={ this.props.title } + placeholder={ __( 'Add title' ) } + onChangeText={ this.props.setTitleAction } + /> + <TextInput + autoCorrect={ false } + accessibilityLabel="html-view-content" + textAlignVertical="top" + multiline + style={ styles.htmlView } + value={ this.state.value } + onChangeText={ this.edit } + onBlur={ this.stopEditing } + placeholder={ __( 'Start writing…' ) } + scrollEnabled={ HTMLInputContainer.scrollEnabled } + /> + </HTMLInputContainer> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + getEditedPostContent, + } = select( 'core/editor' ); + + return { + value: getEditedPostContent(), + }; + } ), + withDispatch( ( dispatch ) => { + const { resetBlocks } = dispatch( 'core/block-editor' ); + const { editPost } = dispatch( 'core/editor' ); + return { + onChange( content ) { + editPost( { content } ); + }, + onPersist( content ) { + resetBlocks( parse( content ) ); + }, + }; + } ), + withInstanceId, +] )( HTMLTextInput ); diff --git a/packages/components/src/mobile/html-text-input/style-common.native.scss b/packages/components/src/mobile/html-text-input/style-common.native.scss new file mode 100644 index 00000000000000..4db5b985161402 --- /dev/null +++ b/packages/components/src/mobile/html-text-input/style-common.native.scss @@ -0,0 +1,15 @@ +$padding: 8; +$backgroundColor: $white; +$htmlFont: $default-monospace-font; + +.keyboardAvoidingView { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; +} + +.container { + flex: 1; +} diff --git a/packages/components/src/mobile/html-text-input/style.android.scss b/packages/components/src/mobile/html-text-input/style.android.scss new file mode 100644 index 00000000000000..10594358722c37 --- /dev/null +++ b/packages/components/src/mobile/html-text-input/style.android.scss @@ -0,0 +1,23 @@ +@import "./style-common.scss"; + +.htmlView { + font-family: $htmlFont; + background-color: $backgroundColor; + padding-left: $padding; + padding-right: $padding; + padding-top: $padding; + padding-bottom: $padding + 16; +} + +.htmlViewTitle { + font-family: $htmlFont; + background-color: $backgroundColor; + padding-left: $padding; + padding-right: $padding; + padding-top: $padding; + padding-bottom: $padding; +} + +.scrollView { + flex: 1; +} diff --git a/packages/components/src/mobile/html-text-input/style.ios.scss b/packages/components/src/mobile/html-text-input/style.ios.scss new file mode 100644 index 00000000000000..8b13392b95a9ae --- /dev/null +++ b/packages/components/src/mobile/html-text-input/style.ios.scss @@ -0,0 +1,21 @@ +@import "./style-common.scss"; + +$title-height: 32; + +.htmlView { + font-family: $htmlFont; + background-color: $backgroundColor; + padding-left: $padding; + padding-right: $padding; + padding-bottom: $title-height + $padding; +} + +.htmlViewTitle { + font-family: $htmlFont; + background-color: $backgroundColor; + padding-left: $padding; + padding-right: $padding; + padding-top: $padding; + padding-bottom: $padding; + height: $title-height; +} diff --git a/packages/components/src/mobile/html-text-input/test/index.native.js b/packages/components/src/mobile/html-text-input/test/index.native.js new file mode 100644 index 00000000000000..39153dd945cca1 --- /dev/null +++ b/packages/components/src/mobile/html-text-input/test/index.native.js @@ -0,0 +1,116 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { HTMLTextInput } from '..'; + +// Utility to find a TextInput in a ShallowWrapper +const findTextInputInWrapper = ( wrapper, matchingProps ) => { + return wrapper.dive().findWhere( ( node ) => { + return node.name() === 'TextInput' && node.is( matchingProps ); + } ).first(); +}; + +// Finds the Content TextInput in our HTMLInputView +const findContentTextInput = ( wrapper ) => { + const placeholder = __( 'Start writing…' ); + const matchingProps = { multiline: true, placeholder }; + return findTextInputInWrapper( wrapper, matchingProps ); +}; + +// Finds the Title TextInput in our HTMLInputView +const findTitleTextInput = ( wrapper ) => { + const placeholder = __( 'Add title' ); + return findTextInputInWrapper( wrapper, { placeholder } ); +}; + +describe( 'HTMLTextInput', () => { + it( 'HTMLTextInput renders', () => { + const wrapper = shallow( + <HTMLTextInput /> + ); + expect( wrapper ).toBeTruthy(); + } ); + + it( 'HTMLTextInput updates store and state on HTML text change', () => { + const onChange = jest.fn(); + + const wrapper = shallow( + <HTMLTextInput + onChange={ onChange } + /> + ); + + expect( wrapper.instance().state.isDirty ).toBeFalsy(); + + // Simulate user typing text + const htmlTextInput = findContentTextInput( wrapper ); + htmlTextInput.simulate( 'changeText', 'text' ); + + //Check if the onChange is called and the state is updated + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenCalledWith( 'text' ); + + expect( wrapper.instance().state.isDirty ).toBeTruthy(); + expect( wrapper.instance().state.value ).toEqual( 'text' ); + } ); + + it( 'HTMLTextInput persists changes in HTML text input on blur', () => { + const onPersist = jest.fn(); + + const wrapper = shallow( + <HTMLTextInput + onPersist={ onPersist } + onChange={ jest.fn() } + /> + ); + + // Simulate user typing text + const htmlTextInput = findContentTextInput( wrapper ); + htmlTextInput.simulate( 'changeText', 'text' ); + + //Simulate blur event + htmlTextInput.simulate( 'blur' ); + + //Normally prop.value is updated with the help of withSelect + //But we don't have it in tests so we just simulate it + wrapper.setProps( { value: 'text' } ); + + //Check if the onPersist is called and the state is updated + expect( onPersist ).toHaveBeenCalledTimes( 1 ); + expect( onPersist ).toHaveBeenCalledWith( 'text' ); + + expect( wrapper.instance().state.isDirty ).toBeFalsy(); + + //We expect state.value is getting propagated from prop.value + expect( wrapper.instance().state.value ).toEqual( 'text' ); + } ); + + it( 'HTMLTextInput propagates title changes to store', () => { + const setTitleAction = jest.fn(); + + const wrapper = shallow( + <HTMLTextInput + setTitleAction={ setTitleAction } + /> + ); + + // Simulate user typing text + const textInput = findTitleTextInput( wrapper ); + textInput.simulate( 'changeText', 'text' ); + + //Check if the setTitleAction is called + expect( setTitleAction ).toHaveBeenCalledTimes( 1 ); + expect( setTitleAction ).toHaveBeenCalledWith( 'text' ); + } ); +} ); + From 284bb1781457941eb38a2b080d926ffdcc416e01 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Fri, 21 Jun 2019 13:14:38 +0200 Subject: [PATCH 373/664] Re-enable Video block on Android after Android X migration (#16215) --- packages/edit-post/src/index.native.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 5854c991101955..1abd926f26bc8c 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { Platform } from 'react-native'; - /** * WordPress dependencies */ @@ -27,11 +22,6 @@ export function initializeEditor() { // eslint-disable-next-line no-undef if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); - - // Disable Video block except for iOS for now. - if ( Platform.OS !== 'ios' ) { - unregisterBlockType( 'core/video' ); - } } } From a18ba18f2ac7c36310576aa3934b88434c3477bb Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Fri, 21 Jun 2019 18:55:57 +0200 Subject: [PATCH 374/664] Add native support for BlockList to @wordpress/block-editor (Ported from gutenberg mobile) (#16239) * Add native support for BlockList to @wordpress/block-editor (Ported from gutenberg mobile) * Fix tests * Exclude flow check from BlockList --- .../src/components/block-list/block.native.js | 4 +- .../src/components/block-list/index.native.js | 283 ++++++++++++++++++ .../components/block-list/style.native.scss | 75 +++++ .../src/components/index.native.js | 3 +- .../mobile/html-text-input/index.native.js | 7 +- .../html-text-input/test/index.native.js | 8 +- .../components/visual-editor/index.native.js | 102 +++++++ .../visual-editor/style.native.scss | 17 ++ packages/edit-post/src/index.native.js | 2 + 9 files changed, 493 insertions(+), 8 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/index.native.js create mode 100644 packages/block-editor/src/components/block-list/style.native.scss create mode 100644 packages/edit-post/src/components/visual-editor/index.native.js create mode 100644 packages/edit-post/src/components/visual-editor/style.native.scss diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 3d93500ce9d888..d8366273376351 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -14,13 +14,15 @@ import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { getBlockType } from '@wordpress/blocks'; -import { BlockEdit, BlockInvalidWarning, BlockMobileToolbar } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import styles from './block.scss'; +import BlockEdit from '../block-edit'; +import BlockInvalidWarning from './block-invalid-warning'; +import BlockMobileToolbar from './block-mobile-toolbar'; class BlockListBlock extends Component { constructor() { diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js new file mode 100644 index 00000000000000..07815c13913025 --- /dev/null +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -0,0 +1,283 @@ +/** + * External dependencies + */ +import { identity } from 'lodash'; +import { Text, View, Keyboard, SafeAreaView, Platform } from 'react-native'; +import { subscribeMediaAppend } from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { Component, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; +import { HTMLTextInput, KeyboardAvoidingView, KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; +import BlockListBlock from './block'; +import BlockToolbar from '../block-toolbar'; +import DefaultBlockAppender from '../default-block-appender'; +import Inserter from '../inserter'; + +const blockMobileToolbarHeight = 44; +const toolbarHeight = 44; + +export class BlockList extends Component { + constructor() { + super( ...arguments ); + + this.renderItem = this.renderItem.bind( this ); + this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); + this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); + this.onBlockTypeSelected = this.onBlockTypeSelected.bind( this ); + this.keyboardDidShow = this.keyboardDidShow.bind( this ); + this.keyboardDidHide = this.keyboardDidHide.bind( this ); + this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( this ); + this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this ); + + this.state = { + blockTypePickerVisible: false, + isKeyboardVisible: false, + }; + } + + // TODO: in the near future this will likely be changed to onShowBlockTypePicker and bound to this.props + // once we move the action to the toolbar + showBlockTypePicker( show ) { + this.setState( { blockTypePickerVisible: show } ); + } + + onBlockTypeSelected( itemValue ) { + this.setState( { blockTypePickerVisible: false } ); + + // create an empty block of the selected type + const newBlock = createBlock( itemValue ); + + this.finishBlockAppendingOrReplacing( newBlock ); + } + + finishBlockAppendingOrReplacing( newBlock ) { + // now determine whether we need to replace the currently selected block (if it's empty) + // or just add a new block as usual + if ( this.isReplaceable( this.props.selectedBlock ) ) { + // do replace here + this.props.replaceBlock( this.props.selectedBlockClientId, newBlock ); + } else { + const indexAfterSelected = this.props.selectedBlockOrder + 1; + const insertionIndex = indexAfterSelected || this.props.blockCount; + this.props.insertBlock( newBlock, insertionIndex ); + } + } + + blockHolderBorderStyle() { + return this.state.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; + } + + componentDidMount() { + this._isMounted = true; + Keyboard.addListener( 'keyboardDidShow', this.keyboardDidShow ); + Keyboard.addListener( 'keyboardDidHide', this.keyboardDidHide ); + + this.subscriptionParentMediaAppend = subscribeMediaAppend( ( payload ) => { + // create an empty media block + const newMediaBlock = createBlock( 'core/' + payload.mediaType ); + + // now set the url and id + if ( payload.mediaType === 'image' ) { + newMediaBlock.attributes.url = payload.mediaUrl; + } else if ( payload.mediaType === 'video' ) { + newMediaBlock.attributes.src = payload.mediaUrl; + } + + newMediaBlock.attributes.id = payload.mediaId; + + // finally append or replace as appropriate + this.finishBlockAppendingOrReplacing( newMediaBlock ); + } ); + } + + componentWillUnmount() { + Keyboard.removeListener( 'keyboardDidShow', this.keyboardDidShow ); + Keyboard.removeListener( 'keyboardDidHide', this.keyboardDidHide ); + + if ( this.subscriptionParentMediaAppend ) { + this.subscriptionParentMediaAppend.remove(); + } + this._isMounted = false; + } + + keyboardDidShow() { + this.setState( { isKeyboardVisible: true } ); + } + + keyboardDidHide() { + this.setState( { isKeyboardVisible: false } ); + } + + onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { + KeyboardAwareFlatList.handleCaretVerticalPositionChange( this.scrollViewRef, targetId, caretY, previousCaretY ); + } + + scrollViewInnerRef( ref ) { + this.scrollViewRef = ref; + } + + shouldFlatListPreventAutomaticScroll() { + return this.state.blockTypePickerVisible; + } + + renderDefaultBlockAppender() { + return ( + <ReadableContentView> + <DefaultBlockAppender + rootClientId={ this.props.rootClientId } + containerStyle={ [ + styles.blockContainerFocused, + this.blockHolderBorderStyle(), + { borderColor: 'transparent' }, + ] } + /> + </ReadableContentView> + ); + } + + renderList() { + return ( + <View + style={ { flex: 1 } } + onAccessibilityEscape={ this.props.clearSelectedBlock } + > + <KeyboardAwareFlatList + { ...( Platform.OS === 'android' ? { removeClippedSubviews: false } : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 + accessibilityLabel="block-list" + innerRef={ this.scrollViewInnerRef } + blockToolbarHeight={ toolbarHeight } + innerToolbarHeight={ blockMobileToolbarHeight } + safeAreaBottomInset={ this.props.safeAreaBottomInset } + parentHeight={ this.props.rootViewHeight } + keyboardShouldPersistTaps="always" + style={ styles.list } + data={ this.props.blockClientIds } + extraData={ [ this.props.isFullyBordered ] } + keyExtractor={ identity } + renderItem={ this.renderItem } + shouldPreventAutomaticScroll={ this.shouldFlatListPreventAutomaticScroll } + title={ this.props.title } + ListHeaderComponent={ this.props.header } + ListEmptyComponent={ this.renderDefaultBlockAppender } + /> + <SafeAreaView> + <View style={ { height: toolbarHeight } } /> + </SafeAreaView> + <KeyboardAvoidingView + style={ styles.blockToolbarKeyboardAvoidingView } + parentHeight={ this.props.rootViewHeight } + > + <BlockToolbar + onInsertClick={ () => { + this.showBlockTypePicker( true ); + } } + showKeyboardHideButton={ this.state.isKeyboardVisible } + /> + </KeyboardAvoidingView> + </View> + ); + } + + render() { + return ( + <Fragment> + { this.renderList() } + { this.state.blockTypePickerVisible && ( + <Inserter + onDismiss={ () => this.showBlockTypePicker( false ) } + onValueSelected={ this.onBlockTypeSelected } + isReplacement={ this.isReplaceable( this.props.selectedBlock ) } + addExtraBottomPadding={ this.props.safeAreaBottomInset === 0 } + /> + ) } + </Fragment> + ); + } + + isReplaceable( block ) { + if ( ! block ) { + return false; + } + return isUnmodifiedDefaultBlock( block ); + } + + renderItem( { item: clientId } ) { + return ( + <ReadableContentView> + <BlockListBlock + key={ clientId } + showTitle={ false } + clientId={ clientId } + rootClientId={ this.props.rootClientId } + onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } + borderStyle={ this.blockHolderBorderStyle() } + focusedBorderColor={ styles.blockHolderFocused.borderColor } + /> + { this.state.blockTypePickerVisible && this.props.isBlockSelected( clientId ) && ( + <View style={ styles.containerStyleAddHere } > + <View style={ styles.lineStyleAddHere }></View> + <Text style={ styles.labelStyleAddHere } >{ __( 'ADD BLOCK HERE' ) }</Text> + <View style={ styles.lineStyleAddHere }></View> + </View> + ) } + </ReadableContentView> + ); + } + + renderHTML() { + return ( + <HTMLTextInput { ...this.props } parentHeight={ this.props.rootViewHeight } /> + ); + } +} + +export default compose( [ + withSelect( ( select, { rootClientId } ) => { + const { + getBlockCount, + getBlockName, + getBlockIndex, + getBlockOrder, + getSelectedBlock, + getSelectedBlockClientId, + isBlockSelected, + } = select( 'core/block-editor' ); + + const selectedBlockClientId = getSelectedBlockClientId(); + + return { + blockClientIds: getBlockOrder( rootClientId ), + blockCount: getBlockCount( rootClientId ), + getBlockName, + isBlockSelected, + selectedBlock: getSelectedBlock(), + selectedBlockClientId, + selectedBlockOrder: getBlockIndex( selectedBlockClientId ), + }; + } ), + withDispatch( ( dispatch ) => { + const { + insertBlock, + replaceBlock, + clearSelectedBlock, + } = dispatch( 'core/block-editor' ); + + return { + clearSelectedBlock, + insertBlock, + replaceBlock, + }; + } ), +] )( BlockList ); + diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss new file mode 100644 index 00000000000000..f856893d5160ad --- /dev/null +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -0,0 +1,75 @@ +.container { + flex: 1; + justify-content: flex-start; + background-color: #fff; +} + +.list { + flex: 1; +} + +.switch { + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 10px; +} + +.switchLabel { + margin-left: 10px; +} + +.lineStyleAddHere { + flex: 1; + background-color: #0087be; // blue_wordpress + align-self: center; + height: 2px; +} + +.labelStyleAddHere { + flex: 1; + text-align: center; + font-family: $default-monospace-font; + font-size: 12px; + font-weight: bold; +} + +.containerStyleAddHere { + flex: 1; + flex-direction: row; + background-color: $white; +} + +.blockToolbarKeyboardAvoidingView { + position: absolute; + bottom: 0; + right: 0; + left: 0; +} + +.blockHolderSemiBordered { + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 0; + border-right-width: 0; +} + +.blockHolderFullBordered { + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-right-width: 1px; +} + + +.blockContainerFocused { + background-color: $white; + padding-left: 16; + padding-right: 16; + padding-top: 12; + padding-bottom: 0; // will be flushed into inline toolbar height +} + +.blockHolderFocused { + border-color: $gray-lighten-30; +} diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 49038ecd6ef2f4..dee7b5fbdeddae 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -19,8 +19,7 @@ export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; // Content Related Components -export { default as BlockListBlock } from './block-list/block'; -export { default as BlockMobileToolbar } from './block-list/block-mobile-toolbar'; +export { default as BlockList } from './block-list'; export { default as BlockMover } from './block-mover'; export { default as BlockToolbar } from './block-toolbar'; export { default as DefaultBlockAppender } from './default-block-appender'; diff --git a/packages/components/src/mobile/html-text-input/index.native.js b/packages/components/src/mobile/html-text-input/index.native.js index 3811085213dd12..e25f7a1af71c4d 100644 --- a/packages/components/src/mobile/html-text-input/index.native.js +++ b/packages/components/src/mobile/html-text-input/index.native.js @@ -70,7 +70,7 @@ export class HTMLTextInput extends Component { style={ styles.htmlViewTitle } value={ this.props.title } placeholder={ __( 'Add title' ) } - onChangeText={ this.props.setTitleAction } + onChangeText={ this.props.editTitle } /> <TextInput autoCorrect={ false } @@ -92,10 +92,12 @@ export class HTMLTextInput extends Component { export default compose( [ withSelect( ( select ) => { const { + getEditedPostAttribute, getEditedPostContent, } = select( 'core/editor' ); return { + title: getEditedPostAttribute( 'title' ), value: getEditedPostContent(), }; } ), @@ -103,6 +105,9 @@ export default compose( [ const { resetBlocks } = dispatch( 'core/block-editor' ); const { editPost } = dispatch( 'core/editor' ); return { + editTitle( title ) { + editPost( { title } ); + }, onChange( content ) { editPost( { content } ); }, diff --git a/packages/components/src/mobile/html-text-input/test/index.native.js b/packages/components/src/mobile/html-text-input/test/index.native.js index 39153dd945cca1..479846d3f6a960 100644 --- a/packages/components/src/mobile/html-text-input/test/index.native.js +++ b/packages/components/src/mobile/html-text-input/test/index.native.js @@ -96,11 +96,11 @@ describe( 'HTMLTextInput', () => { } ); it( 'HTMLTextInput propagates title changes to store', () => { - const setTitleAction = jest.fn(); + const editTitle = jest.fn(); const wrapper = shallow( <HTMLTextInput - setTitleAction={ setTitleAction } + editTitle={ editTitle } /> ); @@ -109,8 +109,8 @@ describe( 'HTMLTextInput', () => { textInput.simulate( 'changeText', 'text' ); //Check if the setTitleAction is called - expect( setTitleAction ).toHaveBeenCalledTimes( 1 ); - expect( setTitleAction ).toHaveBeenCalledWith( 'text' ); + expect( editTitle ).toHaveBeenCalledTimes( 1 ); + expect( editTitle ).toHaveBeenCalledWith( 'text' ); } ); } ); diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js new file mode 100644 index 00000000000000..ddc0995123ff9f --- /dev/null +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -0,0 +1,102 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { BlockEditorProvider, BlockList } from '@wordpress/block-editor'; +import { PostTitle } from '@wordpress/editor'; +import { ReadableContentView } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +class VisualEditor extends Component { + renderHeader() { + const { + editTitle, + setTitleRef, + title, + } = this.props; + + return ( + <ReadableContentView> + <PostTitle + innerRef={ setTitleRef } + title={ title } + onUpdate={ editTitle } + placeholder={ __( 'Add title' ) } + borderStyle={ + this.props.isFullyBordered ? + styles.blockHolderFullBordered : + styles.blockHolderSemiBordered + } + focusedBorderColor={ styles.blockHolderFocused.borderColor } + accessibilityLabel="post-title" + /> + </ReadableContentView> + ); + } + + render() { + const { + blocks, + isFullyBordered, + resetEditorBlocks, + resetEditorBlocksWithoutUndoLevel, + rootViewHeight, + safeAreaBottomInset, + } = this.props; + + return ( + <BlockEditorProvider + value={ blocks } + onInput={ resetEditorBlocksWithoutUndoLevel } + onChange={ resetEditorBlocks } + settings={ null } + > + <BlockList + header={ this.renderHeader() } + isFullyBordered={ isFullyBordered } + rootViewHeight={ rootViewHeight } + safeAreaBottomInset={ safeAreaBottomInset } + /> + </BlockEditorProvider> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + getEditorBlocks, + getEditedPostAttribute, + } = select( 'core/editor' ); + + return { + blocks: getEditorBlocks(), + title: getEditedPostAttribute( 'title' ), + }; + } ), + withDispatch( ( dispatch ) => { + const { + editPost, + resetEditorBlocks, + } = dispatch( 'core/editor' ); + + return { + editTitle( title ) { + editPost( { title } ); + }, + resetEditorBlocks, + resetEditorBlocksWithoutUndoLevel( blocks ) { + resetEditorBlocks( blocks, { + __unstableShouldCreateUndoLevel: false, + } ); + }, + }; + } ), +] )( VisualEditor ); diff --git a/packages/edit-post/src/components/visual-editor/style.native.scss b/packages/edit-post/src/components/visual-editor/style.native.scss new file mode 100644 index 00000000000000..02b49a1515584c --- /dev/null +++ b/packages/edit-post/src/components/visual-editor/style.native.scss @@ -0,0 +1,17 @@ +.blockHolderSemiBordered { + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 0; + border-right-width: 0; +} + +.blockHolderFullBordered { + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-right-width: 1px; +} + +.blockHolderFocused { + border-color: $gray-lighten-30; +} diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 1abd926f26bc8c..6a2d926d2c193d 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -11,6 +11,8 @@ import { unregisterBlockType } from '@wordpress/blocks'; */ import './store'; +export { default as VisualEditor } from './components/visual-editor'; + /** * Initializes the Editor. */ From af6e5ecd8b398e08127ad1b3c784ce97899de100 Mon Sep 17 00:00:00 2001 From: Maxime Jobin <maximejobin@users.noreply.github.com> Date: Sat, 22 Jun 2019 03:20:09 -0400 Subject: [PATCH 375/664] Update the text as Gutenberg was officially released. (#15394) * Update the text as Gutenberg was officially released. * Update docs/designers-developers/faq.md Co-Authored-By: maximejobin <maximejobin@users.noreply.github.com> * Change nested blocks answer. --- docs/designers-developers/faq.md | 46 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/designers-developers/faq.md b/docs/designers-developers/faq.md index a59f5b3e4e18be..154055b1a6f431 100644 --- a/docs/designers-developers/faq.md +++ b/docs/designers-developers/faq.md @@ -18,7 +18,7 @@ Gutenberg is being developed on [GitHub](https://github.com/WordPress/gutenberg) ## When will Gutenberg be merged into WordPress? -Gutenberg will be merged into WordPress 5.0, and will be released at the end of 2018, or early 2019. Please follow [WordPress.org News](https://wordpress.org/news/) for the latest information. +Gutenberg was merged into [WordPress 5.0](https://wordpress.org/news/2018/12/bebo/) that was released in December 2018. The editor focus started in early 2017 with the first three months spent designing, planning, prototyping, and testing prototypes, to help us inform how to approach this project. The actual plugin, which you can install from the repository, was launched during WordCamp Europe in June. @@ -37,7 +37,7 @@ As we thought about these uses and how to make them obvious and consistent, we b ## What is the writing experience like? -Our goal with Gutenberg is not just to create a seamless post- and page-building experience. We also want to ensure that it provides a seamless writing experience. Though individual paragraphs of text will become their own “blocks,” the creation and editing of these blocks are being designed in a way that could be just as simple—if not more so—than the current WordPress editor experience. Here is a brief animation illustrating the Gutenberg writing experience: +Our goal with Gutenberg is not just to create a seamless post- and page-building experience. We also want to ensure that it provides a seamless writing experience. Though individual paragraphs of text become their own “blocks,” the creation and editing of these blocks are being designed in a way that could be just as simple—if not more so—than the current WordPress editor experience. Here is a brief animation illustrating the Gutenberg writing experience: ![Typing](https://make.wordpress.org/core/files/2017/10/gutenberg-typing-1_6.gif) @@ -243,9 +243,9 @@ Here is a brief animation illustrating how to find and use the keyboard shortcut No. [TinyMCE](https://www.tinymce.com/) is only used for the "Classic" block. -## What browsers will Gutenberg support? +## What browsers does Gutenberg support? -Gutenberg will work in modern browsers, and Internet Explorer 11. +Gutenberg works in modern browsers, and Internet Explorer 11. Our [list of supported browsers can be found in the Make WordPress handbook](https://make.wordpress.org/core/handbook/best-practices/browser-support/). By “modern browsers” we generally mean the *current and past two versions* of each major browser. @@ -257,7 +257,7 @@ The API for creating blocks is a crucial aspect of the project. We are working o No, we are designing Gutenberg primarily as a replacement for the post and page editing screens. That said, front-end editing is often confused with an editor that looks exactly like the front end. And that is something that Gutenberg will allow as themes customize individual blocks and provide those styles to the editor. Since content is designed to be distributed across so many different experiences—from desktop and mobile to full-text feeds and syndicated article platforms—we believe it's not ideal to create or design posts from just one front-end experience. -## Given Gutenberg is built in JavaScript, how will old meta boxes (PHP) work? +## Given Gutenberg is built in JavaScript, how do old meta boxes (PHP) work? We plan to continue supporting existing meta boxes while providing new ways to extend the interface. @@ -267,21 +267,19 @@ We plan to continue supporting existing meta boxes while providing new ways to e The main extension point we want to emphasize is creating new blocks. We are still working on how to extend the rest of the UI that is built in JS. We are tracking it here: [Issue #1352](https://github.com/WordPress/gutenberg/issues/1352) -## Will Custom Post Types be supported? +## Are Custom Post Types still supported? Indeed. There are multiple ways in which custom post types can leverage Gutenberg. The plan is to allow them to specify the blocks they support, as well as defining a default block for the post type. It's not currently the case, but if a post type disables the content field, the “advanced” section at the bottom would fill the page. -## Will there be columns? +## Does Gutenberg support columns? Yes, a columns block is available in Gutenberg. -## Will there be nested blocks? +## Does Gutenberg support nested blocks? -We are currently implementing the infrastructure for nested blocks support. We expect this to open further customization opportunities. Block authors also can nest components and HTML inside of a block during construction. The UI for manipulating nested blocks is still being refined, and depending on the timing, it might not be included in the first version of Gutenberg. +Yes, it is supported. You can have multiple levels of nesting – blocks within blocks within blocks. -See also [Issue #428](https://github.com/WordPress/gutenberg/issues/428) - -## Will drag and drop be used for rearranging blocks? +## Does drag and drop work for rearranging blocks? Yes, you can drag and drop blocks to rearrange their order. @@ -289,17 +287,17 @@ Yes, you can drag and drop blocks to rearrange their order. Yes. Blocks can provide their own styles, which themes can add to or override, or they can provide no styles at all and rely fully on what the theme provides. -## How will block styles work in both the front-end and back-end? +## How do block styles work in both the front-end and back-end? -Blocks will be able to provide base structural CSS styles, and themes can add styles on top of this. Some blocks, like a Separator (`<hr/>`), likely won't need any front-end styles, while others, like a Gallery, need a few. +Blocks are able to provide base structural CSS styles, and themes can add styles on top of this. Some blocks, like a Separator (`<hr/>`), likely don't need any front-end styles, while others, like a Gallery, need a few. -Other features, like the new _wide_ and _full-wide_ alignment options, will simply be CSS classes applied to blocks that offer this alignment. We are looking at how a theme can opt in to this feature, for example using `add_theme_support`. +Other features, like the new _wide_ and _full-wide_ alignment options, are simply CSS classes applied to blocks that offer this alignment. We are looking at how a theme can opt in to this feature, for example using `add_theme_support`. *See:* [Theme Support](/docs/designers-developers/developers/themes/theme-support.md) -## How will editor styles work? +## How do editor styles work? -Regular editor styles are opt-in and will work as is in most cases. Themes can also load extra stylesheets by using the following hook: +Regular editor styles are opt-in and work as is in most cases. Themes can also load extra stylesheets by using the following hook: ```php function gutenbergtheme_editor_styles() { @@ -318,22 +316,22 @@ Aside from enabling a rich post and page building experience, a meta goal is to We realize it's a big change. We also think there will be many new opportunities for plugins. WordPress is likely to ship with a range of basic blocks, but there will be plenty of room for highly tailored premium plugins to augment existing blocks or add new blocks to the mix. -## Will I be able to opt out of Gutenberg for my site? +## Is it possible to opt out of Gutenberg for my site? There is a “Classic” block, which is virtually the same as the current editor, except in block form. There is also the [Classic Editor plugin](https://wordpress.org/plugins/classic-editor/) which restores the previous editor, see the plugin for more information. The WordPress Core team has committed to supporting the Classic Editor plugin [until December 2021](https://make.wordpress.org/core/2018/11/07/classic-editor-plugin-support-window/). -## How will custom TinyMCE buttons work in Gutenberg? +## How do custom TinyMCE buttons work in Gutenberg? -Custom TinyMCE buttons will still work in the “Classic” block, which is a block version of the classic editor you know today. +Custom TinyMCE buttons still work in the “Classic” block, which is a block version of the classic editor you know today. (Gutenberg comes with a new universal inserter tool, which gives you access to every block available, searchable, sorted by recency and categories. This inserter tool levels the playing field for every plugin that adds content to the editor, and provides a single interface to learn how to use.) -## How will shortcodes work in Gutenberg? +## How do shortcodes work in Gutenberg? -Shortcodes will continue to work as they do now. +Shortcodes continue to work as they do now. However we see the block as an evolution of the `[shortcode]`. Instead of having to type out code, you can use the universal inserter tray to pick a block and get a richer interface for both configuring the block and previewing it. We would recommend people eventually upgrade their shortcodes to be blocks. @@ -341,7 +339,7 @@ However we see the block as an evolution of the `[shortcode]`. Instead of having We think so. Blocks are designed to be visually representative of the final look, and they will likely become the expected way in which users will discover and insert content in WordPress. -## Will Gutenberg be made properly accessible? +## Is Gutenberg made to be properly accessible? Accessibility is not an afterthought. Not every aspect of Gutenberg is accessible at the moment. You can check logged issues [here](https://github.com/WordPress/gutenberg/labels/Accessibility). We understand that WordPress is for everyone, and that accessibility is about inclusion. This is a key value for us. @@ -378,7 +376,7 @@ As part of the focus on the editor in 2017, a focus on customization and sitebui With the editor, we lay the foundation for bigger things when it comes to page building and customization. -A lot of features are planned, too many to list. But a rough roadmap is: v1) post and page editor v2) page template editor, v3) site builder. +A lot of features are planned, too many to list here. You can check [Gutenberg's roadmap](https://github.com/WordPress/gutenberg/blob/master/docs/roadmap.md) for more details. ## WordPress is already the world's most popular publishing platform. Why change the editor at all? As an open-source project, we believe that it is critical for WordPress to continue to innovate and keep working to make the core experience intuitive and enjoyable for all users. As a community project, Gutenberg has the potential to do just that, and we're excited to pursue this goal together. If you'd like to test, contribute, or offer feedback, [we welcome it here](http://wordpressdotorg.polldaddy.com/s/gutenberg-support). From 4246e7d9c783b4f6c6d7892c8d1b8829ff8b707b Mon Sep 17 00:00:00 2001 From: Joen Asmussen <joen@automattic.com> Date: Mon, 24 Jun 2019 13:55:02 +0200 Subject: [PATCH 376/664] Fix h1 heading typo so h1 is same size as title (#16253) Fixes #11223. This is the smallest PR I've ever made. It's one character. But it does fix a typo so the font size of the post title is the same as the h1. --- packages/editor/src/components/post-title/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-title/style.scss b/packages/editor/src/components/post-title/style.scss index feff1a077f8964..8f758e9947ae76 100644 --- a/packages/editor/src/components/post-title/style.scss +++ b/packages/editor/src/components/post-title/style.scss @@ -36,7 +36,7 @@ } // Match h1 heading. - font-size: 2.441em; + font-size: 2.44em; font-weight: 600; // Large text needs a 3:1 contrast ratio. From 1465f21881e8835d9dd918c3e4a841b3070f1cf5 Mon Sep 17 00:00:00 2001 From: Martin Splitt <mr.avgp@googlemail.com> Date: Mon, 24 Jun 2019 16:59:23 +0200 Subject: [PATCH 377/664] Makes cleanForSlug more robust when receiving falsy values. (#16236) --- packages/editor/src/utils/test/url.js | 8 ++++++++ packages/editor/src/utils/url.js | 3 +++ 2 files changed, 11 insertions(+) diff --git a/packages/editor/src/utils/test/url.js b/packages/editor/src/utils/test/url.js index 9aca0fc8503e50..50be061f75028f 100644 --- a/packages/editor/src/utils/test/url.js +++ b/packages/editor/src/utils/test/url.js @@ -7,4 +7,12 @@ describe( 'cleanForSlug()', () => { it( 'Should return string prepared for use as url slug', () => { expect( cleanForSlug( ' /Déjà_vu. ' ) ).toBe( 'deja-vu' ); } ); + + it( 'Should return an empty string for missing argument', () => { + expect( cleanForSlug() ).toBe( '' ); + } ); + + it( 'Should return an empty string for falsy argument', () => { + expect( cleanForSlug( null ) ).toBe( '' ); + } ); } ); diff --git a/packages/editor/src/utils/url.js b/packages/editor/src/utils/url.js index 371cdedc93ff45..8eb941d30b39d1 100644 --- a/packages/editor/src/utils/url.js +++ b/packages/editor/src/utils/url.js @@ -39,5 +39,8 @@ export function getWPAdminURL( page, query ) { * @return {string} Processed string */ export function cleanForSlug( string ) { + if ( ! string ) { + return ''; + } return toLower( deburr( trim( string.replace( /[\s\./_]+/g, '-' ), '-' ) ) ); } From c3f65fba62cfa832ba15afeee8520d010f300e14 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 24 Jun 2019 12:15:37 -0400 Subject: [PATCH 378/664] Switch to flex-basis instead of width to prevent zoom issues. (#15984) --- .../editor/src/components/table-of-contents/style.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/table-of-contents/style.scss b/packages/editor/src/components/table-of-contents/style.scss index 2365aa4c534387..4c10c825a41f7d 100644 --- a/packages/editor/src/components/table-of-contents/style.scss +++ b/packages/editor/src/components/table-of-contents/style.scss @@ -29,12 +29,17 @@ } .table-of-contents__count { - width: 25%; + flex-basis: 25%; display: flex; flex-direction: column; font-size: $default-font-size; color: $dark-gray-300; + padding-right: $grid-size; margin-bottom: 0; + + &:last-child { + padding-right: 0; + } } .table-of-contents__number, From 855be7927f31cf89229e954829e78e23d6a871e8 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 24 Jun 2019 19:33:54 +0200 Subject: [PATCH 379/664] Add Columns template options, support InnerBlock templateOptions (#16129) --- .../src/components/inner-blocks/README.md | 63 ++++++ .../src/components/inner-blocks/index.js | 24 ++- .../src/components/inner-blocks/style.scss | 71 +++++++ .../inner-blocks/template-picker.js | 67 +++++++ packages/block-library/src/columns/block.json | 4 - .../block-library/src/columns/deprecated.js | 34 +++- packages/block-library/src/columns/edit.js | 184 ++++++++++++++---- packages/block-library/src/columns/save.js | 4 +- .../block-library/src/columns/test/utils.js | 6 + packages/block-library/src/columns/utils.js | 4 + .../fixtures/blocks/core__columns.html | 4 +- .../fixtures/blocks/core__columns.json | 6 +- .../fixtures/blocks/core__columns.parsed.json | 8 +- .../blocks/core__columns.serialized.html | 4 +- .../blocks/core__columns__deprecated.json | 4 +- .../core__columns__deprecated.serialized.html | 4 +- packages/e2e-tests/plugins/templates.php | 2 +- .../block-hierarchy-navigation.test.js.snap | 8 +- .../__snapshots__/writing-flow.test.js.snap | 2 +- .../specs/block-hierarchy-navigation.test.js | 2 + .../e2e-tests/specs/blocks/columns.test.js | 1 + .../__snapshots__/templates.test.js.snap | 4 +- packages/e2e-tests/specs/writing-flow.test.js | 1 + .../full-content/server-registered.json | 2 +- 24 files changed, 433 insertions(+), 80 deletions(-) create mode 100644 packages/block-editor/src/components/inner-blocks/template-picker.js diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index ef8b015b713b50..303e24730f7fe3 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -90,6 +90,69 @@ const TEMPLATE = [ [ 'core/columns', {}, [ The previous example creates an InnerBlocks area containing two columns one with an image and the other with a paragraph. +### `__experimentalTemplateOptions` + +* **Type:** `Array<Object>` + +To present the user with a set of template choices for the inner blocks, you may provide an array of template options. + +A template option is an object consisting of the following properties: + +- `title` (`string`): A human-readable label which describes the template. +- `icon` (`WPElement|string`): An element or [Dashicon](https://developer.wordpress.org/resource/dashicons/) slug to show as a visual approximation of the template. +- `template` (`Array<Array>`): The template to apply when the option has been selected. See [`template` documentation](#template) for more information. + +For the template placeholder selection to be displayed, you must render `InnerBlocks` with a `template` prop value of `null`. You may track this as state of your component, updating its value when receiving the selected template via `__experimentalOnSelectTemplateOption`. + +```jsx +import { useState } from '@wordpress/element'; + +const TEMPLATE_OPTIONS = [ + { + title: 'Two Columns', + icon: <svg />, + template: [ + [ 'core/column', { width: 50 } ], + [ 'core/column', { width: 50 } ], + ], + }, + { + title: 'Three Columns', + icon: <svg />, + template: [ + [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: 33.33 } ], + ], + }, +]; + +function edit() { + const [ template, setTemplate ] = useState( null ); + + return ( + <InnerBlocks + template={ template } + __experimentalTemplateOptions={ TEMPLATE_OPTIONS } + __experimentalOnSelectTemplateOption={ setTemplate } + /> + ); +} +``` + +### `__experimentalOnSelectTemplateOption` + +* **Type:** `Function` + +Callback function invoked when the user makes a template selection, used in combination with the `__experimentalTemplateOptions` props. The selected template is passed as the first and only argument. The value of the template may be `undefined` if the `__experimentalAllowTemplateOptionSkip` prop is passed to `InnerBlocks` and the user opts to skip template selection. + +### `__experimentalAllowTemplateOptionSkip` + +* **Type:** `Boolean` +* **Default:** `false` + +Whether to include a button in the template selection placeholder to allow the user to skip selection, used in combination with the `__experimentalTemplateOptions` prop. When clicked, the `__experimentalOnSelectTemplateOption` callback will be passed an `undefined` value as the template. + ### `templateInsertUpdatesSelection` * **Type:** `Boolean` * **Default:** `true` diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 75a6731f42db57..0d1feef14ff39d 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -24,6 +24,7 @@ import DefaultBlockAppender from './default-block-appender'; */ import BlockList from '../block-list'; import { withBlockEditContext } from '../block-edit/context'; +import TemplatePicker from './template-picker'; class InnerBlocks extends Component { constructor() { @@ -48,6 +49,7 @@ class InnerBlocks extends Component { if ( innerBlocks.length === 0 || this.getTemplateLock() === 'all' ) { this.synchronizeBlocksWithTemplate(); } + if ( this.state.templateInProcess ) { this.setState( { templateInProcess: false, @@ -107,20 +109,32 @@ class InnerBlocks extends Component { clientId, hasOverlay, renderAppender, + template, + __experimentalTemplateOptions: templateOptions, + __experimentalOnSelectTemplateOption: onSelectTemplateOption, + __experimentalAllowTemplateOptionSkip: allowTemplateOptionSkip, } = this.props; const { templateInProcess } = this.state; + const isPlaceholder = template === null && !! templateOptions; + const classes = classnames( 'editor-inner-blocks block-editor-inner-blocks', { - 'has-overlay': hasOverlay, + 'has-overlay': hasOverlay && ! isPlaceholder, } ); return ( <div className={ classes }> { ! templateInProcess && ( - <BlockList - rootClientId={ clientId } - renderAppender={ renderAppender } - /> + isPlaceholder ? + <TemplatePicker + options={ templateOptions } + onSelect={ onSelectTemplateOption } + allowSkip={ allowTemplateOptionSkip } + /> : + <BlockList + rootClientId={ clientId } + renderAppender={ renderAppender } + /> ) } </div> ); diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index ff8e4b9adbe96a..0f8576fd5ecf34 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -17,3 +17,74 @@ right: 0; left: 0; } + +.block-editor-inner-blocks__template-picker { + .components-placeholder__instructions { + // Defer to vertical margins applied by template picker options. + margin-bottom: 0; + } + + .components-placeholder__fieldset { + // Options will render horizontally, but the immediate children of the + // fieldset are the options and the skip button, oriented vertically. + flex-direction: column; + } + + &.has-many-options .components-placeholder__fieldset { + // Allow options to occupy a greater amount of the available space if + // many options exist. + max-width: 90%; + } +} + +.block-editor-inner-blocks__template-picker-options.block-editor-inner-blocks__template-picker-options { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + width: 100%; + margin: $grid-size-large 0; + list-style: none; + + > li { + list-style: none; + flex-basis: 100%; + flex-shrink: 1; + margin: 0 $grid-size; + max-width: 100px; + } +} + +.block-editor-inner-blocks__template-picker-option { + width: 100%; + + &.components-icon-button { + // Override default styles inherited from <IconButton /> to center + // icon horizontally. + justify-content: center; + + &.is-default { + background-color: $white; + } + } + + &.components-button { + // Override default styles inherited from <Button /> to allow button + // to grow vertically. + height: auto; + padding: 0; + } + + &::before { + // Use `padding-bottom` trick to style element as perfect square. + content: ""; + padding-bottom: 100%; + } + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } +} diff --git a/packages/block-editor/src/components/inner-blocks/template-picker.js b/packages/block-editor/src/components/inner-blocks/template-picker.js new file mode 100644 index 00000000000000..8afc65a34e25eb --- /dev/null +++ b/packages/block-editor/src/components/inner-blocks/template-picker.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button, IconButton, Placeholder } from '@wordpress/components'; + +function InnerBlocksTemplatePicker( { + options, + onSelect, + allowSkip, +} ) { + const classes = classnames( 'block-editor-inner-blocks__template-picker', { + 'has-many-options': options.length > 4, + } ); + + const instructions = allowSkip ? + __( 'Select a layout to start with, or make one yourself.' ) : + __( 'Select a layout to start with.' ); + + return ( + <Placeholder + icon="layout" + label={ __( 'Choose Layout' ) } + instructions={ instructions } + className={ classes } + > + { + /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ + } + <ul className="block-editor-inner-blocks__template-picker-options" role="list"> + { options.map( ( templateOption, index ) => ( + <li key={ index }> + <IconButton + isLarge + icon={ templateOption.icon } + onClick={ () => onSelect( templateOption.template ) } + className="block-editor-inner-blocks__template-picker-option" + label={ templateOption.title } + /> + </li> + ) ) } + </ul> + { /* eslint-enable jsx-a11y/no-redundant-roles */ } + { allowSkip && ( + <div className="block-editor-inner-blocks__template-picker-skip"> + <Button + isLink + onClick={ () => onSelect( undefined ) } + > + { __( 'Skip' ) } + </Button> + </div> + ) } + </Placeholder> + ); +} + +export default InnerBlocksTemplatePicker; diff --git a/packages/block-library/src/columns/block.json b/packages/block-library/src/columns/block.json index 267aef219883e2..3c22ca71fba621 100644 --- a/packages/block-library/src/columns/block.json +++ b/packages/block-library/src/columns/block.json @@ -2,10 +2,6 @@ "name": "core/columns", "category": "layout", "attributes": { - "columns": { - "type": "number", - "default": 2 - }, "verticalAlignment": { "type": "string" } diff --git a/packages/block-library/src/columns/deprecated.js b/packages/block-library/src/columns/deprecated.js index 7d3d16c770adbf..56ffe5d90bc6a1 100644 --- a/packages/block-library/src/columns/deprecated.js +++ b/packages/block-library/src/columns/deprecated.js @@ -1,3 +1,9 @@ +/** + * External dependencies + */ +import { omit } from 'lodash'; +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -82,7 +88,7 @@ export default [ ) ); return [ - attributes, + omit( attributes, [ 'columns' ] ), migratedInnerBlocks, ]; }, @@ -96,4 +102,30 @@ export default [ ); }, }, + { + attributes: { + columns: { + type: 'number', + default: 2, + }, + }, + migrate( attributes, innerBlocks ) { + attributes = omit( attributes, [ 'columns' ] ); + + return [ attributes, innerBlocks ]; + }, + save( { attributes } ) { + const { verticalAlignment, columns } = attributes; + + const wrapperClasses = classnames( `has-${ columns }-columns`, { + [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + } ); + + return ( + <div className={ wrapperClasses }> + <InnerBlocks.Content /> + </div> + ); + }, + }, ]; diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 71625625233815..d573500cd551d9 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { dropRight } from 'lodash'; +import { dropRight, times } from 'lodash'; /** * WordPress dependencies @@ -11,6 +11,8 @@ import { __ } from '@wordpress/i18n'; import { PanelBody, RangeControl, + SVG, + Path, } from '@wordpress/components'; import { InspectorControls, @@ -18,8 +20,9 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { withDispatch } from '@wordpress/data'; +import { withDispatch, useSelect } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; +import { useState, useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -43,40 +46,133 @@ import { */ const ALLOWED_BLOCKS = [ 'core/column' ]; +/** + * Template option choices for predefined columns layouts. + * + * @constant + * @type {Array} + */ +const TEMPLATE_OPTIONS = [ + { + title: __( 'Two columns; equal split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H25V34H39ZM23 34H9V14H23V34Z" /></SVG>, + template: [ + [ 'core/column' ], + [ 'core/column' ], + ], + }, + { + title: __( 'Two columns; one-third, two-thirds split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H20V34H39ZM18 34H9V14H18V34Z" /></SVG>, + template: [ + [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: 66.66 } ], + ], + }, + { + title: __( 'Two columns; two-thirds, one-third split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H30V34H39ZM28 34H9V14H28V34Z" /></SVG>, + template: [ + [ 'core/column', { width: 66.66 } ], + [ 'core/column', { width: 33.33 } ], + ], + }, + { + title: __( 'Three columns; equal split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM28.5 34h-9V14h9v20zm2 0V14H39v20h-8.5zm-13 0H9V14h8.5v20z" /></SVG>, + template: [ + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + ], + }, + { + title: __( 'Three columns; wide center column' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM31 34H17V14h14v20zm2 0V14h6v20h-6zm-18 0H9V14h6v20z" /></SVG>, + template: [ + [ 'core/column', { width: 25 } ], + [ 'core/column', { width: 50 } ], + [ 'core/column', { width: 25 } ], + ], + }, +]; + +/** + * Number of columns to assume for template in case the user opts to skip + * template option selection. + * + * @type {Number} + */ +const DEFAULT_COLUMNS = 2; + export function ColumnsEdit( { attributes, className, updateAlignment, updateColumns, + clientId, } ) { - const { columns, verticalAlignment } = attributes; + const { verticalAlignment } = attributes; + + const { count } = useSelect( ( select ) => { + return { + count: select( 'core/block-editor' ).getBlockCount( clientId ), + }; + } ); + const [ template, setTemplate ] = useState( getColumnsTemplate( count ) ); + const [ forceUseTemplate, setForceUseTemplate ] = useState( false ); + + // This is used to force the usage of the template even if the count doesn't match the template + // The count doesn't match the template once you use undo/redo (this is used to reset to the placeholder state). + useEffect( () => { + // Once the template is applied, reset it. + if ( forceUseTemplate ) { + setForceUseTemplate( false ); + } + }, [ forceUseTemplate ] ); - const classes = classnames( className, `has-${ columns }-columns`, { + const classes = classnames( className, { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); return ( <> - <InspectorControls> - <PanelBody> - <RangeControl - label={ __( 'Columns' ) } - value={ columns } - onChange={ updateColumns } - min={ 2 } - max={ 6 } - /> - </PanelBody> - </InspectorControls> - <BlockControls> - <BlockVerticalAlignmentToolbar - onChange={ updateAlignment } - value={ verticalAlignment } - /> - </BlockControls> + { template && ( + <> + <InspectorControls> + <PanelBody> + <RangeControl + label={ __( 'Columns' ) } + value={ count } + onChange={ ( value ) => updateColumns( count, value ) } + min={ 2 } + max={ 6 } + /> + </PanelBody> + </InspectorControls> + <BlockControls> + <BlockVerticalAlignmentToolbar + onChange={ updateAlignment } + value={ verticalAlignment } + /> + </BlockControls> + </> + ) } <div className={ classes }> <InnerBlocks - template={ getColumnsTemplate( columns ) } + __experimentalTemplateOptions={ TEMPLATE_OPTIONS } + __experimentalOnSelectTemplateOption={ ( nextTemplate ) => { + if ( nextTemplate === undefined ) { + nextTemplate = getColumnsTemplate( DEFAULT_COLUMNS ); + } + + setTemplate( nextTemplate ); + setForceUseTemplate( true ); + } } + __experimentalAllowTemplateOptionSkip + // setting the template to null when the inner blocks + // are empty allows to reset to the placeholder state. + template={ count === 0 && ! forceUseTemplate ? null : template } templateLock="all" allowedBlocks={ ALLOWED_BLOCKS } /> </div> @@ -113,29 +209,24 @@ export default withDispatch( ( dispatch, ownProps, registry ) => ( { * Updates the column count, including necessary revisions to child Column * blocks to grant required or redistribute available space. * - * @param {number} columns New column count. + * @param {number} previousColumns Previous column count. + * @param {number} newColumns New column count. */ - updateColumns( columns ) { - const { clientId, setAttributes, attributes } = ownProps; + updateColumns( previousColumns, newColumns ) { + const { clientId } = ownProps; const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); const { getBlocks } = registry.select( 'core/block-editor' ); - // Update columns count. - setAttributes( { columns } ); - let innerBlocks = getBlocks( clientId ); - if ( ! hasExplicitColumnWidths( innerBlocks ) ) { - return; - } + const hasExplicitWidths = hasExplicitColumnWidths( innerBlocks ); // Redistribute available width for existing inner blocks. - const { columns: previousColumns } = attributes; - const isAddingColumn = columns > previousColumns; + const isAddingColumn = newColumns > previousColumns; - if ( isAddingColumn ) { + if ( isAddingColumn && hasExplicitWidths ) { // If adding a new column, assign width to the new column equal to // as if it were `1 / columns` of the total available space. - const newColumnWidth = toWidthPrecision( 100 / columns ); + const newColumnWidth = toWidthPrecision( 100 / newColumns ); // Redistribute in consideration of pending block insertion as // constraining the available working width. @@ -143,18 +234,29 @@ export default withDispatch( ( dispatch, ownProps, registry ) => ( { innerBlocks = [ ...getMappedColumnWidths( innerBlocks, widths ), - createBlock( 'core/column', { - width: newColumnWidth, + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column', { + width: newColumnWidth, + } ); + } ), + ]; + } else if ( isAddingColumn ) { + innerBlocks = [ + ...innerBlocks, + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column' ); } ), ]; } else { // The removed column will be the last of the inner blocks. - innerBlocks = dropRight( innerBlocks ); + innerBlocks = dropRight( innerBlocks, previousColumns - newColumns ); - // Redistribute as if block is already removed. - const widths = getRedistributedColumnWidths( innerBlocks, 100 ); + if ( hasExplicitWidths ) { + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( innerBlocks, 100 ); - innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } } replaceInnerBlocks( clientId, innerBlocks, false ); diff --git a/packages/block-library/src/columns/save.js b/packages/block-library/src/columns/save.js index 851a4270f7a4d6..8f9b8c2d673620 100644 --- a/packages/block-library/src/columns/save.js +++ b/packages/block-library/src/columns/save.js @@ -9,9 +9,9 @@ import classnames from 'classnames'; import { InnerBlocks } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { columns, verticalAlignment } = attributes; + const { verticalAlignment } = attributes; - const wrapperClasses = classnames( `has-${ columns }-columns`, { + const wrapperClasses = classnames( { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); diff --git a/packages/block-library/src/columns/test/utils.js b/packages/block-library/src/columns/test/utils.js index cb69e1740e1f32..b89a63e2e01533 100644 --- a/packages/block-library/src/columns/test/utils.js +++ b/packages/block-library/src/columns/test/utils.js @@ -24,6 +24,12 @@ describe( 'getColumnsTemplate', () => { [ 'core/column' ], ] ); } ); + + it( 'should return null if columns count is not defined', () => { + const template = getColumnsTemplate( undefined ); + + expect( template ).toBe( null ); + } ); } ); describe( 'toWidthPrecision', () => { diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index 0c4e4c59e9ccd3..77a0b7cf4375a8 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -12,6 +12,10 @@ import { times, findIndex, sumBy, merge, mapValues } from 'lodash'; * @return {Object[]} Columns layout configuration. */ export const getColumnsTemplate = memoize( ( columns ) => { + if ( columns === undefined ) { + return null; + } + return times( columns, () => [ 'core/column' ] ); } ); diff --git a/packages/e2e-tests/fixtures/blocks/core__columns.html b/packages/e2e-tests/fixtures/blocks/core__columns.html index bcced4e7b9f244..10316fc04d135a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__columns.html +++ b/packages/e2e-tests/fixtures/blocks/core__columns.html @@ -1,5 +1,5 @@ -<!-- wp:columns {"columns":3} --> -<div class="wp-block-columns has-3-columns"> +<!-- wp:columns --> +<div class="wp-block-columns"> <!-- wp:column --> <div class="wp-block-column"> <!-- wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__columns.json b/packages/e2e-tests/fixtures/blocks/core__columns.json index 2a446b72dc79d0..d2c4a123741dc5 100644 --- a/packages/e2e-tests/fixtures/blocks/core__columns.json +++ b/packages/e2e-tests/fixtures/blocks/core__columns.json @@ -3,9 +3,7 @@ "clientId": "_clientId_0", "name": "core/columns", "isValid": true, - "attributes": { - "columns": 3 - }, + "attributes": {}, "innerBlocks": [ { "clientId": "_clientId_0", @@ -70,6 +68,6 @@ "originalContent": "<div class=\"wp-block-column\">\n\t\t\n\t\t\n\t</div>" } ], - "originalContent": "<div class=\"wp-block-columns has-3-columns\">\n\t\n\t\n</div>" + "originalContent": "<div class=\"wp-block-columns\">\n\t\n\t\n</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__columns.parsed.json b/packages/e2e-tests/fixtures/blocks/core__columns.parsed.json index 7dbde278bc08c6..e3a1d3c00b0b15 100644 --- a/packages/e2e-tests/fixtures/blocks/core__columns.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__columns.parsed.json @@ -1,9 +1,7 @@ [ { "blockName": "core/columns", - "attrs": { - "columns": 3 - }, + "attrs": {}, "innerBlocks": [ { "blockName": "core/column", @@ -70,9 +68,9 @@ ] } ], - "innerHTML": "\n<div class=\"wp-block-columns has-3-columns\">\n\t\n\t\n</div>\n", + "innerHTML": "\n<div class=\"wp-block-columns\">\n\t\n\t\n</div>\n", "innerContent": [ - "\n<div class=\"wp-block-columns has-3-columns\">\n\t", + "\n<div class=\"wp-block-columns\">\n\t", null, "\n\t", null, diff --git a/packages/e2e-tests/fixtures/blocks/core__columns.serialized.html b/packages/e2e-tests/fixtures/blocks/core__columns.serialized.html index b7472ac094fbbb..47dcbe2fa69175 100644 --- a/packages/e2e-tests/fixtures/blocks/core__columns.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__columns.serialized.html @@ -1,5 +1,5 @@ -<!-- wp:columns {"columns":3} --> -<div class="wp-block-columns has-3-columns"><!-- wp:column --> +<!-- wp:columns --> +<div class="wp-block-columns"><!-- wp:column --> <div class="wp-block-column"><!-- wp:paragraph --> <p>Column One, Paragraph One</p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.json index 6993fb5da69c4a..c5ad0b8151b8e3 100644 --- a/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.json +++ b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.json @@ -3,9 +3,7 @@ "clientId": "_clientId_0", "name": "core/columns", "isValid": true, - "attributes": { - "columns": 3 - }, + "attributes": {}, "innerBlocks": [ { "clientId": "_clientId_0", diff --git a/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.serialized.html index 88041c92cb39d3..d8271e568f7af3 100644 --- a/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__columns__deprecated.serialized.html @@ -1,5 +1,5 @@ -<!-- wp:columns {"columns":3} --> -<div class="wp-block-columns has-3-columns"><!-- wp:column --> +<!-- wp:columns --> +<div class="wp-block-columns"><!-- wp:column --> <div class="wp-block-column"><!-- wp:paragraph {"className":"layout-column-1"} --> <p class="layout-column-1">Column One, Paragraph One</p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/plugins/templates.php b/packages/e2e-tests/plugins/templates.php index b4d399905c68eb..f245813dfce294 100644 --- a/packages/e2e-tests/plugins/templates.php +++ b/packages/e2e-tests/plugins/templates.php @@ -26,7 +26,7 @@ function gutenberg_test_templates_register_book_type() { array( 'core/quote' ), array( 'core/columns', - array(), + array( 'columns' => 2 ), array( array( 'core/column', diff --git a/packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap index dc278255bc8565..4ee1d7710bbe18 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap @@ -11,8 +11,8 @@ exports[`Navigating the block hierarchy should appear and function even without `; exports[`Navigating the block hierarchy should navigate block hierarchy using only the keyboard 1`] = ` -"<!-- wp:columns {\\"columns\\":3} --> -<div class=\\"wp-block-columns has-3-columns\\"><!-- wp:column --> +"<!-- wp:columns --> +<div class=\\"wp-block-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> <p>First column</p> <!-- /wp:paragraph --></div> @@ -31,8 +31,8 @@ exports[`Navigating the block hierarchy should navigate block hierarchy using on `; exports[`Navigating the block hierarchy should navigate using the block hierarchy dropdown menu 1`] = ` -"<!-- wp:columns {\\"columns\\":3} --> -<div class=\\"wp-block-columns has-3-columns\\"><!-- wp:column --> +"<!-- wp:columns --> +<div class=\\"wp-block-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> <p>First column</p> <!-- /wp:paragraph --></div> diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 7852dacfc4174f..e693daea38401b 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -6,7 +6,7 @@ exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = ` <!-- /wp:paragraph --> <!-- wp:columns --> -<div class=\\"wp-block-columns has-2-columns\\"><!-- wp:column --> +<div class=\\"wp-block-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> <p>First col</p> <!-- /wp:paragraph --></div> diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 56d1ce2c3694b2..3f0e1c8c3f55c7 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -20,6 +20,7 @@ describe( 'Navigating the block hierarchy', () => { it( 'should navigate using the block hierarchy dropdown menu', async () => { await insertBlock( 'Columns' ); + await page.click( '[aria-label="Two columns; equal split"]' ); // Add a paragraph in the first column. await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. @@ -61,6 +62,7 @@ describe( 'Navigating the block hierarchy', () => { it( 'should navigate block hierarchy using only the keyboard', async () => { await insertBlock( 'Columns' ); + await page.click( '[aria-label="Two columns; equal split"]' ); // Add a paragraph in the first column. await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. diff --git a/packages/e2e-tests/specs/blocks/columns.test.js b/packages/e2e-tests/specs/blocks/columns.test.js index fd061642478e74..4372f5ee6dbeb9 100644 --- a/packages/e2e-tests/specs/blocks/columns.test.js +++ b/packages/e2e-tests/specs/blocks/columns.test.js @@ -16,6 +16,7 @@ describe( 'Columns', () => { it( 'restricts all blocks inside the columns block', async () => { await insertBlock( 'Columns' ); + await page.click( '[aria-label="Two columns; equal split"]' ); await page.click( '[aria-label="Block Navigation"]' ); const columnBlockMenuItem = ( await page.$x( '//button[contains(concat(" ", @class, " "), " block-editor-block-navigation__item-button ")][text()="Column"]' ) )[ 0 ]; await columnBlockMenuItem.click(); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap index 52f4469dddeeae..f089390857781a 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap @@ -14,7 +14,7 @@ exports[`templates Using a CPT with a predefined template Should add a custom po <!-- /wp:quote --> <!-- wp:columns --> -<div class=\\"wp-block-columns has-2-columns\\"><!-- wp:column --> +<div class=\\"wp-block-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:image --> <figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> <!-- /wp:image --></div> @@ -40,7 +40,7 @@ exports[`templates Using a CPT with a predefined template Should respect user ed <!-- /wp:quote --> <!-- wp:columns --> -<div class=\\"wp-block-columns has-2-columns\\"><!-- wp:column --> +<div class=\\"wp-block-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:image --> <figure class=\\"wp-block-image\\"><img alt=\\"\\"/></figure> <!-- /wp:image --></div> diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index 5bcf9869c86469..b9ac58f46fd405 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -28,6 +28,7 @@ describe( 'adding blocks', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( '/columns' ); await page.keyboard.press( 'Enter' ); + await page.click( ':focus [aria-label="Two columns; equal split"]' ); await page.click( ':focus .block-editor-button-block-appender' ); await page.waitForSelector( ':focus.block-editor-inserter__search' ); await page.keyboard.type( 'Paragraph' ); diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index 49912d76c49605..3915c3edec678b 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostContent":{"type":"boolean","default":false},"displayPostContentRadio":{"type":"string","default":"excerpt"},"excerptLength":{"type":"number","default":55},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"className":{"type":"string"},"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file +{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostContent":{"type":"boolean","default":false},"displayPostContentRadio":{"type":"string","default":"excerpt"},"excerptLength":{"type":"number","default":55},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file From 01512c17555a223ca2c5246add4172b3081d4a00 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 24 Jun 2019 14:13:20 -0400 Subject: [PATCH 380/664] Bump plugin version to 6.0.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index ed0eb8e6c0f508..5a03abdad7c309 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 5.9.2 + * Version: 6.0.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 879de9fcea37e4..779d31bd957e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.2", + "version": "6.0.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c74000d2a1f86d..64a7a54a098d70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "5.9.2", + "version": "6.0.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 91163be43904824d213ff63606b9598d7d00a9c8 Mon Sep 17 00:00:00 2001 From: Josh Pollock <jpollock412@gmail.com> Date: Tue, 25 Jun 2019 02:24:21 -0400 Subject: [PATCH 381/664] change unit to e2e tests in parts of scripts package README #16264 (#16267) --- packages/scripts/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index 531def5b59d22e..c1c3f6ae107703 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -236,9 +236,9 @@ _Example:_ This is how you execute those scripts using the presented setup: -* `npm run test:e2e` - runs all unit tests. -* `npm run test:e2e:help` - prints all available options to configure unit tests runner. -* `npm run test-e2e -- --puppeteer-interactive` - runs all unit tests interactively. +* `npm run test:e2e` - runs all e2e tests. +* `npm run test:e2e:help` - prints all available options to configure e2e test runner. +* `npm run test-e2e -- --puppeteer-interactive` - runs all e2e tests interactively. * `npm run test-e2e FILE_NAME -- --puppeteer-interactive ` - runs one test file interactively. * `npm run test-e2e:watch -- --puppeteer-interactive` - runs all tests interactively and watch for changes. From 736adf32c92f88c5f401f1b351b2501aedff4bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 25 Jun 2019 12:15:39 +0200 Subject: [PATCH 382/664] Snackbar notification for "Copy All Content (#16265) * Add `Content copied` message * Update message --- .../plugins/copy-content-menu-item/index.js | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index 06c83f73d10778..af93ecb542ba6e 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -2,17 +2,27 @@ * WordPress dependencies */ import { ClipboardButton } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; +import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { withState, compose } from '@wordpress/compose'; -function CopyContentMenuItem( { editedPostContent, hasCopied, setState } ) { +function CopyContentMenuItem( { createNotice, editedPostContent, hasCopied, setState } ) { return ( <ClipboardButton text={ editedPostContent } role="menuitem" className="components-menu-item__button" - onCopy={ () => setState( { hasCopied: true } ) } + onCopy={ () => { + setState( { hasCopied: true } ); + createNotice( + 'info', + 'All content copied.', + { + isDismissible: true, + type: 'snackbar', + } + ); + } } onFinishCopy={ () => setState( { hasCopied: false } ) } > { hasCopied ? @@ -26,5 +36,14 @@ export default compose( withSelect( ( select ) => ( { editedPostContent: select( 'core/editor' ).getEditedPostAttribute( 'content' ), } ) ), + withDispatch( ( dispatch ) => { + const { + createNotice, + } = dispatch( 'core/notices' ); + + return { + createNotice, + }; + } ), withState( { hasCopied: false } ) )( CopyContentMenuItem ); From 9b8714e029d3d4b75f5f5602f4a90129906d75e1 Mon Sep 17 00:00:00 2001 From: Myles Hyson <mylesshyson@gmail.com> Date: Tue, 25 Jun 2019 09:59:23 -0400 Subject: [PATCH 383/664] add check for variable in gutenberg_is_block_editor function to prevent failure (#16201) --- lib/widgets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets.php b/lib/widgets.php index 3c528b14469271..85a9dea30ce99c 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -17,7 +17,7 @@ function gutenberg_is_block_editor() { return false; } $screen = get_current_screen(); - return $screen->is_block_editor() || 'gutenberg_page_gutenberg-widgets' === $screen->id; + return ! empty( $screen ) && ( $screen->is_block_editor() || 'gutenberg_page_gutenberg-widgets' === $screen->id ); } /** From 64a8a7c5208324b5308d2298bf0f98c906cfd66f Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 25 Jun 2019 15:32:32 +0100 Subject: [PATCH 384/664] Add block inspector to the widget screen (#16203) --- .../src/components/block-inspector/index.js | 19 +++++-- .../src/components/layout/index.js | 6 ++- .../src/components/sidebar/index.js | 10 +++- .../src/components/sidebar/style.scss | 4 ++ .../src/components/widget-area/index.js | 24 +++++++-- .../widget-area/selection-observer.js | 54 +++++++++++++++++++ .../src/components/widget-areas/index.js | 11 ++++ 7 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 packages/edit-widgets/src/components/widget-area/selection-observer.js diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 6682741f9101e8..e04dbcbd932be1 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -20,8 +20,14 @@ import InspectorControls from '../inspector-controls'; import InspectorAdvancedControls from '../inspector-advanced-controls'; import BlockStyles from '../block-styles'; import MultiSelectionInspector from '../multi-selection-inspector'; - -const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, count, hasBlockStyles } ) => { +const BlockInspector = ( { + blockType, + count, + hasBlockStyles, + selectedBlockClientId, + selectedBlockName, + showNoBlockSelectedMessage = true, +} ) => { if ( count > 1 ) { return <MultiSelectionInspector />; } @@ -33,7 +39,14 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, blockType, * because we want the user to focus on the unregistered block warning, not block settings. */ if ( ! blockType || ! selectedBlockClientId || isSelectedBlockUnregistered ) { - return <span className="editor-block-inspector__no-blocks block-editor-block-inspector__no-blocks">{ __( 'No block selected.' ) }</span>; + if ( showNoBlockSelectedMessage ) { + return ( + <span className="editor-block-inspector__no-blocks block-editor-block-inspector__no-blocks"> + { __( 'No block selected.' ) } + </span> + ); + } + return null; } return ( diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 44b6d1d64896ea..e3a264980d5d88 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -2,7 +2,11 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { navigateRegions, Popover, SlotFillProvider } from '@wordpress/components'; +import { + navigateRegions, + Popover, + SlotFillProvider, +} from '@wordpress/components'; /** * Internal dependencies diff --git a/packages/edit-widgets/src/components/sidebar/index.js b/packages/edit-widgets/src/components/sidebar/index.js index 3fe0cb0e91352f..60cb68b22fb0a1 100644 --- a/packages/edit-widgets/src/components/sidebar/index.js +++ b/packages/edit-widgets/src/components/sidebar/index.js @@ -1,9 +1,11 @@ /** * WordPress dependencies */ -import { Panel } from '@wordpress/components'; +import { createSlotFill, Panel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +export const { Fill: BlockSidebarFill, Slot: BlockSidebarSlot } = createSlotFill( 'EditWidgetsBlockSidebar' ); + function Sidebar() { return ( <div @@ -12,9 +14,13 @@ function Sidebar() { aria-label={ __( 'Widgets advanced settings' ) } tabIndex="-1" > - <Panel header={ __( 'Block Areas' ) } /> + <Panel header={ __( 'Block Areas' ) }> + <BlockSidebarSlot bubblesVirtually /> + </Panel> </div> ); } +Sidebar.Inspector = BlockSidebarFill; + export default Sidebar; diff --git a/packages/edit-widgets/src/components/sidebar/style.scss b/packages/edit-widgets/src/components/sidebar/style.scss index c528439c50c9a6..53bab2c7ffac8d 100644 --- a/packages/edit-widgets/src/components/sidebar/style.scss +++ b/packages/edit-widgets/src/components/sidebar/style.scss @@ -39,4 +39,8 @@ background: $light-gray-200; } } + + .block-editor-block-inspector__card { + margin: 0; + } } diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 4e3048e43ec6e6..bb7a5f5cc6b433 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -11,13 +11,20 @@ import { uploadMedia } from '@wordpress/media-utils'; import { compose } from '@wordpress/compose'; import { Panel, PanelBody } from '@wordpress/components'; import { + BlockInspector, BlockEditorProvider, BlockList, - ObserveTyping, WritingFlow, + ObserveTyping, } from '@wordpress/block-editor'; import { withDispatch, withSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import Sidebar from '../sidebar'; +import SelectionObserver from './selection-observer'; + function getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ) { if ( ! hasUploadPermissions ) { return blockEditorSettings; @@ -38,10 +45,12 @@ function getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ) { function WidgetArea( { blockEditorSettings, blocks, + hasUploadPermissions, initialOpen, + isSelectedArea, + onBlockSelected, updateBlocks, widgetAreaName, - hasUploadPermissions, } ) { const settings = useMemo( () => getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ), @@ -59,6 +68,13 @@ function WidgetArea( { onChange={ updateBlocks } settings={ settings } > + <SelectionObserver + isSelectedArea={ isSelectedArea } + onBlockSelected={ onBlockSelected } + /> + <Sidebar.Inspector> + <BlockInspector showNoBlockSelectedMessage={ false } /> + </Sidebar.Inspector> <WritingFlow> <ObserveTyping> <BlockList /> @@ -88,7 +104,9 @@ export default compose( [ withDispatch( ( dispatch, { id } ) => { return { updateBlocks( blocks ) { - const { updateBlocksInWidgetArea } = dispatch( 'core/edit-widgets' ); + const { + updateBlocksInWidgetArea, + } = dispatch( 'core/edit-widgets' ); updateBlocksInWidgetArea( id, blocks ); }, }; diff --git a/packages/edit-widgets/src/components/widget-area/selection-observer.js b/packages/edit-widgets/src/components/widget-area/selection-observer.js new file mode 100644 index 00000000000000..3b1cdf5c378e49 --- /dev/null +++ b/packages/edit-widgets/src/components/widget-area/selection-observer.js @@ -0,0 +1,54 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; +import { withSelect, withDispatch } from '@wordpress/data'; + +/** + * Component which calls onBlockSelected prop when a block becomes selected. It + * can be used to ensuring that only one block appears as selected + * when multiple editors exist in a page. + * + * @type {WPComponent} + */ + +class SelectionObserver extends Component { + componentDidUpdate( prevProps ) { + const { + hasSelectedBlock, + onBlockSelected, + isSelectedArea, + clearSelectedBlock, + } = this.props; + + if ( hasSelectedBlock && ! prevProps.hasSelectedBlock ) { + onBlockSelected(); + } + + if ( ! isSelectedArea && prevProps.isSelectedArea ) { + clearSelectedBlock(); + } + } + + render() { + return null; + } +} + +export default compose( [ + withSelect( ( select ) => { + const { hasSelectedBlock } = select( 'core/block-editor' ); + + return { + hasSelectedBlock: hasSelectedBlock(), + }; + } ), + withDispatch( ( dispatch ) => { + const { clearSelectedBlock } = dispatch( 'core/block-editor' ); + + return { + clearSelectedBlock, + }; + } ), +] )( SelectionObserver ); diff --git a/packages/edit-widgets/src/components/widget-areas/index.js b/packages/edit-widgets/src/components/widget-areas/index.js index 53eea964ec65ac..c6c5094651d438 100644 --- a/packages/edit-widgets/src/components/widget-areas/index.js +++ b/packages/edit-widgets/src/components/widget-areas/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { useMemo, useState } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; @@ -10,8 +11,18 @@ import { withSelect } from '@wordpress/data'; import WidgetArea from '../widget-area'; function WidgetAreas( { areas, blockEditorSettings } ) { + const [ selectedArea, setSelectedArea ] = useState( 0 ); + const onBlockSelectedInArea = useMemo( + () => areas.map( ( value, index ) => ( () => { + setSelectedArea( index ); + } ) ), + [ areas, setSelectedArea ] + ); + return areas.map( ( { id }, index ) => ( <WidgetArea + isSelectedArea={ index === selectedArea } + onBlockSelected={ onBlockSelectedInArea[ index ] } blockEditorSettings={ blockEditorSettings } key={ id } id={ id } From 56de120cf56836c227710baf335323c21efc9ac5 Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Tue, 25 Jun 2019 11:55:59 -0400 Subject: [PATCH 385/664] Add new block indicator should show in place of empty block instead of below it (#16272) Show "ADD BLOCK HERE" item above block when a block is empty --- .../src/components/block-list/index.native.js | 23 ++++++++++++------- .../readable-content-view/index.native.js | 4 ++-- .../readable-content-view/style.native.scss | 6 +++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 07815c13913025..1642b5bf7d0fa8 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -32,6 +32,7 @@ export class BlockList extends Component { super( ...arguments ); this.renderItem = this.renderItem.bind( this ); + this.renderAddBlockSeparator = this.renderAddBlockSeparator.bind( this ); this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); this.onBlockTypeSelected = this.onBlockTypeSelected.bind( this ); @@ -213,8 +214,10 @@ export class BlockList extends Component { } renderItem( { item: clientId } ) { + const shouldReverseContent = this.isReplaceable( this.props.selectedBlock ); + return ( - <ReadableContentView> + <ReadableContentView reversed={ shouldReverseContent }> <BlockListBlock key={ clientId } showTitle={ false } @@ -224,17 +227,21 @@ export class BlockList extends Component { borderStyle={ this.blockHolderBorderStyle() } focusedBorderColor={ styles.blockHolderFocused.borderColor } /> - { this.state.blockTypePickerVisible && this.props.isBlockSelected( clientId ) && ( - <View style={ styles.containerStyleAddHere } > - <View style={ styles.lineStyleAddHere }></View> - <Text style={ styles.labelStyleAddHere } >{ __( 'ADD BLOCK HERE' ) }</Text> - <View style={ styles.lineStyleAddHere }></View> - </View> - ) } + { this.state.blockTypePickerVisible && this.props.isBlockSelected( clientId ) && this.renderAddBlockSeparator() } </ReadableContentView> ); } + renderAddBlockSeparator() { + return ( + <View style={ styles.containerStyleAddHere } > + <View style={ styles.lineStyleAddHere }></View> + <Text style={ styles.labelStyleAddHere } >{ __( 'ADD BLOCK HERE' ) }</Text> + <View style={ styles.lineStyleAddHere }></View> + </View> + ); + } + renderHTML() { return ( <HTMLTextInput { ...this.props } parentHeight={ this.props.rootViewHeight } /> diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js index bc550f93de9262..dbeb172ab04342 100644 --- a/packages/components/src/mobile/readable-content-view/index.native.js +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -8,9 +8,9 @@ import { View, Dimensions } from 'react-native'; */ import styles from './style.scss'; -const ReadableContentView = ( { children } ) => ( +const ReadableContentView = ( { reversed, children } ) => ( <View style={ styles.container } > - <View style={ styles.centeredContent } > + <View style={ reversed ? styles.reversedCenteredContent : styles.centeredContent } > { children } </View> </View> diff --git a/packages/components/src/mobile/readable-content-view/style.native.scss b/packages/components/src/mobile/readable-content-view/style.native.scss index 611d4054a470fe..61097fc4a2bbd0 100644 --- a/packages/components/src/mobile/readable-content-view/style.native.scss +++ b/packages/components/src/mobile/readable-content-view/style.native.scss @@ -6,3 +6,9 @@ width: 100%; max-width: 580; } + +.reversedCenteredContent { + flex-direction: column-reverse; + width: 100%; + max-width: 580; +} From 15a899294dcfa9b0c7d3ada2cbfaecd432ae5766 Mon Sep 17 00:00:00 2001 From: Dominik Schilling <dominikschilling+git@gmail.com> Date: Tue, 25 Jun 2019 20:42:49 +0200 Subject: [PATCH 386/664] Fix missing closing tag for RegistryConsumer example (#16153) --- packages/data/README.md | 7 ++++--- packages/data/src/components/registry-provider/context.js | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/data/README.md b/packages/data/README.md index cee8f5c9733d63..0e117eca77d876 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -398,7 +398,7 @@ You can read more about the react context api here: _Usage_ -````js +```js const { RegistryProvider, RegistryConsumer, @@ -419,6 +419,7 @@ const App = ( { props } ) => { </RegistryConsumer> </RegistryProvider> } +``` <a name="RegistryProvider" href="#RegistryProvider">#</a> **RegistryProvider** @@ -434,13 +435,13 @@ Given the name of a registered store, returns an object of the store's selectors The selector functions are been pre-bound to pass the current state automatically. As a consumer, you need only pass arguments of the selector, if applicable. -*Usage* +_Usage_ ```js const { select } = wp.data; select( 'my-shop' ).getPrice( 'hammer' ); -```` +``` _Parameters_ diff --git a/packages/data/src/components/registry-provider/context.js b/packages/data/src/components/registry-provider/context.js index a1fe1a9e56183c..157225a7c7888b 100644 --- a/packages/data/src/components/registry-provider/context.js +++ b/packages/data/src/components/registry-provider/context.js @@ -41,6 +41,7 @@ const { Consumer, Provider } = Context; * </RegistryConsumer> * </RegistryProvider> * } + * ``` */ export const RegistryConsumer = Consumer; From 7d2b3a209d6990f702032047d072dea982941628 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Tue, 25 Jun 2019 14:55:12 -0400 Subject: [PATCH 387/664] docs(components/autocomplete): prune outdated props and function params from README.md (#16170) * docs(components/autocomplete): prune outdated props and function params from README.md * prune more outdated docs * remove other outdated docblocks --- packages/components/src/autocomplete/README.md | 7 ------- packages/components/src/autocomplete/index.js | 15 +++------------ 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/packages/components/src/autocomplete/README.md b/packages/components/src/autocomplete/README.md index 1a14bf63237245..745323b23771c8 100644 --- a/packages/components/src/autocomplete/README.md +++ b/packages/components/src/autocomplete/README.md @@ -76,13 +76,6 @@ There are currently two supported actions: * "insert-at-caret" - Insert the `value` into the text (the default completion action). * "replace" - Replace the current block with the block specified in the `value` property. -#### allowNode - -A function that takes a text node and returns a boolean indicating whether the completer should be considered for that node. - -- Type: `Function` -- Required: No - #### allowContext A function that takes a Range before and a Range after the autocomplete trigger and query text and returns a boolean indicating whether the completer should be considered for that context. diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index aa0f4204fc5c56..6cc8a87e3a3999 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -60,19 +60,12 @@ import withSpokenMessages from '../higher-order/with-spoken-messages'; * @returns {(string|Array.<(string|Component)>)} list of react components to render. */ -/** - * @callback FnAllowNode - * @param {Node} textNode check if the completer can handle this text node. - * - * @returns {boolean} true if the completer can handle this text node. - */ - /** * @callback FnAllowContext - * @param {Range} before the range before the auto complete trigger and query. - * @param {Range} after the range after the autocomplete trigger and query. + * @param {string} before the string before the auto complete trigger and query. + * @param {string} after the string after the autocomplete trigger and query. * - * @returns {boolean} true if the completer can handle these ranges. + * @returns {boolean} true if the completer can handle. */ /** @@ -89,7 +82,6 @@ import withSpokenMessages from '../higher-order/with-spoken-messages'; /** * @callback FnGetOptionCompletion * @param {CompleterOption} value the value of the completer option. - * @param {Range} range the nodes included in the autocomplete trigger and query. * @param {String} query the text value of the autocomplete query. * * @returns {(OptionCompletion|OptionCompletionValue)} the completion for the given option. If an @@ -106,7 +98,6 @@ import withSpokenMessages from '../higher-order/with-spoken-messages'; * @property {?FnGetOptionKeywords} getOptionKeywords get the keywords for a given option. * @property {?FnIsOptionDisabled} isOptionDisabled get whether or not the given option is disabled. * @property {FnGetOptionLabel} getOptionLabel get the label for a given option. - * @property {?FnAllowNode} allowNode filter the allowed text nodes in the autocomplete. * @property {?FnAllowContext} allowContext filter the context under which the autocomplete activates. * @property {FnGetOptionCompletion} getOptionCompletion get the completion associated with a given option. */ From abd7ba38508688ce736573874571ce94bd6db4f4 Mon Sep 17 00:00:00 2001 From: Brendan Warkentin <faazshift@gmail.com> Date: Tue, 25 Jun 2019 14:16:43 -0600 Subject: [PATCH 388/664] Switched to regular function in shortcode replace callback (#16242) --- packages/shortcode/src/index.js | 2 +- packages/shortcode/src/test/index.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.js index d9a3de767d97ac..ef609e4a3d170d 100644 --- a/packages/shortcode/src/index.js +++ b/packages/shortcode/src/index.js @@ -90,7 +90,7 @@ export function next( tag, text, index = 0 ) { * @return {string} Text with shortcodes replaced. */ export function replace( tag, text, callback ) { - return text.replace( regexp( tag ), ( match, left, $3, attrs, slash, content, closing, right ) => { + return text.replace( regexp( tag ), function( match, left, $3, attrs, slash, content, closing, right ) { // If both extra brackets exist, the shortcode has been properly // escaped. if ( left === '[' && right === ']' ) { diff --git a/packages/shortcode/src/test/index.js b/packages/shortcode/src/test/index.js index 9e3f376426103a..9b3ca086abbec1 100644 --- a/packages/shortcode/src/test/index.js +++ b/packages/shortcode/src/test/index.js @@ -90,6 +90,13 @@ describe( 'shortcode', () => { expect( result2 ).toBe( 'this has the bar shortcode' ); } ); + it( 'should replace the shortcode with data from an attribute', () => { + const result1 = replace( 'foo', 'this [foo param="replacement text"] came from a shortcode attribute', ( match ) => { + return match.attrs.named.param || ''; + } ); + expect( result1 ).toBe( 'this replacement text came from a shortcode attribute' ); + } ); + it( 'should not replace the shortcode when it does not match', () => { const result1 = replace( 'bar', 'this has the [foo] shortcode', () => 'bar' ); expect( result1 ).toBe( 'this has the [foo] shortcode' ); From 3d5583aaa27b760703acd7ad11819858fb0a0902 Mon Sep 17 00:00:00 2001 From: Brent Swisher <brent@brentswisher.com> Date: Tue, 25 Jun 2019 16:25:31 -0400 Subject: [PATCH 389/664] Update permalink panel text to the view_item label from get_post_type_labels instead of 'Preview' (#16041) --- packages/edit-post/src/components/sidebar/post-link/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 674c14b8615f9b..b4b722dc34845b 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -30,6 +30,7 @@ function PostLink( { postTitle, postSlug, postID, + postTypeLabel, } ) { const { prefix, suffix } = permalinkParts; let prefixElement, postNameElement, suffixElement; @@ -95,7 +96,7 @@ function PostLink( { </div> ) } <p className="edit-post-post-link__preview-label"> - { __( 'Preview' ) } + { postTypeLabel || __( 'View Post' ) } </p> <ExternalLink className="edit-post-post-link__link" @@ -148,6 +149,7 @@ export default compose( [ postTitle: getEditedPostAttribute( 'title' ), postSlug: getEditedPostAttribute( 'slug' ), postID: id, + postTypeLabel: get( postType, [ 'labels', 'view_item' ] ), }; } ), ifCondition( ( { isEnabled, isNew, postLink, isViewable, permalinkParts } ) => { From 21832953536dd57ac94da0ab77c1e892c83603b7 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 25 Jun 2019 22:00:48 +0100 Subject: [PATCH 390/664] Add global Inserter to the widget screen (#16287) --- .../edit-widgets/src/components/header/index.js | 6 +++++- .../src/components/inserter/index.js | 16 ++++++++++++++++ .../src/components/widget-area/index.js | 7 +++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 packages/edit-widgets/src/components/inserter/index.js diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js index f18519fc3d5fcc..f918e7dea196ac 100644 --- a/packages/edit-widgets/src/components/header/index.js +++ b/packages/edit-widgets/src/components/header/index.js @@ -2,10 +2,12 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { NavigableMenu } from '@wordpress/components'; /** * Internal dependencies */ +import Inserter from '../inserter'; import SaveButton from '../save-button'; function Header() { @@ -16,10 +18,12 @@ function Header() { aria-label={ __( 'Widgets screen top bar' ) } tabIndex="-1" > + <NavigableMenu> + <Inserter.Slot /> + </NavigableMenu> <h1 className="edit-widgets-header__title"> { __( 'Block Areas' ) } { __( '(experimental)' ) } </h1> - <div className="edit-widgets-header__actions"> <SaveButton /> </div> diff --git a/packages/edit-widgets/src/components/inserter/index.js b/packages/edit-widgets/src/components/inserter/index.js new file mode 100644 index 00000000000000..12139805745e17 --- /dev/null +++ b/packages/edit-widgets/src/components/inserter/index.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; + +const { Fill: BlockInserterFill, Slot: BlockInserterSlot } = createSlotFill( 'EditWidgetsInserter' ); + +const Inserter = BlockInserterFill; + +Inserter.Slot = function() { + return ( + <BlockInserterSlot bubblesVirtually /> + ); +}; + +export default Inserter; diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index bb7a5f5cc6b433..3369f3ba1525b5 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -14,6 +14,7 @@ import { BlockInspector, BlockEditorProvider, BlockList, + Inserter as BlockInserter, WritingFlow, ObserveTyping, } from '@wordpress/block-editor'; @@ -24,6 +25,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; */ import Sidebar from '../sidebar'; import SelectionObserver from './selection-observer'; +import Inserter from '../inserter'; function getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ) { if ( ! hasUploadPermissions ) { @@ -68,6 +70,11 @@ function WidgetArea( { onChange={ updateBlocks } settings={ settings } > + { isSelectedArea && ( + <Inserter> + <BlockInserter /> + </Inserter> + ) } <SelectionObserver isSelectedArea={ isSelectedArea } onBlockSelected={ onBlockSelected } From ebd94e265aa3956d5a52f06911dd04fd1c6951cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <iseulde@automattic.com> Date: Tue, 25 Jun 2019 23:12:36 +0200 Subject: [PATCH 391/664] Framework: split RichText component (part 1) (#15212) * Framework: move RichText component to rich-text package * Add style attr * Fix deprecated matcher * Simplify * Remove dead code * Mark block related props unstable * Mark block related props unstable * Clean up * Add inline doc * Move doc block to export statement * Do not mutate props * Move rich text native to separate package (#16219) * Move React Native RichText to new package * Fix multiline reading. * Move default styles to component. * Move tests to component. * Fix paste on title --- package-lock.json | 10 + .../rich-text/format-toolbar/index.js | 4 +- .../rich-text/format-toolbar/index.native.js | 4 +- .../src/components/rich-text/index.js | 1193 ++--------------- .../src/components/rich-text/index.native.js | 952 ++----------- .../src/components/rich-text/patterns.js | 24 +- .../src/components/rich-text/style.scss | 23 - .../plugins/deprecated-node-matcher/index.js | 1 - .../src/components/post-title/index.native.js | 7 +- .../components/post-title/style.native.scss | 1 + packages/rich-text/README.md | 5 + packages/rich-text/package.json | 10 + .../src/component}/aria.js | 0 .../src/component}/editable.js | 48 +- .../src/component}/format-edit.js | 7 +- packages/rich-text/src/component/index.js | 1116 +++++++++++++++ .../rich-text/src/component/index.native.js | 883 ++++++++++++ .../src/component}/style.native.scss | 4 +- .../src/component}/test/index.js | 0 .../src/component}/test/index.native.js | 0 packages/rich-text/src/index.js | 8 +- 21 files changed, 2308 insertions(+), 1992 deletions(-) rename packages/{block-editor/src/components/rich-text => rich-text/src/component}/aria.js (100%) rename packages/{block-editor/src/components/rich-text => rich-text/src/component}/editable.js (78%) rename packages/{block-editor/src/components/rich-text => rich-text/src/component}/format-edit.js (87%) create mode 100644 packages/rich-text/src/component/index.js create mode 100644 packages/rich-text/src/component/index.native.js rename packages/{block-editor/src/components/rich-text => rich-text/src/component}/style.native.scss (69%) rename packages/{block-editor/src/components/rich-text => rich-text/src/component}/test/index.js (100%) rename packages/{block-editor/src/components/rich-text => rich-text/src/component}/test/index.native.js (100%) diff --git a/package-lock.json b/package-lock.json index 779d31bd957e86..7b34929e4cd4c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3787,11 +3787,21 @@ "version": "file:packages/rich-text", "requires": { "@babel/runtime": "^7.4.4", + "@wordpress/blob": "file:packages/blob", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", + "@wordpress/deprecated": "file:packages/deprecated", + "@wordpress/dom": "file:packages/dom", + "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", "@wordpress/hooks": "file:packages/hooks", + "@wordpress/html-entities": "file:packages/html-entities", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", + "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/url": "file:packages/url", + "classnames": "^2.2.5", "lodash": "^4.17.11", + "memize": "^1.0.5", "rememo": "^3.0.0" } }, diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js index aa6570f620035b..e596de9d6ea28f 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.js @@ -11,11 +11,11 @@ import { orderBy } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Toolbar, Slot, DropdownMenu } from '@wordpress/components'; -const FormatToolbar = ( { controls } ) => { +const FormatToolbar = () => { return ( <div className="editor-format-toolbar block-editor-format-toolbar"> <Toolbar> - { controls.map( ( format ) => + { [ 'bold', 'italic', 'link' ].map( ( format ) => <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> ) } <Slot name="RichText.ToolbarControls"> diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js index d90860c05f4d67..d39f9b4668eb44 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js @@ -4,10 +4,10 @@ import { Toolbar, Slot } from '@wordpress/components'; -const FormatToolbar = ( { controls } ) => { +const FormatToolbar = () => { return ( <Toolbar> - { controls.map( ( format ) => + { [ 'bold', 'italic', 'link' ].map( ( format ) => <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> ) } <Slot name="RichText.ToolbarControls" /> diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 40f758f064e081..baca2921d1677f 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -2,1112 +2,128 @@ * External dependencies */ import classnames from 'classnames'; -import { - find, - isNil, - omit, - pickBy, -} from 'lodash'; +import { omit } from 'lodash'; /** * WordPress dependencies */ -import { Component, RawHTML } from '@wordpress/element'; -import { isHorizontalEdge } from '@wordpress/dom'; -import { createBlobURL } from '@wordpress/blob'; -import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; +import { RawHTML } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; -import { pasteHandler, children, getBlockTransforms, findTransform } from '@wordpress/blocks'; -import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; -import { isURL } from '@wordpress/url'; -import { - isEmpty, - create, - __unstableApply as apply, - applyFormat, - split, - toHTMLString, - getTextContent, - insert, - __unstableInsertLineSeparator as insertLineSeparator, - __unstableRemoveLineSeparator as removeLineSeparator, - __unstableIsEmptyLine as isEmptyLine, - __unstableToDom as toDom, - remove, - removeFormat, - isCollapsed, - __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR, - __unstableIndentListItems as indentListItems, - __unstableGetActiveFormats as getActiveFormats, - __unstableUpdateFormats as updateFormats, - replace, -} from '@wordpress/rich-text'; -import { decodeEntities } from '@wordpress/html-entities'; +import { pasteHandler, children } from '@wordpress/blocks'; +import { withInstanceId, compose } from '@wordpress/compose'; +import { RichText, __unstableCreateElement } from '@wordpress/rich-text'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; -import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies */ import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; -import FormatEdit from './format-edit'; import FormatToolbar from './format-toolbar'; -import Editable, { className as editableClassName } from './editable'; -import { pickAriaProps } from './aria'; -import { getPatterns } from './patterns'; +import { getPatterns, getEnterPatterns } from './patterns'; import { withBlockEditContext } from '../block-edit/context'; import { ListEdit } from './list-edit'; import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; -/** - * Browser dependencies - */ - -const { getSelection, getComputedStyle } = window; - -/** - * All inserting input types that would insert HTML into the DOM. - * - * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes - * - * @type {Set} - */ -const INSERTION_INPUT_TYPES_TO_IGNORE = new Set( [ - 'insertParagraph', - 'insertOrderedList', - 'insertUnorderedList', - 'insertHorizontalRule', - 'insertLink', -] ); - -/** - * Global stylesheet. - */ -const globalStyle = document.createElement( 'style' ); - -document.head.appendChild( globalStyle ); - -function createPrepareEditableTree( props, prefix ) { - const fns = Object.keys( props ).reduce( ( accumulator, key ) => { - if ( key.startsWith( prefix ) ) { - accumulator.push( props[ key ] ); - } - - return accumulator; - }, [] ); - - return ( value ) => fns.reduce( ( accumulator, fn ) => { - return fn( accumulator, value.text ); - }, value.formats ); -} - -export class RichText extends Component { - constructor( { value, onReplace, multiline, selectionStart, selectionEnd } ) { - super( ...arguments ); - - if ( multiline === true || multiline === 'p' || multiline === 'li' ) { - this.multilineTag = multiline === true ? 'p' : multiline; - } - - if ( this.multilineTag === 'li' ) { - this.multilineWrapperTags = [ 'ul', 'ol' ]; - } - - this.onFocus = this.onFocus.bind( this ); - this.onBlur = this.onBlur.bind( this ); - this.onChange = this.onChange.bind( this ); - this.onDeleteKeyDown = this.onDeleteKeyDown.bind( this ); - this.onKeyDown = this.onKeyDown.bind( this ); - this.onPaste = this.onPaste.bind( this ); - this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); - this.onInput = this.onInput.bind( this ); - this.onCompositionEnd = this.onCompositionEnd.bind( this ); - this.onSelectionChange = this.onSelectionChange.bind( this ); - this.getRecord = this.getRecord.bind( this ); - this.createRecord = this.createRecord.bind( this ); - this.applyRecord = this.applyRecord.bind( this ); - this.isEmpty = this.isEmpty.bind( this ); - this.valueToFormat = this.valueToFormat.bind( this ); - this.setRef = this.setRef.bind( this ); - this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); - this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); - this.onPointerDown = this.onPointerDown.bind( this ); - this.onSplit = this.onSplit.bind( this ); - - this.patterns = getPatterns( { - onReplace, - valueToFormat: this.valueToFormat, - } ); - this.enterPatterns = getBlockTransforms( 'from' ) - .filter( ( { type } ) => type === 'enter' ); - - this.state = {}; - - this.usedDeprecatedChildrenSource = Array.isArray( value ); - this.lastHistoryValue = value; - - // Internal values are updated synchronously, unlike props and state. - this.value = value; - this.record = this.formatToValue( value ); - this.record.start = selectionStart; - this.record.end = selectionEnd; - } - - componentWillUnmount() { - document.removeEventListener( 'selectionchange', this.onSelectionChange ); - } - - setRef( node ) { - if ( node ) { - if ( process.env.NODE_ENV === 'development' ) { - const computedStyle = getComputedStyle( node ); - - if ( computedStyle.display === 'inline' ) { - // eslint-disable-next-line no-console - console.warn( 'RichText cannot be used with an inline container. Please use a different tagName.' ); - } - } - - this.editableRef = node; - } else { - delete this.editableRef; - } - } - - /** - * Get the current record (value and selection) from props and state. - * - * @return {Object} The current record (value and selection). - */ - getRecord() { - return this.record; - } - - createRecord() { - const selection = getSelection(); - const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; - - return create( { - element: this.editableRef, - range, - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - __unstableIsEditableTree: true, - } ); - } - - applyRecord( record, { domOnly } = {} ) { - apply( { - value: record, - current: this.editableRef, - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), - __unstableDomOnly: domOnly, - } ); - } - - isEmpty() { - return isEmpty( this.record ); - } - - /** - * Handles a paste event. - * - * Saves the pasted data as plain text in `pastedPlainText`. - * - * @param {PasteEvent} event The paste event. - */ - onPaste( event ) { - const clipboardData = event.clipboardData; - let { items, files } = clipboardData; - - // In Edge these properties can be null instead of undefined, so a more - // rigorous test is required over using default values. - items = isNil( items ) ? [] : items; - files = isNil( files ) ? [] : files; - - let plainText = ''; - let html = ''; - - // IE11 only supports `Text` as an argument for `getData` and will - // otherwise throw an invalid argument error, so we try the standard - // arguments first, then fallback to `Text` if they fail. - try { - plainText = clipboardData.getData( 'text/plain' ); - html = clipboardData.getData( 'text/html' ); - } catch ( error1 ) { - try { - html = clipboardData.getData( 'Text' ); - } catch ( error2 ) { - // Some browsers like UC Browser paste plain text by default and - // don't support clipboardData at all, so allow default - // behaviour. - return; - } - } - - event.preventDefault(); - - // Allows us to ask for this information when we get a report. - window.console.log( 'Received HTML:\n\n', html ); - window.console.log( 'Received plain text:\n\n', plainText ); - - // Only process file if no HTML is present. - // Note: a pasted file may have the URL as plain text. - const item = find( [ ...items, ...files ], ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); - const record = this.getRecord(); - - if ( item && ! html ) { - const file = item.getAsFile ? item.getAsFile() : item; - const content = pasteHandler( { - HTML: `<img src="${ createBlobURL( file ) }">`, - mode: 'BLOCKS', - tagName: this.props.tagName, - } ); - const shouldReplace = this.props.onReplace && this.isEmpty(); - - // Allows us to ask for this information when we get a report. - window.console.log( 'Received item:\n\n', file ); - - if ( shouldReplace ) { - this.props.onReplace( content ); - } else if ( this.onSplit ) { - this.onSplit( record, content ); - } - - return; - } - - // There is a selection, check if a URL is pasted. - if ( ! isCollapsed( record ) ) { - const pastedText = ( html || plainText ).replace( /<[^>]+>/g, '' ).trim(); - - // A URL was pasted, turn the selection into a link - if ( isURL( pastedText ) ) { - this.onChange( applyFormat( record, { - type: 'a', - attributes: { - href: decodeEntities( pastedText ), - }, - } ) ); - - // Allows us to ask for this information when we get a report. - window.console.log( 'Created link:\n\n', pastedText ); - - return; - } - } - - const canReplace = this.props.onReplace && this.isEmpty(); - const canSplit = this.props.onReplace && this.props.onSplit; - - let mode = 'INLINE'; - - if ( canReplace ) { - mode = 'BLOCKS'; - } else if ( canSplit ) { - mode = 'AUTO'; - } - - const content = pasteHandler( { - HTML: html, - plainText, - mode, - tagName: this.props.tagName, - canUserUseUnfilteredHTML: this.props.canUserUseUnfilteredHTML, - } ); - - if ( typeof content === 'string' ) { - let valueToInsert = create( { html: content } ); - - // If the content should be multiline, we should process text - // separated by a line break as separate lines. - if ( this.multilineTag ) { - valueToInsert = replace( valueToInsert, /\n+/g, LINE_SEPARATOR ); - } - - this.onChange( insert( record, valueToInsert ) ); - } else if ( content.length > 0 ) { - if ( canReplace ) { - this.props.onReplace( content ); - } else { - this.onSplit( record, content ); - } - } - } - - /** - * Handles a focus event on the contenteditable field, calling the - * `unstableOnFocus` prop callback if one is defined. The callback does not - * receive any arguments. - * - * This is marked as a private API and the `unstableOnFocus` prop is not - * documented, as the current requirements where it is used are subject to - * future refactoring following `isSelected` handling. - * - * In contrast with `setFocusedElement`, this is only triggered in response - * to focus within the contenteditable field, whereas `setFocusedElement` - * is triggered on focus within any `RichText` descendent element. - * - * @see setFocusedElement - * - * @private - */ - onFocus() { - const { unstableOnFocus } = this.props; - - if ( unstableOnFocus ) { - unstableOnFocus(); - } - - this.recalculateBoundaryStyle(); - - // We know for certain that on focus, the old selection is invalid. It - // will be recalculated on `selectionchange`. - const index = undefined; - const activeFormats = undefined; - - this.record = { - ...this.record, - start: index, - end: index, - activeFormats, - }; - this.props.onSelectionChange( index, index ); - this.setState( { activeFormats } ); - - document.addEventListener( 'selectionchange', this.onSelectionChange ); - } - - onBlur() { - document.removeEventListener( 'selectionchange', this.onSelectionChange ); - } - - /** - * Handle input on the next selection change event. - * - * @param {SyntheticEvent} event Synthetic input event. - */ - onInput( event ) { - // For Input Method Editor (IME), used in Chinese, Japanese, and Korean - // (CJK), do not trigger a change if characters are being composed. - // Browsers setting `isComposing` to `true` will usually emit a final - // `input` event when the characters are composed. - if ( event && event.nativeEvent.isComposing ) { - // Also don't update any selection. - document.removeEventListener( 'selectionchange', this.onSelectionChange ); - return; - } - - if ( event && event.nativeEvent.inputType ) { - const { inputType } = event.nativeEvent; - - // The browser formatted something or tried to insert HTML. - // Overwrite it. It will be handled later by the format library if - // needed. - if ( - inputType.indexOf( 'format' ) === 0 || - INSERTION_INPUT_TYPES_TO_IGNORE.has( inputType ) - ) { - this.applyRecord( this.getRecord() ); - return; - } - } - - const value = this.createRecord(); - const { start, activeFormats = [] } = this.record; - - // Update the formats between the last and new caret position. - const change = updateFormats( { - value, - start, - end: value.start, - formats: activeFormats, - } ); - - this.onChange( change, { withoutHistory: true } ); - - const transformed = this.patterns.reduce( - ( accumlator, transform ) => transform( accumlator ), - change - ); - - if ( transformed !== change ) { - this.onCreateUndoLevel(); - this.onChange( { ...transformed, activeFormats } ); - } - - // Create an undo level when input stops for over a second. - this.props.clearTimeout( this.onInput.timeout ); - this.onInput.timeout = this.props.setTimeout( this.onCreateUndoLevel, 1000 ); - } - - onCompositionEnd() { - // Ensure the value is up-to-date for browsers that don't emit a final - // input event after composition. - this.onInput(); - // Tracking selection changes can be resumed. - document.addEventListener( 'selectionchange', this.onSelectionChange ); - } - - /** - * Handles the `selectionchange` event: sync the selection to local state. - */ - onSelectionChange() { - const { start, end } = this.createRecord(); - const value = this.getRecord(); - - if ( start !== value.start || end !== value.end ) { - const { isCaretWithinFormattedText } = this.props; - const newValue = { - ...value, - start, - end, - // Allow `getActiveFormats` to get new `activeFormats`. - activeFormats: undefined, - }; - - const activeFormats = getActiveFormats( newValue ); - - // Update the value with the new active formats. - newValue.activeFormats = activeFormats; - - if ( ! isCaretWithinFormattedText && activeFormats.length ) { - this.props.onEnterFormattedText(); - } else if ( isCaretWithinFormattedText && ! activeFormats.length ) { - this.props.onExitFormattedText(); - } - - // It is important that the internal value is updated first, - // otherwise the value will be wrong on render! - this.record = newValue; - this.applyRecord( newValue, { domOnly: true } ); - this.props.onSelectionChange( start, end ); - this.setState( { activeFormats } ); - - if ( activeFormats.length > 0 ) { - this.recalculateBoundaryStyle(); - } - } - } - - recalculateBoundaryStyle() { - const boundarySelector = '*[data-rich-text-format-boundary]'; - const element = this.editableRef.querySelector( boundarySelector ); - - if ( ! element ) { - return; - } - - const computedStyle = getComputedStyle( element ); - const newColor = computedStyle.color - .replace( ')', ', 0.2)' ) - .replace( 'rgb', 'rgba' ); - const selector = `.${ editableClassName }:focus ${ boundarySelector }`; - const rule = `background-color: ${ newColor }`; - - globalStyle.innerHTML = `${ selector } {${ rule }}`; - } - - /** - * Sync the value to global state. The node tree and selection will also be - * updated if differences are found. - * - * @param {Object} record The record to sync and apply. - * @param {Object} $2 Named options. - * @param {boolean} $2.withoutHistory If true, no undo level will be - * created. - */ - onChange( record, { withoutHistory } = {} ) { - this.applyRecord( record ); - - const { start, end, activeFormats = [] } = record; - const changeHandlers = pickBy( this.props, ( v, key ) => - key.startsWith( 'format_on_change_functions_' ) - ); - - Object.values( changeHandlers ).forEach( ( changeHandler ) => { - changeHandler( record.formats, record.text ); - } ); - - this.value = this.valueToFormat( record ); - this.record = record; - this.props.onChange( this.value ); - this.props.onSelectionChange( start, end ); - this.setState( { activeFormats } ); - - if ( ! withoutHistory ) { - this.onCreateUndoLevel(); - } - } - - onCreateUndoLevel() { - // If the content is the same, no level needs to be created. - if ( this.lastHistoryValue === this.value ) { - return; - } - - this.props.onCreateUndoLevel(); - this.lastHistoryValue = this.value; - } - - /** - * Handles a delete keyDown event to handle merge or removal for collapsed - * selection where caret is at directional edge: forward for a delete key, - * reverse for a backspace key. - * - * @see https://en.wikipedia.org/wiki/Caret_navigation - * - * @param {KeyboardEvent} event Keydown event. - */ - onDeleteKeyDown( event ) { - const { onMerge, onRemove } = this.props; - if ( ! onMerge && ! onRemove ) { - return; - } - - const { keyCode } = event; - const isReverse = keyCode === BACKSPACE; - - // Only process delete if the key press occurs at uncollapsed edge. - if ( ! isCollapsed( this.createRecord() ) ) { - return; - } - - const empty = this.isEmpty(); - - // It is important to consider emptiness because an empty container - // will include a padding BR node _after_ the caret, so in a forward - // deletion the isHorizontalEdge function will incorrectly interpret the - // presence of the BR node as not being at the edge. - const isEdge = ( empty || isHorizontalEdge( this.editableRef, isReverse ) ); - - if ( ! isEdge ) { - return; - } - - if ( onMerge ) { - onMerge( ! isReverse ); - } - - // Only handle remove on Backspace. This serves dual-purpose of being - // an intentional user interaction distinguishing between Backspace and - // Delete to remove the empty field, but also to avoid merge & remove - // causing destruction of two fields (merge, then removed merged). - if ( onRemove && empty && isReverse ) { - onRemove( ! isReverse ); - } - - event.preventDefault(); - } - - /** - * Handles a keydown event. - * - * @param {SyntheticEvent} event A synthetic keyboard event. - */ - onKeyDown( event ) { - const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; - const { onReplace, onSplit } = this.props; - const canSplit = onReplace && onSplit; - - if ( - // Only override left and right keys without modifiers pressed. - ! shiftKey && ! altKey && ! metaKey && ! ctrlKey && - ( keyCode === LEFT || keyCode === RIGHT ) - ) { - this.handleHorizontalNavigation( event ); - } - - // Use the space key in list items (at the start of an item) to indent - // the list item. - if ( keyCode === SPACE && this.multilineTag === 'li' ) { - const value = this.createRecord(); - - if ( isCollapsed( value ) ) { - const { text, start } = value; - const characterBefore = text[ start - 1 ]; - - // The caret must be at the start of a line. - if ( ! characterBefore || characterBefore === LINE_SEPARATOR ) { - this.onChange( indentListItems( value, { type: this.props.tagName } ) ); - event.preventDefault(); - } - } - } - - if ( keyCode === DELETE || keyCode === BACKSPACE ) { - const value = this.createRecord(); - const { start, end } = value; - - // Always handle full content deletion ourselves. - if ( start === 0 && end !== 0 && end === value.text.length ) { - this.onChange( remove( value ) ); - event.preventDefault(); - return; - } - - if ( this.multilineTag ) { - const newValue = removeLineSeparator( value, keyCode === BACKSPACE ); - if ( newValue ) { - this.onChange( newValue ); - event.preventDefault(); - } - } - - this.onDeleteKeyDown( event ); - } else if ( keyCode === ENTER ) { - event.preventDefault(); - - const record = this.createRecord(); - - if ( this.props.onReplace ) { - const text = getTextContent( record ); - const transformation = findTransform( this.enterPatterns, ( item ) => { - return item.regExp.test( text ); - } ); - - if ( transformation ) { - this.props.onReplace( [ - transformation.transform( { content: text } ), - ] ); - return; - } - } - - if ( this.multilineTag ) { - if ( event.shiftKey ) { - this.onChange( insert( record, '\n' ) ); - } else if ( canSplit && isEmptyLine( record ) ) { - this.onSplit( record ); - } else { - this.onChange( insertLineSeparator( record ) ); - } - } else if ( event.shiftKey || ! canSplit ) { - this.onChange( insert( record, '\n' ) ); - } else { - this.onSplit( record ); - } - } - } - - /** - * Handles horizontal keyboard navigation when no modifiers are pressed. The - * navigation is handled separately to move correctly around format - * boundaries. - * - * @param {SyntheticEvent} event A synthetic keyboard event. - */ - handleHorizontalNavigation( event ) { - const value = this.getRecord(); - const { text, formats, start, end, activeFormats = [] } = value; - const collapsed = isCollapsed( value ); - // To do: ideally, we should look at visual position instead. - const { direction } = getComputedStyle( this.editableRef ); - const reverseKey = direction === 'rtl' ? RIGHT : LEFT; - const isReverse = event.keyCode === reverseKey; - - // If the selection is collapsed and at the very start, do nothing if - // navigating backward. - // If the selection is collapsed and at the very end, do nothing if - // navigating forward. - if ( collapsed && activeFormats.length === 0 ) { - if ( start === 0 && isReverse ) { - return; - } - - if ( end === text.length && ! isReverse ) { - return; - } - } - - // If the selection is not collapsed, let the browser handle collapsing - // the selection for now. Later we could expand this logic to set - // boundary positions if needed. - if ( ! collapsed ) { - return; - } - - // In all other cases, prevent default behaviour. - event.preventDefault(); - - const formatsBefore = formats[ start - 1 ] || []; - const formatsAfter = formats[ start ] || []; - - let newActiveFormatsLength = activeFormats.length; - let source = formatsAfter; - - if ( formatsBefore.length > formatsAfter.length ) { - source = formatsBefore; - } - - // If the amount of formats before the caret and after the caret is - // different, the caret is at a format boundary. - if ( formatsBefore.length < formatsAfter.length ) { - if ( ! isReverse && activeFormats.length < formatsAfter.length ) { - newActiveFormatsLength++; - } - - if ( isReverse && activeFormats.length > formatsBefore.length ) { - newActiveFormatsLength--; - } - } else if ( formatsBefore.length > formatsAfter.length ) { - if ( ! isReverse && activeFormats.length > formatsAfter.length ) { - newActiveFormatsLength--; - } - - if ( isReverse && activeFormats.length < formatsBefore.length ) { - newActiveFormatsLength++; - } - } - - // Wait for boundary class to be added. - this.props.setTimeout( () => this.recalculateBoundaryStyle() ); - - if ( newActiveFormatsLength !== activeFormats.length ) { - const newActiveFormats = source.slice( 0, newActiveFormatsLength ); - const newValue = { ...value, activeFormats: newActiveFormats }; - this.record = newValue; - this.applyRecord( newValue ); - this.setState( { activeFormats: newActiveFormats } ); - return; - } - - const newPos = value.start + ( isReverse ? -1 : 1 ); - const newActiveFormats = isReverse ? formatsBefore : formatsAfter; - const newValue = { - ...value, - start: newPos, - end: newPos, - activeFormats: newActiveFormats, - }; - - this.record = newValue; - this.applyRecord( newValue ); - this.props.onSelectionChange( newPos, newPos ); - this.setState( { activeFormats: newActiveFormats } ); - } - - /** - * Signals to the RichText owner that the block can be replaced with two - * blocks as a result of splitting the block by pressing enter, or with - * blocks as a result of splitting the block by pasting block content in the - * instance. - * - * @param {Object} record The rich text value to split. - * @param {Array} pastedBlocks The pasted blocks to insert, if any. - */ - onSplit( record, pastedBlocks = [] ) { - const { - onReplace, - onSplit, - __unstableOnSplitMiddle: onSplitMiddle, - } = this.props; - - if ( ! onReplace || ! onSplit ) { - return; - } - - const blocks = []; - const [ before, after ] = split( record ); - const hasPastedBlocks = pastedBlocks.length > 0; - - // Create a block with the content before the caret if there's no pasted - // blocks, or if there are pasted blocks and the value is not empty. - // We do not want a leading empty block on paste, but we do if split - // with e.g. the enter key. - if ( ! hasPastedBlocks || ! isEmpty( before ) ) { - blocks.push( onSplit( this.valueToFormat( before ) ) ); - } - - if ( hasPastedBlocks ) { - blocks.push( ...pastedBlocks ); - } else if ( onSplitMiddle ) { - blocks.push( onSplitMiddle() ); - } - - // If there's pasted blocks, append a block with the content after the - // caret. Otherwise, do append and empty block if there is no - // `onSplitMiddle` prop, but if there is and the content is empty, the - // middle block is enough to set focus in. - if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { - blocks.push( onSplit( this.valueToFormat( after ) ) ); - } - - // If there are pasted blocks, set the selection to the last one. - // Otherwise, set the selection to the second block. - const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; - - onReplace( blocks, indexToSelect ); - } - - /** - * Select object when they are clicked. The browser will not set any - * selection when clicking e.g. an image. - * - * @param {SyntheticEvent} event Synthetic mousedown or touchstart event. - */ - onPointerDown( event ) { - const { target } = event; - - // If the child element has no text content, it must be an object. - if ( target === this.editableRef || target.textContent ) { - return; - } - - const { parentNode } = target; - const index = Array.from( parentNode.childNodes ).indexOf( target ); - const range = target.ownerDocument.createRange(); - const selection = getSelection(); - - range.setStart( target.parentNode, index ); - range.setEnd( target.parentNode, index + 1 ); - - selection.removeAllRanges(); - selection.addRange( range ); - } - - componentDidUpdate( prevProps ) { - const { tagName, value, selectionStart, selectionEnd, isSelected } = this.props; - - // Check if the content changed. - let shouldReapply = ( - tagName === prevProps.tagName && - value !== prevProps.value && - value !== this.value - ); - - // Check if the selection changed. - shouldReapply = shouldReapply || ( - isSelected && ! prevProps.isSelected && ( - this.record.start !== selectionStart || - this.record.end !== selectionEnd - ) - ); - - const prefix = 'format_prepare_props_'; - const predicate = ( v, key ) => key.startsWith( prefix ); - const prepareProps = pickBy( this.props, predicate ); - const prevPrepareProps = pickBy( prevProps, predicate ); - - // Check if any format props changed. - shouldReapply = shouldReapply || - ! isShallowEqual( prepareProps, prevPrepareProps ); - - const { activeFormats = [] } = this.record; - - if ( shouldReapply ) { - this.value = value; - this.record = this.formatToValue( value ); - this.record.start = selectionStart; - this.record.end = selectionEnd; - - updateFormats( { - value: this.record, - start: this.record.start, - end: this.record.end, - formats: activeFormats, - } ); - - this.applyRecord( this.record ); - } else if ( - this.record.start !== selectionStart || - this.record.end !== selectionEnd - ) { - this.record = { - ...this.record, - start: selectionStart, - end: selectionEnd, - }; - } - } - - /** - * Converts the outside data structure to our internal representation. - * - * @param {*} value The outside value, data type depends on props. - * @return {Object} An internal rich-text value. - */ - formatToValue( value ) { - // Handle deprecated `children` and `node` sources. - if ( Array.isArray( value ) ) { - value = children.toHTML( value ); - } - - if ( this.props.format === 'string' ) { - const prepare = createPrepareEditableTree( this.props, 'format_value_functions' ); - - value = create( { - html: value, - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - } ); - value.formats = prepare( value ); - - return value; - } - - return value; - } - - valueToEditableHTML( value ) { - return toDom( { - value, - multilineTag: this.multilineTag, - prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), - } ).body.innerHTML; - } - - /** - * Removes editor only formats from the value. - * - * Editor only formats are applied using `prepareEditableTree`, so we need to - * remove them before converting the internal state - * - * @param {Object} value The internal rich-text value. - * @return {Object} A new rich-text value. - */ - removeEditorOnlyFormats( value ) { - this.props.formatTypes.forEach( ( formatType ) => { - // Remove formats created by prepareEditableTree, because they are editor only. - if ( formatType.__experimentalCreatePrepareEditableTree ) { - value = removeFormat( value, formatType.name, 0, value.text.length ); - } - } ); - - return value; - } - - /** - * Converts the internal value to the external data format. - * - * @param {Object} value The internal rich-text value. - * @return {*} The external data format, data type depends on props. - */ - valueToFormat( value ) { - value = this.removeEditorOnlyFormats( value ); - - // Handle deprecated `children` and `node` sources. - if ( this.usedDeprecatedChildrenSource ) { - return children.fromDOM( toDom( { - value, - multilineTag: this.multilineTag, - isEditableTree: false, - } ).body.childNodes ); - } - - if ( this.props.format === 'string' ) { - return toHTMLString( { - value, - multilineTag: this.multilineTag, - } ); - } - - return value; - } - - render() { - const { - tagName: Tagname = 'div', - style, - wrapperClassName, - className, - inlineToolbar = false, - formattingControls, - placeholder, - keepPlaceholderOnFocus = false, - isSelected, - autocompleters, - onTagNameChange, - } = this.props; - - // Generating a key that includes `tagName` ensures that if the tag - // changes, we replace the relevant element. This is needed because we - // prevent Editable component updates. - const key = Tagname; - const MultilineTag = this.multilineTag; - const ariaProps = pickAriaProps( this.props ); - const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && this.isEmpty(); - const classes = classnames( wrapperClassName, 'editor-rich-text block-editor-rich-text' ); - const record = this.getRecord(); - - return ( - <div className={ classes }> - { isSelected && this.multilineTag === 'li' && ( - <ListEdit - onTagNameChange={ onTagNameChange } - tagName={ Tagname } - value={ record } - onChange={ this.onChange } - /> - ) } - { isSelected && ! inlineToolbar && ( - <BlockFormatControls> - <FormatToolbar controls={ formattingControls } /> - </BlockFormatControls> - ) } - { isSelected && inlineToolbar && ( - <IsolatedEventContainer className="editor-rich-text__inline-toolbar block-editor-rich-text__inline-toolbar"> - <FormatToolbar controls={ formattingControls } /> - </IsolatedEventContainer> - ) } - <Autocomplete - onReplace={ this.props.onReplace } - completers={ autocompleters } - record={ record } - onChange={ this.onChange } - > - { ( { listBoxId, activeId } ) => ( - <> - <Editable - tagName={ Tagname } - style={ style } - record={ record } - valueToEditableHTML={ this.valueToEditableHTML } - isPlaceholderVisible={ isPlaceholderVisible } - aria-label={ placeholder } - aria-autocomplete="list" - aria-owns={ listBoxId } - aria-activedescendant={ activeId } - { ...ariaProps } - className={ className } - key={ key } - onPaste={ this.onPaste } - onInput={ this.onInput } - onCompositionEnd={ this.onCompositionEnd } - onKeyDown={ this.onKeyDown } - onFocus={ this.onFocus } - onBlur={ this.onBlur } - onMouseDown={ this.onPointerDown } - onTouchStart={ this.onPointerDown } - setRef={ this.setRef } - /> - { isPlaceholderVisible && - <Tagname - className={ classnames( 'editor-rich-text__editable block-editor-rich-text__editable', className ) } - style={ style } - > - { MultilineTag ? <MultilineTag>{ placeholder }</MultilineTag> : placeholder } - </Tagname> - } - { isSelected && <FormatEdit value={ record } onChange={ this.onChange } /> } - </> +const wrapperClasses = 'editor-rich-text block-editor-rich-text'; +const classes = 'editor-rich-text__editable block-editor-rich-text__editable'; + +function RichTextWraper( { + tagName, + value: originalValue, + onChange: originalOnChange, + selectionStart, + selectionEnd, + onSelectionChange, + multiline, + onTagNameChange, + inlineToolbar, + wrapperClassName, + className, + autocompleters, + onReplace, + onRemove, + onMerge, + onSplit, + isCaretWithinFormattedText, + onEnterFormattedText, + onExitFormattedText, + canUserUseUnfilteredHTML, + isSelected: originalIsSelected, + onCreateUndoLevel, + placeholder, + keepPlaceholderOnFocus, + // From experimental filter. + ...experimentalProps +} ) { + let adjustedValue = originalValue; + let adjustedOnChange = originalOnChange; + + // Handle deprecated format. + if ( Array.isArray( originalValue ) ) { + adjustedValue = children.toHTML( originalValue ); + adjustedOnChange = ( newValue ) => originalOnChange( children.fromDOM( + __unstableCreateElement( document, newValue ).childNodes + ) ); + } + + return ( + <RichText + { ...experimentalProps } + value={ adjustedValue } + onChange={ adjustedOnChange } + selectionStart={ selectionStart } + selectionEnd={ selectionEnd } + onSelectionChange={ onSelectionChange } + tagName={ tagName } + wrapperClassName={ classnames( wrapperClasses, wrapperClassName ) } + className={ classnames( classes, className ) } + placeholder={ placeholder } + keepPlaceholderOnFocus={ keepPlaceholderOnFocus } + __unstableIsSelected={ originalIsSelected } + __unstablePatterns={ getPatterns() } + __unstableEnterPatterns={ getEnterPatterns() } + __unstablePasteHandler={ pasteHandler } + __unstableAutocomplete={ Autocomplete } + __unstableAutocompleters={ autocompleters } + __unstableOnReplace={ onReplace } + __unstableOnRemove={ onRemove } + __unstableOnMerge={ onMerge } + __unstableOnSplit={ onSplit } + __unstableMultiline={ multiline } + __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText } + __unstableOnEnterFormattedText={ onEnterFormattedText } + __unstableOnExitFormattedText={ onExitFormattedText } + __unstableCanUserUseUnfilteredHTML={ canUserUseUnfilteredHTML } + __unstableOnCreateUndoLevel={ onCreateUndoLevel } + > + { ( { isSelected, value, onChange } ) => + <> + { isSelected && multiline === 'li' && ( + <ListEdit + onTagNameChange={ onTagNameChange } + tagName={ tagName } + value={ value } + onChange={ onChange } + /> ) } - </Autocomplete> - { isSelected && <RemoveBrowserShortcuts /> } - </div> - ); - } + { isSelected && ! inlineToolbar && ( + <BlockFormatControls> + <FormatToolbar /> + </BlockFormatControls> + ) } + { inlineToolbar && ( + <IsolatedEventContainer> + <FormatToolbar /> + </IsolatedEventContainer> + ) } + { isSelected && <RemoveBrowserShortcuts /> } + </> + } + </RichText> + ); } -RichText.defaultProps = { - formattingControls: [ 'bold', 'italic', 'link', 'strikethrough' ], - format: 'string', - value: '', -}; - const RichTextContainer = compose( [ withInstanceId, withBlockEditContext( ( { clientId } ) => ( { clientId } ) ), @@ -1124,7 +140,6 @@ const RichTextContainer = compose( [ getSelectionStart, getSelectionEnd, } = select( 'core/block-editor' ); - const { getFormatTypes } = select( 'core/rich-text' ); const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); @@ -1139,7 +154,6 @@ const RichTextContainer = compose( [ return { canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(), isCaretWithinFormattedText: isCaretWithinFormattedText(), - formatTypes: getFormatTypes(), selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, @@ -1166,9 +180,8 @@ const RichTextContainer = compose( [ }, }; } ), - withSafeTimeout, withFilters( 'experimentalRichText' ), -] )( RichText ); +] )( RichTextWraper ); RichTextContainer.Content = ( { value, tagName: Tag, multiline, ...props } ) => { let html = value; diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 7d8cfe2b737df5..61cbf2551751a1 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -1,869 +1,112 @@ -/*eslint no-console: ["error", { allow: ["warn"] }] */ - /** * External dependencies */ -import RCTAztecView from 'react-native-aztec'; -import { View, Platform } from 'react-native'; -import { - pickBy, -} from 'lodash'; -import memize from 'memize'; +import classnames from 'classnames'; +import { View } from 'react-native'; /** * WordPress dependencies */ -import { Component, RawHTML } from '@wordpress/element'; +import { RawHTML } from '@wordpress/element'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { pasteHandler, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; import { withInstanceId, compose } from '@wordpress/compose'; -import { BlockFormatControls } from '@wordpress/block-editor'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { - applyFormat, - getActiveFormat, - __unstableGetActiveFormats as getActiveFormats, - isEmpty, - create, - split, - toHTMLString, - insert, - __unstableInsertLineSeparator as insertLineSeparator, - __unstableIsEmptyLine as isEmptyLine, - __unstableRemoveLineSeparator as removeLineSeparator, - isCollapsed, - remove, -} from '@wordpress/rich-text'; -import { decodeEntities } from '@wordpress/html-entities'; -import { BACKSPACE } from '@wordpress/keycodes'; -import { - children, - isUnmodifiedDefaultBlock, - pasteHandler, -} from '@wordpress/blocks'; -import { isURL } from '@wordpress/url'; +import { RichText } from '@wordpress/rich-text'; /** * Internal dependencies */ -import FormatEdit from './format-edit'; +import Autocomplete from '../autocomplete'; +import BlockFormatControls from '../block-format-controls'; import FormatToolbar from './format-toolbar'; import { withBlockEditContext } from '../block-edit/context'; import { ListEdit } from './list-edit'; -import styles from './style.scss'; - -const isRichTextValueEmpty = ( value ) => { - return ! value || ! value.length; -}; - -const unescapeSpaces = ( text ) => { - return text.replace( /&nbsp;|&#160;/gi, ' ' ); -}; - -/** - * Calls {@link pasteHandler} with a fallback to plain text when HTML processing - * results in errors - * - * @param {Object} [options] The options to pass to {@link pasteHandler} - * - * @return {Array|string} A list of blocks or a string, depending on - * `handlerMode`. - */ -const saferPasteHandler = ( options ) => { - try { - return pasteHandler( options ); - } catch ( error ) { - window.console.log( 'Pasting HTML failed:', error ); - window.console.log( 'HTML:', options.HTML ); - window.console.log( 'Falling back to plain text.' ); - // fallback to plain text - return pasteHandler( { ...options, HTML: '' } ); - } -}; - -const gutenbergFormatNamesToAztec = { - 'core/bold': 'bold', - 'core/italic': 'italic', - 'core/strikethrough': 'strikethrough', -}; - -export class RichText extends Component { - constructor( { value, multiline, selectionStart, selectionEnd } ) { - super( ...arguments ); - - this.isMultiline = false; - if ( multiline === true || multiline === 'p' || multiline === 'li' ) { - this.multilineTag = multiline === true ? 'p' : multiline; - this.isMultiline = true; - } - - if ( this.multilineTag === 'li' ) { - this.multilineWrapperTags = [ 'ul', 'ol' ]; - } - this.onSplit = this.onSplit.bind( this ); - this.isIOS = Platform.OS === 'ios'; - this.createRecord = this.createRecord.bind( this ); - this.onChange = this.onChange.bind( this ); - this.onEnter = this.onEnter.bind( this ); - this.onBackspace = this.onBackspace.bind( this ); - this.onPaste = this.onPaste.bind( this ); - this.onFocus = this.onFocus.bind( this ); - this.onBlur = this.onBlur.bind( this ); - this.onTextUpdate = this.onTextUpdate.bind( this ); - this.onContentSizeChange = this.onContentSizeChange.bind( this ); - this.onFormatChange = this.onFormatChange.bind( this ); - this.formatToValue = memize( - this.formatToValue.bind( this ), - { maxSize: 1 } - ); - - // This prevents a bug in Aztec which triggers onSelectionChange twice on format change - this.onSelectionChange = this.onSelectionChange.bind( this ); - this.onSelectionChangeFromAztec = this.onSelectionChangeFromAztec.bind( this ); - this.valueToFormat = this.valueToFormat.bind( this ); - this.willTrimSpaces = this.willTrimSpaces.bind( this ); - this.getHtmlToRender = this.getHtmlToRender.bind( this ); - this.state = { - activeFormats: [], - selectedFormat: null, - height: 0, - }; - this.needsSelectionUpdate = false; - this.savedContent = ''; - this.isTouched = false; - this.lastAztecEventType = null; - - this.lastHistoryValue = value; - - // Internal values that are update synchronously, unlike props. - this.value = value; - this.selectionStart = selectionStart; - this.selectionEnd = selectionEnd; - } - - /** - * Get the current record (value and selection) from props and state. - * - * @return {Object} The current record (value and selection). - */ - getRecord() { - const { selectionStart: start, selectionEnd: end } = this.props; - let { value } = this.props; - - // Since we get the text selection from Aztec we need to be in sync with the HTML `value` - // Removing leading white spaces using `trim()` should make sure this is the case. - if ( typeof value === 'string' || value instanceof String ) { - value = value.trimLeft(); - } - - const { formats, replacements, text } = this.formatToValue( value ); - const { activeFormats } = this.state; - - return { formats, replacements, text, start, end, activeFormats }; - } - - /** - * Creates a RichText value "record" from the current content and selection - * information - * - * - * @return {Object} A RichText value with formats and selection. - */ - createRecord() { - const value = { - start: this.selectionStart, - end: this.selectionEnd, - ...create( { - html: this.value, - range: null, - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - } ), - }; - const start = Math.min( this.selectionStart, value.text.length ); - const end = Math.min( this.selectionEnd, value.text.length ); - return { ...value, start, end }; - } - - /** - * Signals to the RichText owner that the block can be replaced with two - * blocks as a result of splitting the block by pressing enter, or with - * blocks as a result of splitting the block by pasting block content in the - * instance. - * - * @param {Object} record The rich text value to split. - * @param {Array} pastedBlocks The pasted blocks to insert, if any. - */ - onSplit( record, pastedBlocks = [] ) { - const { - onReplace, - onSplit, - __unstableOnSplitMiddle: onSplitMiddle, - } = this.props; - - if ( ! onReplace || ! onSplit ) { - return; - } - - const blocks = []; - const [ before, after ] = split( record ); - const hasPastedBlocks = pastedBlocks.length > 0; - - // Create a block with the content before the caret if there's no pasted - // blocks, or if there are pasted blocks and the value is not empty. - // We do not want a leading empty block on paste, but we do if split - // with e.g. the enter key. - if ( ! hasPastedBlocks || ! isEmpty( before ) ) { - blocks.push( onSplit( this.valueToFormat( before ) ) ); - } - - if ( hasPastedBlocks ) { - blocks.push( ...pastedBlocks ); - } else if ( onSplitMiddle ) { - blocks.push( onSplitMiddle() ); - } - - // If there's pasted blocks, append a block with the content after the - // caret. Otherwise, do append and empty block if there is no - // `onSplitMiddle` prop, but if there is and the content is empty, the - // middle block is enough to set focus in. - if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { - blocks.push( onSplit( this.valueToFormat( after ) ) ); - } - - // If there are pasted blocks, set the selection to the last one. - // Otherwise, set the selection to the second block. - const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; - // The onSplit event can cause a content update event for this block. Such event should - // definitely be processed by our native components, since they have no knowledge of - // how the split works. Setting lastEventCount to undefined forces the native component to - // always update when provided with new content. - this.lastEventCount = undefined; - onReplace( blocks, indexToSelect ); - } - - valueToFormat( value ) { - // remove the outer root tags - return this.removeRootTagsProduceByAztec( toHTMLString( { - value, - multilineTag: this.multilineTag, - } ) ); - } - - getActiveFormatNames( record ) { - const { - formatTypes, - } = this.props; - - return formatTypes.map( ( { name } ) => name ).filter( ( name ) => { - return getActiveFormat( record, name ) !== undefined; - } ).map( ( name ) => gutenbergFormatNamesToAztec[ name ] ).filter( Boolean ); - } - - onFormatChange( record ) { - const { start, end, activeFormats = [] } = record; - const changeHandlers = pickBy( this.props, ( v, key ) => - key.startsWith( 'format_on_change_functions_' ) - ); - - Object.values( changeHandlers ).forEach( ( changeHandler ) => { - changeHandler( record.formats, record.text ); - } ); - - this.value = this.valueToFormat( record ); - this.props.onChange( this.value ); - this.setState( { activeFormats } ); - this.props.onSelectionChange( start, end ); - this.selectionStart = start; - this.selectionEnd = end; - - this.onCreateUndoLevel(); - - this.lastAztecEventType = 'format change'; - } - - onCreateUndoLevel() { - // If the content is the same, no level needs to be created. - if ( this.lastHistoryValue === this.value ) { - return; - } - - this.props.onCreateUndoLevel(); - this.lastHistoryValue = this.value; - } - - /* - * Cleans up any root tags produced by aztec. - * TODO: This should be removed on a later version when aztec doesn't return the top tag of the text being edited - */ - removeRootTagsProduceByAztec( html ) { - let result = this.removeRootTag( this.props.tagName, html ); - // Temporary workaround for https://github.com/WordPress/gutenberg/pull/13763 - if ( this.props.rootTagsToEliminate ) { - this.props.rootTagsToEliminate.forEach( ( element ) => { - result = this.removeRootTag( element, result ); - } ); - } - - if ( this.props.tagsToEliminate ) { - this.props.tagsToEliminate.forEach( ( element ) => { - result = this.removeTag( element, result ); - } ); - } - return result; - } - - removeRootTag( tag, html ) { - const openingTagRegexp = RegExp( '^<' + tag + '>', 'gim' ); - const closingTagRegexp = RegExp( '</' + tag + '>$', 'gim' ); - return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); - } - removeTag( tag, html ) { - const openingTagRegexp = RegExp( '<' + tag + '>', 'gim' ); - const closingTagRegexp = RegExp( '</' + tag + '>', 'gim' ); - return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); - } - - /* - * Handles any case where the content of the AztecRN instance has changed - */ - onChange( event ) { - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); - // On iOS, onChange can be triggered after selection changes, even though there are no content changes. - if ( contentWithoutRootTag === this.value ) { - return; - } - this.lastEventCount = event.nativeEvent.eventCount; - this.comesFromAztec = true; - this.firedAfterTextChanged = true; // the onChange event always fires after the fact - this.onTextUpdate( event ); - this.lastAztecEventType = 'input'; - } - - onTextUpdate( event ) { - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); - - const refresh = this.value !== contentWithoutRootTag; - this.value = contentWithoutRootTag; - - // we don't want to refresh if our goal is just to create a record - if ( refresh ) { - this.props.onChange( contentWithoutRootTag ); - } - } - - /* - * Handles any case where the content of the AztecRN instance has changed in size - */ - onContentSizeChange( contentSize ) { - const contentHeight = contentSize.height; - this.setState( { height: contentHeight } ); - this.lastAztecEventType = 'content size change'; - } - - // eslint-disable-next-line no-unused-vars - onEnter( event ) { - if ( this.props.onEnter ) { - this.props.onEnter(); - return; - } - - this.lastEventCount = event.nativeEvent.eventCount; - this.comesFromAztec = true; - this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; - const { onReplace, onSplit } = this.props; - const canSplit = onReplace && onSplit; - const currentRecord = this.createRecord(); - if ( this.multilineTag ) { - if ( event.shiftKey ) { - this.needsSelectionUpdate = true; - const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; - this.onFormatChange( insertedLineBreak ); - } else if ( canSplit && isEmptyLine( currentRecord ) ) { - this.onSplit( currentRecord ); - } else { - this.needsSelectionUpdate = true; - const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; - this.onFormatChange( insertedLineSeparator ); +const wrapperClasses = 'editor-rich-text block-editor-rich-text'; +const classes = 'editor-rich-text__editable block-editor-rich-text__editable'; + +function RichTextWraper( { + tagName, + value: originalValue, + onChange: originalOnChange, + selectionStart, + selectionEnd, + onSelectionChange, + multiline, + onTagNameChange, + inlineToolbar, + wrapperClassName, + className, + autocompleters, + onReplace, + onRemove, + onMerge, + onSplit, + isCaretWithinFormattedText, + onEnterFormattedText, + onExitFormattedText, + canUserUseUnfilteredHTML, + isSelected: originalIsSelected, + onCreateUndoLevel, + placeholder, + keepPlaceholderOnFocus, + // From experimental filter. + ...experimentalProps +} ) { + const adjustedValue = originalValue; + const adjustedOnChange = originalOnChange; + + return ( + <RichText + { ...experimentalProps } + value={ adjustedValue } + onChange={ adjustedOnChange } + selectionStart={ selectionStart } + selectionEnd={ selectionEnd } + onSelectionChange={ onSelectionChange } + tagName={ tagName } + wrapperClassName={ classnames( wrapperClasses, wrapperClassName ) } + className={ classnames( classes, className ) } + placeholder={ placeholder } + keepPlaceholderOnFocus={ keepPlaceholderOnFocus } + __unstableIsSelected={ originalIsSelected } + //__unstablePatterns={ getPatterns() } + //__unstableEnterPatterns={ getEnterPatterns() } + __unstablePasteHandler={ pasteHandler } + __unstableAutocomplete={ Autocomplete } + __unstableAutocompleters={ autocompleters } + __unstableOnReplace={ onReplace } + __unstableOnRemove={ onRemove } + __unstableOnMerge={ onMerge } + __unstableOnSplit={ onSplit } + __unstableMultiline={ multiline } + __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText } + __unstableOnEnterFormattedText={ onEnterFormattedText } + __unstableOnExitFormattedText={ onExitFormattedText } + __unstableCanUserUseUnfilteredHTML={ canUserUseUnfilteredHTML } + __unstableOnCreateUndoLevel={ onCreateUndoLevel } + > + { ( { isSelected, value, onChange } ) => + <View> + { isSelected && multiline === 'li' && ( + <ListEdit + onTagNameChange={ onTagNameChange } + tagName={ tagName } + value={ value } + onChange={ onChange } + /> + ) } + { isSelected && ! inlineToolbar && ( + <BlockFormatControls> + <FormatToolbar /> + </BlockFormatControls> + ) } + </View> } - } else if ( event.shiftKey || ! onSplit ) { - this.needsSelectionUpdate = true; - const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; - this.onFormatChange( insertedLineBreak ); - } else { - this.onSplit( currentRecord ); - } - this.lastAztecEventType = 'input'; - } - - // eslint-disable-next-line no-unused-vars - onBackspace( event ) { - const { onMerge, onRemove } = this.props; - if ( ! onMerge && ! onRemove ) { - return; - } - - const keyCode = BACKSPACE; // TODO : should we differentiate BACKSPACE and DELETE? - const isReverse = keyCode === BACKSPACE; - - this.lastEventCount = event.nativeEvent.eventCount; - this.comesFromAztec = true; - this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; - const value = this.createRecord(); - const { start, end } = value; - let newValue; - - // Always handle full content deletion ourselves. - if ( start === 0 && end !== 0 && end >= value.text.length ) { - newValue = remove( value, start, end ); - this.props.onChange( newValue ); - return; - } - - if ( this.multilineTag ) { - newValue = removeLineSeparator( value, keyCode === BACKSPACE ); - if ( newValue ) { - this.onFormatChange( newValue ); - return; - } - } - - const empty = this.isEmpty(); - - if ( onMerge ) { - onMerge( ! isReverse ); - } - - // Only handle remove on Backspace. This serves dual-purpose of being - // an intentional user interaction distinguishing between Backspace and - // Delete to remove the empty field, but also to avoid merge & remove - // causing destruction of two fields (merge, then removed merged). - if ( onRemove && empty && isReverse ) { - onRemove( ! isReverse ); - } - - event.preventDefault(); - this.lastAztecEventType = 'input'; - } - - /** - * Handles a paste event from the native Aztec Wrapper. - * - * @param {PasteEvent} event The paste event which wraps `nativeEvent`. - */ - onPaste( event ) { - const { onSplit } = this.props; - - const { pastedText, pastedHtml, files } = event.nativeEvent; - const currentRecord = this.createRecord(); - - event.preventDefault(); - - // Only process file if no HTML is present. - // Note: a pasted file may have the URL as plain text. - if ( files && files.length > 0 ) { - const uploadId = Number.MAX_SAFE_INTEGER; - let html = ''; - files.forEach( ( file ) => { - html += `<img src="${ file }" class="wp-image-${ uploadId }">`; - } ); - const content = pasteHandler( { - HTML: html, - mode: 'BLOCKS', - tagName: this.props.tagName, - } ); - const shouldReplace = this.props.onReplace && this.isEmpty(); - - if ( shouldReplace ) { - this.props.onReplace( content ); - } else { - this.onSplit( currentRecord, content ); - } - - return; - } - - // There is a selection, check if a URL is pasted. - if ( ! isCollapsed( currentRecord ) ) { - const trimmedText = ( pastedHtml || pastedText ).replace( /<[^>]+>/g, '' ) - .trim(); - - // A URL was pasted, turn the selection into a link - if ( isURL( trimmedText ) ) { - const linkedRecord = applyFormat( currentRecord, { - type: 'a', - attributes: { - href: decodeEntities( trimmedText ), - }, - } ); - this.value = this.valueToFormat( linkedRecord ); - this.props.onChange( this.value ); - - // Allows us to ask for this information when we get a report. - window.console.log( 'Created link:\n\n', trimmedText ); - - return; - } - } - - const shouldReplace = this.props.onReplace && this.isEmpty(); - - let mode = 'INLINE'; - - if ( shouldReplace ) { - mode = 'BLOCKS'; - } else if ( onSplit ) { - mode = 'AUTO'; - } - - const pastedContent = saferPasteHandler( { - HTML: pastedHtml, - plainText: pastedText, - mode, - tagName: this.props.tagName, - canUserUseUnfilteredHTML: this.props.canUserUseUnfilteredHTML, - } ); - - if ( typeof pastedContent === 'string' ) { - const recordToInsert = create( { html: pastedContent } ); - const resultingRecord = insert( currentRecord, recordToInsert ); - const resultingContent = this.valueToFormat( resultingRecord ); - - this.lastEventCount = undefined; - this.value = resultingContent; - - // explicitly set selection after inline paste - this.onSelectionChange( resultingRecord.start, resultingRecord.end ); - - this.props.onChange( this.value ); - } else if ( onSplit ) { - if ( ! pastedContent.length ) { - return; - } - - if ( shouldReplace ) { - this.props.onReplace( pastedContent ); - } else { - this.onSplit( currentRecord, pastedContent ); - } - } - } - - onFocus() { - this.isTouched = true; - - const { unstableOnFocus } = this.props; - - if ( unstableOnFocus ) { - unstableOnFocus(); - } - - // We know for certain that on focus, the old selection is invalid. It - // will be recalculated on `selectionchange`. - const index = undefined; - - this.props.onSelectionChange( index, index ); - - this.lastAztecEventType = 'focus'; - } - - onBlur( event ) { - this.isTouched = false; - - if ( this.props.onBlur ) { - this.props.onBlur( event ); - } - - this.lastAztecEventType = 'blur'; - } - - onSelectionChange( start, end ) { - const hasChanged = this.selectionStart !== start || this.selectionEnd !== end; - - this.selectionStart = start; - this.selectionEnd = end; - - // This is a manual selection change event if onChange was not triggered just before - // and we did not just trigger a text update - // `onChange` could be the last event and could have been triggered a long time ago so - // this approach is not perfectly reliable - const isManual = this.lastAztecEventType !== 'input' && this.props.value === this.value; - if ( hasChanged && isManual ) { - const value = this.createRecord(); - const activeFormats = getActiveFormats( value ); - this.setState( { activeFormats } ); - } - - this.props.onSelectionChange( start, end ); - } - - onSelectionChangeFromAztec( start, end, text, event ) { - // `end` can be less than `start` on iOS - // Let's fix that here so `rich-text/slice` can work properly - const realStart = Math.min( start, end ); - const realEnd = Math.max( start, end ); - - // check and dicsard stray event, where the text and selection is equal to the ones already cached - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); - if ( contentWithoutRootTag === this.value && realStart === this.selectionStart && realEnd === this.selectionEnd ) { - return; - } - - this.comesFromAztec = true; - this.firedAfterTextChanged = true; // Selection change event always fires after the fact - - // update text before updating selection - // Make sure there are changes made to the content before upgrading it upward - this.onTextUpdate( event ); - - this.onSelectionChange( realStart, realEnd ); - - // Update lastEventCount to prevent Aztec from re-rendering the content it just sent - this.lastEventCount = event.nativeEvent.eventCount; - - this.lastAztecEventType = 'selection change'; - } - - isEmpty() { - return isEmpty( this.formatToValue( this.props.value ) ); - } - - formatToValue( value ) { - // Handle deprecated `children` and `node` sources. - if ( Array.isArray( value ) ) { - return create( { - html: children.toHTML( value ), - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - } ); - } - - if ( this.props.format === 'string' ) { - return create( { - html: value, - multilineTag: this.multilineTag, - multilineWrapperTags: this.multilineWrapperTags, - } ); - } - - // Guard for blocks passing `null` in onSplit callbacks. May be removed - // if onSplit is revised to not pass a `null` value. - if ( value === null ) { - return create(); - } - - return value; - } - - shouldComponentUpdate( nextProps ) { - if ( nextProps.tagName !== this.props.tagName ) { - this.lastEventCount = undefined; - this.value = undefined; - return true; - } - - // TODO: Please re-introduce the check to avoid updating the content right after an `onChange` call. - // It was removed in https://github.com/WordPress/gutenberg/pull/12417 to fix undo/redo problem. - - // If the component is changed React side (undo/redo/merging/splitting/custom text actions) - // we need to make sure the native is updated as well. - - // Also, don't trust the "this.lastContent" as on Android, incomplete text events arrive - // with only some of the text, while the virtual keyboard's suggestion system does its magic. - // ** compare with this.lastContent for optimizing performance by not forcing Aztec with text it already has - // , but compare with props.value to not lose "half word" text because of Android virtual keyb autosuggestion behavior - if ( ( typeof nextProps.value !== 'undefined' ) && - ( typeof this.props.value !== 'undefined' ) && - ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) && - nextProps.value !== this.props.value ) { - // Gutenberg seems to try to mirror the caret state even on events that only change the content so, - // let's force caret update if state has selection set. - if ( typeof nextProps.selectionStart !== 'undefined' && typeof nextProps.selectionEnd !== 'undefined' ) { - this.needsSelectionUpdate = true; - } - - this.lastEventCount = undefined; // force a refresh on the native side - } - - if ( ! this.comesFromAztec ) { - if ( ( typeof nextProps.selectionStart !== 'undefined' ) && - ( typeof nextProps.selectionEnd !== 'undefined' ) && - nextProps.selectionStart !== this.props.selectionStart && - nextProps.selectionStart !== this.selectionStart && - nextProps.isSelected ) { - this.needsSelectionUpdate = true; - this.lastEventCount = undefined; // force a refresh on the native side - } - } - - return true; - } - - componentDidMount() { - // Request focus if wrapping block is selected and parent hasn't inhibited the focus request. This method of focusing - // is trying to implement the web-side counterpart of BlockList's `focusTabbable` where the BlockList is focusing an - // inputbox by searching the DOM. We don't have the DOM in RN so, using the combination of blockIsSelected and __unstableMobileNoFocusOnMount - // to determine if we should focus the RichText. - if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { - this._editor.focus(); - this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); - } - } - - componentWillUnmount() { - if ( this._editor.isFocused() && this.props.shouldBlurOnUnmount ) { - this._editor.blur(); - } - } - - componentDidUpdate( prevProps ) { - if ( this.props.value !== this.value ) { - this.value = this.props.value; - this.lastEventCount = undefined; - } - if ( this.props.isSelected && ! prevProps.isSelected ) { - this._editor.focus(); - // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange - // if its internal value hasn't change. When created, default value is 0, 0 - this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); - } else if ( ! this.props.isSelected && prevProps.isSelected ) { - this._editor.blur(); - } - } - - willTrimSpaces( html ) { - // regex for detecting spaces around block element html tags - const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; - const leadingOrTrailingSpaces = new RegExp( `(\\s+)<\/?${ blockHtmlElements }>|<\/?${ blockHtmlElements }>(\\s+)`, 'g' ); - const matches = html.match( leadingOrTrailingSpaces ); - if ( matches && matches.length > 0 ) { - return true; - } - - return false; - } - - getHtmlToRender( record, tagName ) { - // Save back to HTML from React tree - const value = this.valueToFormat( record ); - - if ( value === undefined || value === '' ) { - this.lastEventCount = undefined; // force a refresh on the native side - return ''; - } else if ( tagName ) { - return `<${ tagName }>${ value }</${ tagName }>`; - } - return value; - } - - render() { - const { - tagName, - style, - formattingControls, - isSelected, - onTagNameChange, - } = this.props; - - const record = this.getRecord(); - const html = this.getHtmlToRender( record, tagName ); - - let minHeight = styles[ 'block-editor-rich-text' ].minHeight; - if ( style && style.minHeight ) { - minHeight = style.minHeight; - } - - let selection = null; - if ( this.needsSelectionUpdate ) { - this.needsSelectionUpdate = false; - selection = { start: this.props.selectionStart, end: this.props.selectionEnd }; - - // On AztecAndroid, setting the caret to an out-of-bounds position will crash the editor so, let's check for some cases. - if ( ! this.isIOS ) { - // Aztec performs some html text cleanup while parsing it so, its internal representation gets out-of-sync with the - // representation of the format-lib on the RN side. We need to avoid trying to set the caret position because it may - // be outside the text bounds and crash Aztec, at least on Android. - if ( this.willTrimSpaces( html ) ) { - // the html will get trimmed by the cleaning up functions in Aztec and caret position will get out-of-sync. - // So, skip forcing it, let Aztec just do its best and just log the fact. - console.warn( 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' ); - selection = null; - } else if ( this.props.selectionStart > record.text.length || this.props.selectionEnd > record.text.length ) { - console.warn( 'Oops, selection will land outside the text, skipping setting it...' ); - selection = null; - } - } - } - - if ( this.comesFromAztec ) { - this.comesFromAztec = false; - this.firedAfterTextChanged = false; - } - - return ( - <View> - { isSelected && this.multilineTag === 'li' && ( - <ListEdit - onTagNameChange={ onTagNameChange } - tagName={ tagName } - value={ record } - onChange={ this.onFormatChange } - /> - ) } - { isSelected && ( - <BlockFormatControls> - <FormatToolbar controls={ formattingControls } /> - </BlockFormatControls> - ) } - <RCTAztecView - ref={ ( ref ) => { - this._editor = ref; - - if ( this.props.setRef ) { - this.props.setRef( ref ); - } - } } - style={ { - ...style, - minHeight: Math.max( minHeight, this.state.height ), - } } - text={ { text: html, eventCount: this.lastEventCount, selection } } - placeholder={ this.props.placeholder } - placeholderTextColor={ this.props.placeholderTextColor || styles[ 'block-editor-rich-text-placeholder' ].color } - deleteEnter={ this.props.deleteEnter } - onChange={ this.onChange } - onFocus={ this.onFocus } - onBlur={ this.onBlur } - onEnter={ this.onEnter } - onBackspace={ this.onBackspace } - onPaste={ this.onPaste } - activeFormats={ this.getActiveFormatNames( record ) } - onContentSizeChange={ this.onContentSizeChange } - onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } - onSelectionChange={ this.onSelectionChangeFromAztec } - blockType={ { tag: tagName } } - color={ styles[ 'block-editor-rich-text' ].color } - linkTextColor={ styles[ 'block-editor-rich-text' ].textDecorationColor } - maxImagesWidth={ 200 } - fontFamily={ this.props.fontFamily || styles[ 'block-editor-rich-text' ].fontFamily } - fontSize={ this.props.fontSize || ( style && style.fontSize ) } - fontWeight={ this.props.fontWeight } - fontStyle={ this.props.fontStyle } - disableEditingMenu={ this.props.disableEditingMenu } - isMultiline={ this.isMultiline } - textAlign={ this.props.textAlign } - /> - { isSelected && <FormatEdit value={ record } onChange={ this.onFormatChange } /> } - </View> - ); - } + </RichText> + ); } -RichText.defaultProps = { - formattingControls: [ 'bold', 'italic', 'link', 'strikethrough' ], - format: 'string', - tagName: 'div', -}; - const RichTextContainer = compose( [ withInstanceId, withBlockEditContext( ( { clientId, onCaretVerticalPositionChange, isSelected }, ownProps ) => { @@ -929,7 +172,7 @@ const RichTextContainer = compose( [ }, }; } ), -] )( RichText ); +] )( RichTextWraper ); RichTextContainer.Content = ( { value, format, tagName: Tag, multiline, ...props } ) => { let content; @@ -957,12 +200,23 @@ RichTextContainer.Content = ( { value, format, tagName: Tag, multiline, ...props return content; }; -RichTextContainer.isEmpty = isRichTextValueEmpty; +RichTextContainer.isEmpty = ( value = '' ) => { + // Handle deprecated `children` and `node` sources. + if ( Array.isArray( value ) ) { + return ! value || value.length === 0; + } + + return value.length === 0; +}; RichTextContainer.Content.defaultProps = { format: 'string', + value: '', }; +/** + * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/rich-text/README.md + */ export default RichTextContainer; export { RichTextShortcut } from './shortcut'; export { RichTextToolbarButton } from './toolbar-button'; diff --git a/packages/block-editor/src/components/rich-text/patterns.js b/packages/block-editor/src/components/rich-text/patterns.js index 6f68efa08b1efe..4c159c5002965f 100644 --- a/packages/block-editor/src/components/rich-text/patterns.js +++ b/packages/block-editor/src/components/rich-text/patterns.js @@ -9,12 +9,12 @@ import { slice, } from '@wordpress/rich-text'; -export function getPatterns( { onReplace, valueToFormat } ) { +export function getPatterns() { const prefixTransforms = getBlockTransforms( 'from' ) .filter( ( { type } ) => type === 'prefix' ); return [ - ( record ) => { + ( record, onReplace, valueToFormat ) => { if ( ! onReplace ) { return record; } @@ -76,3 +76,23 @@ export function getPatterns( { onReplace, valueToFormat } ) { }, ]; } + +export function getEnterPatterns() { + const transforms = getBlockTransforms( 'from' ) + .filter( ( { type } ) => type === 'enter' ); + + return ( record, onReplace ) => { + const text = getTextContent( record ); + const transformation = findTransform( transforms, ( item ) => { + return item.regExp.test( text ); + } ); + + if ( ! transformation ) { + return record; + } + + onReplace( [ + transformation.transform( { content: text } ), + ] ); + }; +} diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index d1560a163c24d4..d4a7e39978a4aa 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -1,32 +1,9 @@ - .block-editor-rich-text { // This is needed to position the formatting toolbar. position: relative; } .block-editor-rich-text__editable { - position: relative; - - // In HTML, leading and trailing spaces are not visible, and multiple spaces - // elsewhere are visually reduced to one space. This rule prevents spaces - // from collapsing so all space is visible in the editor and can be removed. - // It also prevents some browsers from inserting non-breaking spaces at the - // end of a line to prevent the space from visually disappearing. Sometimes - // these non breaking spaces can linger in the editor causing unwanted non - // breaking spaces in between words. If also prevent Firefox from inserting - // a trailing `br` node to visualise any trailing space, causing the element - // to be saved. - // - // > Authors are encouraged to set the 'white-space' property on editing - // > hosts and on markup that was originally created through these editing - // > mechanisms to the value 'pre-wrap'. Default HTML whitespace handling is - // > not well suited to WYSIWYG editing, and line wrapping will not work - // > correctly in some corner cases if 'white-space' is left at its default - // > value. - // > - // > https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors - white-space: pre-wrap !important; - > p:first-child { margin-top: 0; } diff --git a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js index c49c1df95b8360..5eba1b8ec67128 100644 --- a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js +++ b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js @@ -15,7 +15,6 @@ }, category: 'formatting', edit: function( { attributes, setAttributes } ) { - console.log( attributes.value ) return el( RichText, { tagName: 'p', value: attributes.value, diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 2c9435e1ef6b36..d325e81fc725d2 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -8,12 +8,13 @@ import { isEmpty } from 'lodash'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { RichText } from '@wordpress/block-editor'; +import { RichText } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withDispatch } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; +import { pasteHandler } from '@wordpress/blocks'; /** * Internal dependencies @@ -93,6 +94,7 @@ class PostTitle extends Component { onBlur={ this.props.onBlur } // always assign onBlur as a props multiline={ false } style={ style } + styles={ styles } fontSize={ 24 } fontWeight={ 'bold' } deleteEnter={ true } @@ -101,12 +103,13 @@ class PostTitle extends Component { } } placeholder={ decodedPlaceholder } value={ title } - onSplit={ () => { } } + onSelectionChange={ () => { } } onEnter={ this.props.onEnterPress } disableEditingMenu={ true } setRef={ ( ref ) => { this.titleViewRef = ref; } } + __unstablePasteHandler={ pasteHandler } > </RichText> </View> diff --git a/packages/editor/src/components/post-title/style.native.scss b/packages/editor/src/components/post-title/style.native.scss index 5a17299add6e6f..a583018c37e377 100644 --- a/packages/editor/src/components/post-title/style.native.scss +++ b/packages/editor/src/components/post-title/style.native.scss @@ -6,3 +6,4 @@ padding-bottom: $title-block-padding-bottom; margin-top: 24; } + diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 36fa10fae53e02..a29d9d3f0dac3f 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -273,6 +273,11 @@ _Returns_ - `Object`: A new value with replacements applied. +<a name="RichText" href="#RichText">#</a> **RichText** + +Renders a rich content input, providing users with the option to format the +content. + <a name="slice" href="#slice">#</a> **slice** Slice a Rich Text value from `startIndex` to `endIndex`. Indices are diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 2422f20a872611..602d8f15911128 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -22,11 +22,21 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", + "@wordpress/blob": "file:../blob", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", "@wordpress/hooks": "file:../hooks", + "@wordpress/html-entities": "file:../html-entities", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", + "@wordpress/keycodes": "file:../keycodes", + "@wordpress/url": "file:../url", + "classnames": "^2.2.5", "lodash": "^4.17.11", + "memize": "^1.0.5", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/block-editor/src/components/rich-text/aria.js b/packages/rich-text/src/component/aria.js similarity index 100% rename from packages/block-editor/src/components/rich-text/aria.js rename to packages/rich-text/src/component/aria.js diff --git a/packages/block-editor/src/components/rich-text/editable.js b/packages/rich-text/src/component/editable.js similarity index 78% rename from packages/block-editor/src/components/rich-text/editable.js rename to packages/rich-text/src/component/editable.js index c7c9a854fc0b8d..b4054a883c0d27 100644 --- a/packages/block-editor/src/components/rich-text/editable.js +++ b/packages/rich-text/src/component/editable.js @@ -2,7 +2,6 @@ * External dependencies */ import { isEqual } from 'lodash'; -import classnames from 'classnames'; /** * WordPress dependencies @@ -84,10 +83,6 @@ function applyInternetExplorerInputFix( editorNode ) { const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; -const oldClassName = 'editor-rich-text__editable'; - -export const className = 'block-editor-rich-text__editable'; - /** * Whether or not the user agent is Internet Explorer. * @@ -115,15 +110,14 @@ export default class Editable extends Component { if ( ! isEqual( this.props.style, nextProps.style ) ) { this.editorNode.setAttribute( 'style', '' ); - Object.assign( this.editorNode.style, nextProps.style ); + Object.assign( this.editorNode.style, { + ...( nextProps.style || {} ), + whiteSpace: 'pre-wrap', + } ); } if ( ! isEqual( this.props.className, nextProps.className ) ) { - this.editorNode.className = classnames( - className, - oldClassName, - nextProps.className - ); + this.editorNode.className = nextProps.className; } const { removedKeys, updatedKeys } = diffAriaProps( this.props, nextProps ); @@ -160,24 +154,48 @@ export default class Editable extends Component { render() { const { tagName = 'div', - style, + style = {}, record, valueToEditableHTML, - className: additionalClassName, + className, isPlaceholderVisible, ...remainingProps } = this.props; delete remainingProps.setRef; + // In HTML, leading and trailing spaces are not visible, and multiple + // spaces elsewhere are visually reduced to one space. This rule + // prevents spaces from collapsing so all space is visible in the editor + // and can be removed. + // It also prevents some browsers from inserting non-breaking spaces at + // the end of a line to prevent the space from visually disappearing. + // Sometimes these non breaking spaces can linger in the editor causing + // unwanted non breaking spaces in between words. If also prevent + // Firefox from inserting a trailing `br` node to visualise any trailing + // space, causing the element to be saved. + // + // > Authors are encouraged to set the 'white-space' property on editing + // > hosts and on markup that was originally created through these + // > editing mechanisms to the value 'pre-wrap'. Default HTML whitespace + // > handling is not well suited to WYSIWYG editing, and line wrapping + // > will not work correctly in some corner cases if 'white-space' is + // > left at its default value. + // > + // > https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors + const whiteSpace = 'pre-wrap'; + return createElement( tagName, { role: 'textbox', 'aria-multiline': true, - className: classnames( className, oldClassName, additionalClassName ), + className, contentEditable: true, [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, ref: this.bindEditorNode, - style, + style: { + ...style, + whiteSpace, + }, suppressContentEditableWarning: true, dangerouslySetInnerHTML: { __html: valueToEditableHTML( record ) }, ...remainingProps, diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/rich-text/src/component/format-edit.js similarity index 87% rename from packages/block-editor/src/components/rich-text/format-edit.js rename to packages/rich-text/src/component/format-edit.js index f37de7f2091b5f..9a9d1945ba8844 100644 --- a/packages/block-editor/src/components/rich-text/format-edit.js +++ b/packages/rich-text/src/component/format-edit.js @@ -2,7 +2,12 @@ * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { getActiveFormat, getActiveObject } from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ +import { getActiveFormat } from '../get-active-format'; +import { getActiveObject } from '../get-active-object'; const FormatEdit = ( { formatTypes, onChange, value } ) => { return ( diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js new file mode 100644 index 00000000000000..56e3cb3305bc32 --- /dev/null +++ b/packages/rich-text/src/component/index.js @@ -0,0 +1,1116 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { + find, + isNil, + pickBy, +} from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { isHorizontalEdge } from '@wordpress/dom'; +import { createBlobURL } from '@wordpress/blob'; +import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; +import { withSelect } from '@wordpress/data'; +import { withSafeTimeout, compose } from '@wordpress/compose'; +import { isURL } from '@wordpress/url'; +import { decodeEntities } from '@wordpress/html-entities'; +import isShallowEqual from '@wordpress/is-shallow-equal'; + +/** + * Internal dependencies + */ +import FormatEdit from './format-edit'; +import Editable from './editable'; +import { pickAriaProps } from './aria'; +import { isEmpty, isEmptyLine } from '../is-empty'; +import { create } from '../create'; +import { apply, toDom } from '../to-dom'; +import { applyFormat } from '../apply-format'; +import { split } from '../split'; +import { toHTMLString } from '../to-html-string'; +import { insert } from '../insert'; +import { insertLineSeparator } from '../insert-line-separator'; +import { remove } from '../remove'; +import { removeFormat } from '../remove-format'; +import { isCollapsed } from '../is-collapsed'; +import { LINE_SEPARATOR } from '../special-characters'; +import { indentListItems } from '../indent-list-items'; +import { getActiveFormats } from '../get-active-formats'; +import { updateFormats } from '../update-formats'; +import { replace } from '../replace'; +import { removeLineSeparator } from '../remove-line-separator'; + +/** + * Browser dependencies + */ + +const { getSelection, getComputedStyle } = window; + +/** + * All inserting input types that would insert HTML into the DOM. + * + * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes + * + * @type {Set} + */ +const INSERTION_INPUT_TYPES_TO_IGNORE = new Set( [ + 'insertParagraph', + 'insertOrderedList', + 'insertUnorderedList', + 'insertHorizontalRule', + 'insertLink', +] ); + +/** + * Global stylesheet. + */ +const globalStyle = document.createElement( 'style' ); + +document.head.appendChild( globalStyle ); + +function createPrepareEditableTree( props, prefix ) { + const fns = Object.keys( props ).reduce( ( accumulator, key ) => { + if ( key.startsWith( prefix ) ) { + accumulator.push( props[ key ] ); + } + + return accumulator; + }, [] ); + + return ( value ) => fns.reduce( ( accumulator, fn ) => { + return fn( accumulator, value.text ); + }, value.formats ); +} + +/** + * See export statement below. + */ +class RichText extends Component { + constructor( { + value, + __unstableMultiline: multiline, + selectionStart, + selectionEnd, + } ) { + super( ...arguments ); + + if ( multiline === true || multiline === 'p' || multiline === 'li' ) { + this.multilineTag = multiline === true ? 'p' : multiline; + } + + if ( this.multilineTag === 'li' ) { + this.multilineWrapperTags = [ 'ul', 'ol' ]; + } + + this.onFocus = this.onFocus.bind( this ); + this.onBlur = this.onBlur.bind( this ); + this.onChange = this.onChange.bind( this ); + this.onDeleteKeyDown = this.onDeleteKeyDown.bind( this ); + this.onKeyDown = this.onKeyDown.bind( this ); + this.onPaste = this.onPaste.bind( this ); + this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); + this.onInput = this.onInput.bind( this ); + this.onCompositionEnd = this.onCompositionEnd.bind( this ); + this.onSelectionChange = this.onSelectionChange.bind( this ); + this.getRecord = this.getRecord.bind( this ); + this.createRecord = this.createRecord.bind( this ); + this.applyRecord = this.applyRecord.bind( this ); + this.isEmpty = this.isEmpty.bind( this ); + this.valueToFormat = this.valueToFormat.bind( this ); + this.setRef = this.setRef.bind( this ); + this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); + this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); + this.onPointerDown = this.onPointerDown.bind( this ); + this.formatToValue = this.formatToValue.bind( this ); + this.onSplit = this.onSplit.bind( this ); + + this.state = {}; + + this.lastHistoryValue = value; + + // Internal values are updated synchronously, unlike props and state. + this.value = value; + this.record = this.formatToValue( value ); + this.record.start = selectionStart; + this.record.end = selectionEnd; + } + + componentWillUnmount() { + document.removeEventListener( 'selectionchange', this.onSelectionChange ); + } + + setRef( node ) { + if ( node ) { + if ( process.env.NODE_ENV === 'development' ) { + const computedStyle = getComputedStyle( node ); + + if ( computedStyle.display === 'inline' ) { + // eslint-disable-next-line no-console + console.warn( 'RichText cannot be used with an inline container. Please use a different tagName.' ); + } + } + + this.editableRef = node; + } else { + delete this.editableRef; + } + } + + /** + * Get the current record (value and selection) from props and state. + * + * @return {Object} The current record (value and selection). + */ + getRecord() { + return this.record; + } + + createRecord() { + const selection = getSelection(); + const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; + + return create( { + element: this.editableRef, + range, + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + __unstableIsEditableTree: true, + } ); + } + + applyRecord( record, { domOnly } = {} ) { + apply( { + value: record, + current: this.editableRef, + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), + __unstableDomOnly: domOnly, + } ); + } + + isEmpty() { + return isEmpty( this.record ); + } + + /** + * Handles a paste event. + * + * Saves the pasted data as plain text in `pastedPlainText`. + * + * @param {PasteEvent} event The paste event. + */ + onPaste( event ) { + const { + tagName, + __unstableCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, + __unstablePasteHandler: pasteHandler, + __unstableOnReplace: onReplace, + __unstableOnSplit: onSplit, + } = this.props; + const clipboardData = event.clipboardData; + let { items, files } = clipboardData; + + // In Edge these properties can be null instead of undefined, so a more + // rigorous test is required over using default values. + items = isNil( items ) ? [] : items; + files = isNil( files ) ? [] : files; + + let plainText = ''; + let html = ''; + + // IE11 only supports `Text` as an argument for `getData` and will + // otherwise throw an invalid argument error, so we try the standard + // arguments first, then fallback to `Text` if they fail. + try { + plainText = clipboardData.getData( 'text/plain' ); + html = clipboardData.getData( 'text/html' ); + } catch ( error1 ) { + try { + html = clipboardData.getData( 'Text' ); + } catch ( error2 ) { + // Some browsers like UC Browser paste plain text by default and + // don't support clipboardData at all, so allow default + // behaviour. + return; + } + } + + event.preventDefault(); + + // Allows us to ask for this information when we get a report. + window.console.log( 'Received HTML:\n\n', html ); + window.console.log( 'Received plain text:\n\n', plainText ); + + // Only process file if no HTML is present. + // Note: a pasted file may have the URL as plain text. + const item = find( [ ...items, ...files ], ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); + const record = this.getRecord(); + + if ( item && ! html ) { + const file = item.getAsFile ? item.getAsFile() : item; + const content = pasteHandler( { + HTML: `<img src="${ createBlobURL( file ) }">`, + mode: 'BLOCKS', + tagName, + } ); + const shouldReplace = onReplace && this.isEmpty(); + + // Allows us to ask for this information when we get a report. + window.console.log( 'Received item:\n\n', file ); + + if ( shouldReplace ) { + onReplace( content ); + } else if ( this.onSplit ) { + this.onSplit( record, content ); + } + + return; + } + + // There is a selection, check if a URL is pasted. + if ( ! isCollapsed( record ) ) { + const pastedText = ( html || plainText ).replace( /<[^>]+>/g, '' ).trim(); + + // A URL was pasted, turn the selection into a link + if ( isURL( pastedText ) ) { + this.onChange( applyFormat( record, { + type: 'a', + attributes: { + href: decodeEntities( pastedText ), + }, + } ) ); + + // Allows us to ask for this information when we get a report. + window.console.log( 'Created link:\n\n', pastedText ); + + return; + } + } + + const canReplace = onReplace && this.isEmpty(); + const canSplit = onReplace && onSplit; + + let mode = 'INLINE'; + + if ( canReplace ) { + mode = 'BLOCKS'; + } else if ( canSplit ) { + mode = 'AUTO'; + } + + const content = pasteHandler( { + HTML: html, + plainText, + mode, + tagName, + canUserUseUnfilteredHTML, + } ); + + if ( typeof content === 'string' ) { + let valueToInsert = create( { html: content } ); + + // If the content should be multiline, we should process text + // separated by a line break as separate lines. + if ( this.multilineTag ) { + valueToInsert = replace( valueToInsert, /\n+/g, LINE_SEPARATOR ); + } + + this.onChange( insert( record, valueToInsert ) ); + } else if ( content.length > 0 ) { + if ( canReplace ) { + onReplace( content ); + } else { + this.onSplit( record, content ); + } + } + } + + /** + * Handles a focus event on the contenteditable field, calling the + * `unstableOnFocus` prop callback if one is defined. The callback does not + * receive any arguments. + * + * This is marked as a private API and the `unstableOnFocus` prop is not + * documented, as the current requirements where it is used are subject to + * future refactoring following `isSelected` handling. + * + * In contrast with `setFocusedElement`, this is only triggered in response + * to focus within the contenteditable field, whereas `setFocusedElement` + * is triggered on focus within any `RichText` descendent element. + * + * @see setFocusedElement + * + * @private + */ + onFocus() { + const { unstableOnFocus } = this.props; + + if ( unstableOnFocus ) { + unstableOnFocus(); + } + + this.recalculateBoundaryStyle(); + + // We know for certain that on focus, the old selection is invalid. It + // will be recalculated on `selectionchange`. + const index = undefined; + const activeFormats = undefined; + + this.record = { + ...this.record, + start: index, + end: index, + activeFormats, + }; + this.props.onSelectionChange( index, index ); + this.setState( { activeFormats } ); + + document.addEventListener( 'selectionchange', this.onSelectionChange ); + } + + onBlur() { + document.removeEventListener( 'selectionchange', this.onSelectionChange ); + } + + /** + * Handle input on the next selection change event. + * + * @param {SyntheticEvent} event Synthetic input event. + */ + onInput( event ) { + // For Input Method Editor (IME), used in Chinese, Japanese, and Korean + // (CJK), do not trigger a change if characters are being composed. + // Browsers setting `isComposing` to `true` will usually emit a final + // `input` event when the characters are composed. + if ( event && event.nativeEvent.isComposing ) { + // Also don't update any selection. + document.removeEventListener( 'selectionchange', this.onSelectionChange ); + return; + } + + if ( event && event.nativeEvent.inputType ) { + const { inputType } = event.nativeEvent; + + // The browser formatted something or tried to insert HTML. + // Overwrite it. It will be handled later by the format library if + // needed. + if ( + inputType.indexOf( 'format' ) === 0 || + INSERTION_INPUT_TYPES_TO_IGNORE.has( inputType ) + ) { + this.applyRecord( this.getRecord() ); + return; + } + } + + const value = this.createRecord(); + const { start, activeFormats = [] } = this.record; + + // Update the formats between the last and new caret position. + const change = updateFormats( { + value, + start, + end: value.start, + formats: activeFormats, + } ); + + this.onChange( change, { withoutHistory: true } ); + + const { + __unstablePatterns: patterns, + __unstableOnReplace: onReplace, + } = this.props; + + if ( patterns ) { + const transformed = patterns.reduce( + ( accumlator, transform ) => transform( + accumlator, + onReplace, + this.valueToFormat + ), + change + ); + + if ( transformed !== change ) { + this.onCreateUndoLevel(); + this.onChange( { ...transformed, activeFormats } ); + } + } + + // Create an undo level when input stops for over a second. + this.props.clearTimeout( this.onInput.timeout ); + this.onInput.timeout = this.props.setTimeout( this.onCreateUndoLevel, 1000 ); + } + + onCompositionEnd() { + // Ensure the value is up-to-date for browsers that don't emit a final + // input event after composition. + this.onInput(); + // Tracking selection changes can be resumed. + document.addEventListener( 'selectionchange', this.onSelectionChange ); + } + + /** + * Handles the `selectionchange` event: sync the selection to local state. + */ + onSelectionChange() { + const { start, end } = this.createRecord(); + const value = this.getRecord(); + + if ( start !== value.start || end !== value.end ) { + const { + __unstableIsCaretWithinFormattedText: isCaretWithinFormattedText, + __unstableOnEnterFormattedText: onEnterFormattedText, + __unstableOnExitFormattedText: onExitFormattedText, + } = this.props; + const newValue = { + ...value, + start, + end, + // Allow `getActiveFormats` to get new `activeFormats`. + activeFormats: undefined, + }; + + const activeFormats = getActiveFormats( newValue ); + + // Update the value with the new active formats. + newValue.activeFormats = activeFormats; + + if ( ! isCaretWithinFormattedText && activeFormats.length ) { + onEnterFormattedText(); + } else if ( isCaretWithinFormattedText && ! activeFormats.length ) { + onExitFormattedText(); + } + + // It is important that the internal value is updated first, + // otherwise the value will be wrong on render! + this.record = newValue; + this.applyRecord( newValue, { domOnly: true } ); + this.props.onSelectionChange( start, end ); + this.setState( { activeFormats } ); + + if ( activeFormats.length > 0 ) { + this.recalculateBoundaryStyle(); + } + } + } + + recalculateBoundaryStyle() { + const boundarySelector = '*[data-rich-text-format-boundary]'; + const element = this.editableRef.querySelector( boundarySelector ); + + if ( ! element ) { + return; + } + + const computedStyle = getComputedStyle( element ); + const newColor = computedStyle.color + .replace( ')', ', 0.2)' ) + .replace( 'rgb', 'rgba' ); + const selector = `.rich-text:focus ${ boundarySelector }`; + const rule = `background-color: ${ newColor }`; + + globalStyle.innerHTML = `${ selector } {${ rule }}`; + } + + /** + * Sync the value to global state. The node tree and selection will also be + * updated if differences are found. + * + * @param {Object} record The record to sync and apply. + * @param {Object} $2 Named options. + * @param {boolean} $2.withoutHistory If true, no undo level will be + * created. + */ + onChange( record, { withoutHistory } = {} ) { + this.applyRecord( record ); + + const { start, end, activeFormats = [] } = record; + const changeHandlers = pickBy( this.props, ( v, key ) => + key.startsWith( 'format_on_change_functions_' ) + ); + + Object.values( changeHandlers ).forEach( ( changeHandler ) => { + changeHandler( record.formats, record.text ); + } ); + + this.value = this.valueToFormat( record ); + this.record = record; + this.props.onChange( this.value ); + this.props.onSelectionChange( start, end ); + this.setState( { activeFormats } ); + + if ( ! withoutHistory ) { + this.onCreateUndoLevel(); + } + } + + onCreateUndoLevel() { + // If the content is the same, no level needs to be created. + if ( this.lastHistoryValue === this.value ) { + return; + } + + this.props.__unstableOnCreateUndoLevel(); + this.lastHistoryValue = this.value; + } + + /** + * Handles a delete keyDown event to handle merge or removal for collapsed + * selection where caret is at directional edge: forward for a delete key, + * reverse for a backspace key. + * + * @see https://en.wikipedia.org/wiki/Caret_navigation + * + * @param {KeyboardEvent} event Keydown event. + */ + onDeleteKeyDown( event ) { + const { __unstableOnMerge: onMerge, __unstableOnRemove: onRemove } = this.props; + if ( ! onMerge && ! onRemove ) { + return; + } + + const { keyCode } = event; + const isReverse = keyCode === BACKSPACE; + + // Only process delete if the key press occurs at uncollapsed edge. + if ( ! isCollapsed( this.createRecord() ) ) { + return; + } + + const empty = this.isEmpty(); + + // It is important to consider emptiness because an empty container + // will include a padding BR node _after_ the caret, so in a forward + // deletion the isHorizontalEdge function will incorrectly interpret the + // presence of the BR node as not being at the edge. + const isEdge = ( empty || isHorizontalEdge( this.editableRef, isReverse ) ); + + if ( ! isEdge ) { + return; + } + + if ( onMerge ) { + onMerge( ! isReverse ); + } + + // Only handle remove on Backspace. This serves dual-purpose of being + // an intentional user interaction distinguishing between Backspace and + // Delete to remove the empty field, but also to avoid merge & remove + // causing destruction of two fields (merge, then removed merged). + if ( onRemove && empty && isReverse ) { + onRemove( ! isReverse ); + } + + event.preventDefault(); + } + + /** + * Handles a keydown event. + * + * @param {SyntheticEvent} event A synthetic keyboard event. + */ + onKeyDown( event ) { + const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; + const { + __unstableOnReplace: onReplace, + __unstableOnSplit: onSplit, + __unstableEnterPatterns: enterPatterns, + } = this.props; + + const canSplit = onReplace && onSplit; + + if ( + // Only override left and right keys without modifiers pressed. + ! shiftKey && ! altKey && ! metaKey && ! ctrlKey && + ( keyCode === LEFT || keyCode === RIGHT ) + ) { + this.handleHorizontalNavigation( event ); + } + + // Use the space key in list items (at the start of an item) to indent + // the list item. + if ( keyCode === SPACE && this.multilineTag === 'li' ) { + const value = this.createRecord(); + + if ( isCollapsed( value ) ) { + const { text, start } = value; + const characterBefore = text[ start - 1 ]; + + // The caret must be at the start of a line. + if ( ! characterBefore || characterBefore === LINE_SEPARATOR ) { + this.onChange( indentListItems( value, { type: this.props.tagName } ) ); + event.preventDefault(); + } + } + } + + if ( keyCode === DELETE || keyCode === BACKSPACE ) { + const value = this.createRecord(); + const { start, end } = value; + + // Always handle full content deletion ourselves. + if ( start === 0 && end !== 0 && end === value.text.length ) { + this.onChange( remove( value ) ); + event.preventDefault(); + return; + } + + if ( this.multilineTag ) { + const newValue = removeLineSeparator( value, keyCode === BACKSPACE ); + if ( newValue ) { + this.onChange( newValue ); + event.preventDefault(); + } + } + + this.onDeleteKeyDown( event ); + } else if ( keyCode === ENTER ) { + event.preventDefault(); + + const record = this.createRecord(); + + if ( enterPatterns ) { + if ( enterPatterns( + record, + onReplace, + this.valueToFormat, + ) !== record ) { + return; + } + } + + if ( this.multilineTag ) { + if ( event.shiftKey ) { + this.onChange( insert( record, '\n' ) ); + } else if ( canSplit && isEmptyLine( record ) ) { + this.onSplit( record ); + } else { + this.onChange( insertLineSeparator( record ) ); + } + } else if ( event.shiftKey || ! canSplit ) { + this.onChange( insert( record, '\n' ) ); + } else { + this.onSplit( record ); + } + } + } + + /** + * Handles horizontal keyboard navigation when no modifiers are pressed. The + * navigation is handled separately to move correctly around format + * boundaries. + * + * @param {SyntheticEvent} event A synthetic keyboard event. + */ + handleHorizontalNavigation( event ) { + const value = this.getRecord(); + const { text, formats, start, end, activeFormats = [] } = value; + const collapsed = isCollapsed( value ); + // To do: ideally, we should look at visual position instead. + const { direction } = getComputedStyle( this.editableRef ); + const reverseKey = direction === 'rtl' ? RIGHT : LEFT; + const isReverse = event.keyCode === reverseKey; + + // If the selection is collapsed and at the very start, do nothing if + // navigating backward. + // If the selection is collapsed and at the very end, do nothing if + // navigating forward. + if ( collapsed && activeFormats.length === 0 ) { + if ( start === 0 && isReverse ) { + return; + } + + if ( end === text.length && ! isReverse ) { + return; + } + } + + // If the selection is not collapsed, let the browser handle collapsing + // the selection for now. Later we could expand this logic to set + // boundary positions if needed. + if ( ! collapsed ) { + return; + } + + // In all other cases, prevent default behaviour. + event.preventDefault(); + + const formatsBefore = formats[ start - 1 ] || []; + const formatsAfter = formats[ start ] || []; + + let newActiveFormatsLength = activeFormats.length; + let source = formatsAfter; + + if ( formatsBefore.length > formatsAfter.length ) { + source = formatsBefore; + } + + // If the amount of formats before the caret and after the caret is + // different, the caret is at a format boundary. + if ( formatsBefore.length < formatsAfter.length ) { + if ( ! isReverse && activeFormats.length < formatsAfter.length ) { + newActiveFormatsLength++; + } + + if ( isReverse && activeFormats.length > formatsBefore.length ) { + newActiveFormatsLength--; + } + } else if ( formatsBefore.length > formatsAfter.length ) { + if ( ! isReverse && activeFormats.length > formatsAfter.length ) { + newActiveFormatsLength--; + } + + if ( isReverse && activeFormats.length < formatsBefore.length ) { + newActiveFormatsLength++; + } + } + + // Wait for boundary class to be added. + this.props.setTimeout( () => this.recalculateBoundaryStyle() ); + + if ( newActiveFormatsLength !== activeFormats.length ) { + const newActiveFormats = source.slice( 0, newActiveFormatsLength ); + const newValue = { ...value, activeFormats: newActiveFormats }; + this.record = newValue; + this.applyRecord( newValue ); + this.setState( { activeFormats: newActiveFormats } ); + return; + } + + const newPos = value.start + ( isReverse ? -1 : 1 ); + const newActiveFormats = isReverse ? formatsBefore : formatsAfter; + const newValue = { + ...value, + start: newPos, + end: newPos, + activeFormats: newActiveFormats, + }; + + this.record = newValue; + this.applyRecord( newValue ); + this.props.onSelectionChange( newPos, newPos ); + this.setState( { activeFormats: newActiveFormats } ); + } + + /** + * Signals to the RichText owner that the block can be replaced with two + * blocks as a result of splitting the block by pressing enter, or with + * blocks as a result of splitting the block by pasting block content in the + * instance. + * + * @param {Object} record The rich text value to split. + * @param {Array} pastedBlocks The pasted blocks to insert, if any. + */ + onSplit( record, pastedBlocks = [] ) { + const { + __unstableOnReplace: onReplace, + __unstableOnSplit: onSplit, + __unstableOnSplitMiddle: onSplitMiddle, + } = this.props; + + if ( ! onReplace || ! onSplit ) { + return; + } + + const blocks = []; + const [ before, after ] = split( record ); + const hasPastedBlocks = pastedBlocks.length > 0; + + // Create a block with the content before the caret if there's no pasted + // blocks, or if there are pasted blocks and the value is not empty. + // We do not want a leading empty block on paste, but we do if split + // with e.g. the enter key. + if ( ! hasPastedBlocks || ! isEmpty( before ) ) { + blocks.push( onSplit( this.valueToFormat( before ) ) ); + } + + if ( hasPastedBlocks ) { + blocks.push( ...pastedBlocks ); + } else if ( onSplitMiddle ) { + blocks.push( onSplitMiddle() ); + } + + // If there's pasted blocks, append a block with the content after the + // caret. Otherwise, do append and empty block if there is no + // `onSplitMiddle` prop, but if there is and the content is empty, the + // middle block is enough to set focus in. + if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { + blocks.push( onSplit( this.valueToFormat( after ) ) ); + } + + // If there are pasted blocks, set the selection to the last one. + // Otherwise, set the selection to the second block. + const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; + + onReplace( blocks, indexToSelect ); + } + + /** + * Select object when they are clicked. The browser will not set any + * selection when clicking e.g. an image. + * + * @param {SyntheticEvent} event Synthetic mousedown or touchstart event. + */ + onPointerDown( event ) { + const { target } = event; + + // If the child element has no text content, it must be an object. + if ( target === this.editableRef || target.textContent ) { + return; + } + + const { parentNode } = target; + const index = Array.from( parentNode.childNodes ).indexOf( target ); + const range = target.ownerDocument.createRange(); + const selection = getSelection(); + + range.setStart( target.parentNode, index ); + range.setEnd( target.parentNode, index + 1 ); + + selection.removeAllRanges(); + selection.addRange( range ); + } + + componentDidUpdate( prevProps ) { + const { + tagName, + value, + selectionStart, + selectionEnd, + __unstableIsSelected: isSelected, + } = this.props; + + // Check if the content changed. + let shouldReapply = ( + tagName === prevProps.tagName && + value !== prevProps.value && + value !== this.value + ); + + // Check if the selection changed. + shouldReapply = shouldReapply || ( + isSelected && ! prevProps.isSelected && ( + this.record.start !== selectionStart || + this.record.end !== selectionEnd + ) + ); + + const prefix = 'format_prepare_props_'; + const predicate = ( v, key ) => key.startsWith( prefix ); + const prepareProps = pickBy( this.props, predicate ); + const prevPrepareProps = pickBy( prevProps, predicate ); + + // Check if any format props changed. + shouldReapply = shouldReapply || + ! isShallowEqual( prepareProps, prevPrepareProps ); + + const { activeFormats = [] } = this.record; + + if ( shouldReapply ) { + this.value = value; + this.record = this.formatToValue( value ); + this.record.start = selectionStart; + this.record.end = selectionEnd; + + updateFormats( { + value: this.record, + start: this.record.start, + end: this.record.end, + formats: activeFormats, + } ); + + this.applyRecord( this.record ); + } else if ( + this.record.start !== selectionStart || + this.record.end !== selectionEnd + ) { + this.record = { + ...this.record, + start: selectionStart, + end: selectionEnd, + }; + } + } + + /** + * Converts the outside data structure to our internal representation. + * + * @param {*} value The outside value, data type depends on props. + * @return {Object} An internal rich-text value. + */ + formatToValue( value ) { + if ( this.props.format === 'string' ) { + const prepare = createPrepareEditableTree( this.props, 'format_value_functions' ); + + value = create( { + html: value, + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + } ); + value.formats = prepare( value ); + + return value; + } + + return value; + } + + valueToEditableHTML( value ) { + return toDom( { + value, + multilineTag: this.multilineTag, + prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), + } ).body.innerHTML; + } + + /** + * Removes editor only formats from the value. + * + * Editor only formats are applied using `prepareEditableTree`, so we need to + * remove them before converting the internal state + * + * @param {Object} value The internal rich-text value. + * @return {Object} A new rich-text value. + */ + removeEditorOnlyFormats( value ) { + this.props.formatTypes.forEach( ( formatType ) => { + // Remove formats created by prepareEditableTree, because they are editor only. + if ( formatType.__experimentalCreatePrepareEditableTree ) { + value = removeFormat( value, formatType.name, 0, value.text.length ); + } + } ); + + return value; + } + + /** + * Converts the internal value to the external data format. + * + * @param {Object} value The internal rich-text value. + * @return {*} The external data format, data type depends on props. + */ + valueToFormat( value ) { + value = this.removeEditorOnlyFormats( value ); + + if ( this.props.format === 'string' ) { + return toHTMLString( { + value, + multilineTag: this.multilineTag, + } ); + } + + return value; + } + + render() { + const { + tagName: Tagname = 'div', + style, + wrapperClassName, + className, + placeholder, + keepPlaceholderOnFocus = false, + __unstableIsSelected: isSelected, + children, + // To do: move autocompletion logic to rich-text. + __unstableAutocompleters: autocompleters, + __unstableAutocomplete: Autocomplete = ( { children: ch } ) => ch( {} ), + __unstableOnReplace: onReplace, + } = this.props; + + // Generating a key that includes `tagName` ensures that if the tag + // changes, we replace the relevant element. This is needed because we + // prevent Editable component updates. + const key = Tagname; + const MultilineTag = this.multilineTag; + const ariaProps = pickAriaProps( this.props ); + const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && this.isEmpty(); + const record = this.getRecord(); + + const autoCompleteContent = ( { listBoxId, activeId } ) => ( + <> + <Editable + tagName={ Tagname } + style={ style } + record={ record } + valueToEditableHTML={ this.valueToEditableHTML } + isPlaceholderVisible={ isPlaceholderVisible } + aria-label={ placeholder } + aria-autocomplete={ listBoxId ? 'list' : undefined } + aria-owns={ listBoxId } + aria-activedescendant={ activeId } + { ...ariaProps } + className={ classnames( 'rich-text', className ) } + key={ key } + onPaste={ this.onPaste } + onInput={ this.onInput } + onCompositionEnd={ this.onCompositionEnd } + onKeyDown={ this.onKeyDown } + onFocus={ this.onFocus } + onBlur={ this.onBlur } + onMouseDown={ this.onPointerDown } + onTouchStart={ this.onPointerDown } + setRef={ this.setRef } + /> + { isPlaceholderVisible && + <Tagname + className={ classnames( 'rich-text', className ) } + style={ style } + > + { MultilineTag ? <MultilineTag>{ placeholder }</MultilineTag> : placeholder } + </Tagname> + } + { isSelected && <FormatEdit value={ record } onChange={ this.onChange } /> } + </> + ); + + const content = ( + <Autocomplete + onReplace={ onReplace } + completers={ autocompleters } + record={ record } + onChange={ this.onChange } + > + { autoCompleteContent } + </Autocomplete> + ); + + if ( ! children ) { + return content; + } + + return ( + <div className={ wrapperClassName }> + { children( { + isSelected, + value: record, + onChange: this.onChange, + } ) } + { content } + </div> + ); + } +} + +RichText.defaultProps = { + format: 'string', + value: '', +}; + +/** + * Renders a rich content input, providing users with the option to format the + * content. + */ +export default compose( [ + withSelect( ( select ) => ( { + formatTypes: select( 'core/rich-text' ).getFormatTypes(), + } ) ), + withSafeTimeout, +] )( RichText ); diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js new file mode 100644 index 00000000000000..441095f59d39fd --- /dev/null +++ b/packages/rich-text/src/component/index.native.js @@ -0,0 +1,883 @@ +/*eslint no-console: ["error", { allow: ["warn"] }] */ + +/** + * External dependencies + */ +import RCTAztecView from 'react-native-aztec'; +import { View, Platform } from 'react-native'; +import { + pickBy, +} from 'lodash'; +import memize from 'memize'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; +import { childrenBlock } from '@wordpress/blocks'; +import { decodeEntities } from '@wordpress/html-entities'; +import { BACKSPACE } from '@wordpress/keycodes'; +import { isURL } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import FormatEdit from './format-edit'; +import { applyFormat } from '../apply-format'; +import { getActiveFormat } from '../get-active-format'; +import { getActiveFormats } from '../get-active-formats'; +import { isEmpty, isEmptyLine } from '../is-empty'; +import { create } from '../create'; +import { split } from '../split'; +import { toHTMLString } from '../to-html-string'; +import { insert } from '../insert'; +import { insertLineSeparator } from '../insert-line-separator'; +import { removeLineSeparator } from '../remove-line-separator'; +import { isCollapsed } from '../is-collapsed'; +import { remove } from '../remove'; +import styles from './style.scss'; + +const unescapeSpaces = ( text ) => { + return text.replace( /&nbsp;|&#160;/gi, ' ' ); +}; + +/** + * Calls {@link pasteHandler} with a fallback to plain text when HTML processing + * results in errors + * + * @param {function} originalPasteHandler The original handler function + * @param {Object} [options] The options to pass to {@link pasteHandler} + * + * @return {Array|string} A list of blocks or a string, depending on + * `handlerMode`. + */ +const saferPasteHandler = ( originalPasteHandler, options ) => { + try { + return originalPasteHandler( options ); + } catch ( error ) { + window.console.log( 'Pasting HTML failed:', error ); + window.console.log( 'HTML:', options.HTML ); + window.console.log( 'Falling back to plain text.' ); + // fallback to plain text + return originalPasteHandler( { ...options, HTML: '' } ); + } +}; + +const gutenbergFormatNamesToAztec = { + 'core/bold': 'bold', + 'core/italic': 'italic', + 'core/strikethrough': 'strikethrough', +}; + +export class RichText extends Component { + constructor( { value, __unstableMultiline: multiline, selectionStart, selectionEnd } ) { + super( ...arguments ); + + this.isMultiline = false; + if ( multiline === true || multiline === 'p' || multiline === 'li' ) { + this.multilineTag = multiline === true ? 'p' : multiline; + this.isMultiline = true; + } + + if ( this.multilineTag === 'li' ) { + this.multilineWrapperTags = [ 'ul', 'ol' ]; + } + this.onSplit = this.onSplit.bind( this ); + this.isIOS = Platform.OS === 'ios'; + this.createRecord = this.createRecord.bind( this ); + this.onChange = this.onChange.bind( this ); + this.onEnter = this.onEnter.bind( this ); + this.onBackspace = this.onBackspace.bind( this ); + this.onPaste = this.onPaste.bind( this ); + this.onFocus = this.onFocus.bind( this ); + this.onBlur = this.onBlur.bind( this ); + this.onTextUpdate = this.onTextUpdate.bind( this ); + this.onContentSizeChange = this.onContentSizeChange.bind( this ); + this.onFormatChange = this.onFormatChange.bind( this ); + this.formatToValue = memize( + this.formatToValue.bind( this ), + { maxSize: 1 } + ); + + // This prevents a bug in Aztec which triggers onSelectionChange twice on format change + this.onSelectionChange = this.onSelectionChange.bind( this ); + this.onSelectionChangeFromAztec = this.onSelectionChangeFromAztec.bind( this ); + this.valueToFormat = this.valueToFormat.bind( this ); + this.willTrimSpaces = this.willTrimSpaces.bind( this ); + this.getHtmlToRender = this.getHtmlToRender.bind( this ); + this.state = { + activeFormats: [], + selectedFormat: null, + height: 0, + }; + this.needsSelectionUpdate = false; + this.savedContent = ''; + this.isTouched = false; + this.lastAztecEventType = null; + + this.lastHistoryValue = value; + + // Internal values that are update synchronously, unlike props. + this.value = value; + this.selectionStart = selectionStart; + this.selectionEnd = selectionEnd; + } + + /** + * Get the current record (value and selection) from props and state. + * + * @return {Object} The current record (value and selection). + */ + getRecord() { + const { selectionStart: start, selectionEnd: end } = this.props; + let { value } = this.props; + + // Since we get the text selection from Aztec we need to be in sync with the HTML `value` + // Removing leading white spaces using `trim()` should make sure this is the case. + if ( typeof value === 'string' || value instanceof String ) { + value = value.trimLeft(); + } + + const { formats, replacements, text } = this.formatToValue( value ); + const { activeFormats } = this.state; + + return { formats, replacements, text, start, end, activeFormats }; + } + + /** + * Creates a RichText value "record" from the current content and selection + * information + * + * + * @return {Object} A RichText value with formats and selection. + */ + createRecord() { + const value = { + start: this.selectionStart, + end: this.selectionEnd, + ...create( { + html: this.value, + range: null, + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + } ), + }; + const start = Math.min( this.selectionStart, value.text.length ); + const end = Math.min( this.selectionEnd, value.text.length ); + return { ...value, start, end }; + } + + /** + * Signals to the RichText owner that the block can be replaced with two + * blocks as a result of splitting the block by pressing enter, or with + * blocks as a result of splitting the block by pasting block content in the + * instance. + * + * @param {Object} record The rich text value to split. + * @param {Array} pastedBlocks The pasted blocks to insert, if any. + */ + onSplit( record, pastedBlocks = [] ) { + const { + __unstableOnReplace: onReplace, + __unstableOnSplit: onSplit, + __unstableOnSplitMiddle: onSplitMiddle, + } = this.props; + + if ( ! onReplace || ! onSplit ) { + return; + } + + const blocks = []; + const [ before, after ] = split( record ); + const hasPastedBlocks = pastedBlocks.length > 0; + + // Create a block with the content before the caret if there's no pasted + // blocks, or if there are pasted blocks and the value is not empty. + // We do not want a leading empty block on paste, but we do if split + // with e.g. the enter key. + if ( ! hasPastedBlocks || ! isEmpty( before ) ) { + blocks.push( onSplit( this.valueToFormat( before ) ) ); + } + + if ( hasPastedBlocks ) { + blocks.push( ...pastedBlocks ); + } else if ( onSplitMiddle ) { + blocks.push( onSplitMiddle() ); + } + + // If there's pasted blocks, append a block with the content after the + // caret. Otherwise, do append and empty block if there is no + // `onSplitMiddle` prop, but if there is and the content is empty, the + // middle block is enough to set focus in. + if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { + blocks.push( onSplit( this.valueToFormat( after ) ) ); + } + + // If there are pasted blocks, set the selection to the last one. + // Otherwise, set the selection to the second block. + const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; + // The onSplit event can cause a content update event for this block. Such event should + // definitely be processed by our native components, since they have no knowledge of + // how the split works. Setting lastEventCount to undefined forces the native component to + // always update when provided with new content. + this.lastEventCount = undefined; + onReplace( blocks, indexToSelect ); + } + + valueToFormat( value ) { + // remove the outer root tags + return this.removeRootTagsProduceByAztec( toHTMLString( { + value, + multilineTag: this.multilineTag, + } ) ); + } + + getActiveFormatNames( record ) { + const { + formatTypes, + } = this.props; + + return formatTypes.map( ( { name } ) => name ).filter( ( name ) => { + return getActiveFormat( record, name ) !== undefined; + } ).map( ( name ) => gutenbergFormatNamesToAztec[ name ] ).filter( Boolean ); + } + + onFormatChange( record ) { + const { start, end, activeFormats = [] } = record; + const changeHandlers = pickBy( this.props, ( v, key ) => + key.startsWith( 'format_on_change_functions_' ) + ); + + Object.values( changeHandlers ).forEach( ( changeHandler ) => { + changeHandler( record.formats, record.text ); + } ); + + this.value = this.valueToFormat( record ); + this.props.onChange( this.value ); + this.setState( { activeFormats } ); + this.props.onSelectionChange( start, end ); + this.selectionStart = start; + this.selectionEnd = end; + + this.onCreateUndoLevel(); + + this.lastAztecEventType = 'format change'; + } + + onCreateUndoLevel() { + const { __unstableOnCreateUndoLevel: onCreateUndoLevel } = this.props; + // If the content is the same, no level needs to be created. + if ( this.lastHistoryValue === this.value ) { + return; + } + + onCreateUndoLevel(); + this.lastHistoryValue = this.value; + } + + /* + * Cleans up any root tags produced by aztec. + * TODO: This should be removed on a later version when aztec doesn't return the top tag of the text being edited + */ + removeRootTagsProduceByAztec( html ) { + let result = this.removeRootTag( this.props.tagName, html ); + // Temporary workaround for https://github.com/WordPress/gutenberg/pull/13763 + if ( this.props.rootTagsToEliminate ) { + this.props.rootTagsToEliminate.forEach( ( element ) => { + result = this.removeRootTag( element, result ); + } ); + } + + if ( this.props.tagsToEliminate ) { + this.props.tagsToEliminate.forEach( ( element ) => { + result = this.removeTag( element, result ); + } ); + } + return result; + } + + removeRootTag( tag, html ) { + const openingTagRegexp = RegExp( '^<' + tag + '>', 'gim' ); + const closingTagRegexp = RegExp( '</' + tag + '>$', 'gim' ); + return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); + } + removeTag( tag, html ) { + const openingTagRegexp = RegExp( '<' + tag + '>', 'gim' ); + const closingTagRegexp = RegExp( '</' + tag + '>', 'gim' ); + return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); + } + + /* + * Handles any case where the content of the AztecRN instance has changed + */ + onChange( event ) { + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + // On iOS, onChange can be triggered after selection changes, even though there are no content changes. + if ( contentWithoutRootTag === this.value ) { + return; + } + this.lastEventCount = event.nativeEvent.eventCount; + this.comesFromAztec = true; + this.firedAfterTextChanged = true; // the onChange event always fires after the fact + this.onTextUpdate( event ); + this.lastAztecEventType = 'input'; + } + + onTextUpdate( event ) { + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + + const refresh = this.value !== contentWithoutRootTag; + this.value = contentWithoutRootTag; + + // we don't want to refresh if our goal is just to create a record + if ( refresh ) { + this.props.onChange( contentWithoutRootTag ); + } + } + + /* + * Handles any case where the content of the AztecRN instance has changed in size + */ + onContentSizeChange( contentSize ) { + const contentHeight = contentSize.height; + this.setState( { height: contentHeight } ); + this.lastAztecEventType = 'content size change'; + } + + // eslint-disable-next-line no-unused-vars + onEnter( event ) { + if ( this.props.onEnter ) { + this.props.onEnter(); + return; + } + const { + __unstableOnReplace: onReplace, + __unstableOnSplit: onSplit, + } = this.props; + + this.lastEventCount = event.nativeEvent.eventCount; + this.comesFromAztec = true; + this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; + + const canSplit = onReplace && onSplit; + const currentRecord = this.createRecord(); + if ( this.multilineTag ) { + if ( event.shiftKey ) { + this.needsSelectionUpdate = true; + const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; + this.onFormatChange( insertedLineBreak ); + } else if ( canSplit && isEmptyLine( currentRecord ) ) { + this.onSplit( currentRecord ); + } else { + this.needsSelectionUpdate = true; + const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; + this.onFormatChange( insertedLineSeparator ); + } + } else if ( event.shiftKey || ! onSplit ) { + this.needsSelectionUpdate = true; + const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; + this.onFormatChange( insertedLineBreak ); + } else { + this.onSplit( currentRecord ); + } + this.lastAztecEventType = 'input'; + } + + // eslint-disable-next-line no-unused-vars + onBackspace( event ) { + const { + __unstableOnMerge: onMerge, + __unstableOnRemove: onRemove, + onChange, + } = this.props; + if ( ! onMerge && ! onRemove ) { + return; + } + + const keyCode = BACKSPACE; // TODO : should we differentiate BACKSPACE and DELETE? + const isReverse = keyCode === BACKSPACE; + + this.lastEventCount = event.nativeEvent.eventCount; + this.comesFromAztec = true; + this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; + const value = this.createRecord(); + const { start, end } = value; + let newValue; + + // Always handle full content deletion ourselves. + if ( start === 0 && end !== 0 && end >= value.text.length ) { + newValue = remove( value, start, end ); + onChange( newValue ); + return; + } + + if ( this.multilineTag ) { + newValue = removeLineSeparator( value, keyCode === BACKSPACE ); + if ( newValue ) { + this.onFormatChange( newValue ); + return; + } + } + + const empty = this.isEmpty(); + + if ( onMerge ) { + onMerge( ! isReverse ); + } + + // Only handle remove on Backspace. This serves dual-purpose of being + // an intentional user interaction distinguishing between Backspace and + // Delete to remove the empty field, but also to avoid merge & remove + // causing destruction of two fields (merge, then removed merged). + if ( onRemove && empty && isReverse ) { + onRemove( ! isReverse ); + } + + event.preventDefault(); + this.lastAztecEventType = 'input'; + } + + /** + * Handles a paste event from the native Aztec Wrapper. + * + * @param {PasteEvent} event The paste event which wraps `nativeEvent`. + */ + onPaste( event ) { + const { + tagName, + __unstablePasteHandler: pasteHandler, + __unstableOnReplace: onReplace, + __unstableOnSplit: onSplit, + onChange, + } = this.props; + + const { pastedText, pastedHtml, files } = event.nativeEvent; + const currentRecord = this.createRecord(); + + event.preventDefault(); + + // Only process file if no HTML is present. + // Note: a pasted file may have the URL as plain text. + if ( files && files.length > 0 ) { + const uploadId = Number.MAX_SAFE_INTEGER; + let html = ''; + files.forEach( ( file ) => { + html += `<img src="${ file }" class="wp-image-${ uploadId }">`; + } ); + const content = pasteHandler( { + HTML: html, + mode: 'BLOCKS', + tagName, + } ); + const shouldReplace = onReplace && this.isEmpty(); + + if ( shouldReplace ) { + onReplace( content ); + } else { + this.onSplit( currentRecord, content ); + } + + return; + } + + // There is a selection, check if a URL is pasted. + if ( ! isCollapsed( currentRecord ) ) { + const trimmedText = ( pastedHtml || pastedText ).replace( /<[^>]+>/g, '' ) + .trim(); + + // A URL was pasted, turn the selection into a link + if ( isURL( trimmedText ) ) { + const linkedRecord = applyFormat( currentRecord, { + type: 'a', + attributes: { + href: decodeEntities( trimmedText ), + }, + } ); + this.value = this.valueToFormat( linkedRecord ); + onChange( this.value ); + + // Allows us to ask for this information when we get a report. + window.console.log( 'Created link:\n\n', trimmedText ); + + return; + } + } + + const shouldReplace = this.props.onReplace && this.isEmpty(); + + let mode = 'INLINE'; + + if ( shouldReplace ) { + mode = 'BLOCKS'; + } else if ( onSplit ) { + mode = 'AUTO'; + } + + const pastedContent = saferPasteHandler( pasteHandler, { + HTML: pastedHtml, + plainText: pastedText, + mode, + tagName: this.props.tagName, + canUserUseUnfilteredHTML: this.props.canUserUseUnfilteredHTML, + } ); + + if ( typeof pastedContent === 'string' ) { + const recordToInsert = create( { html: pastedContent } ); + const resultingRecord = insert( currentRecord, recordToInsert ); + const resultingContent = this.valueToFormat( resultingRecord ); + + this.lastEventCount = undefined; + this.value = resultingContent; + + // explicitly set selection after inline paste + this.onSelectionChange( resultingRecord.start, resultingRecord.end ); + + onChange( this.value ); + } else if ( onSplit ) { + if ( ! pastedContent.length ) { + return; + } + + if ( shouldReplace ) { + onReplace( pastedContent ); + } else { + this.onSplit( currentRecord, pastedContent ); + } + } + } + + onFocus() { + this.isTouched = true; + + const { + unstableOnFocus, + onSelectionChange, + } = this.props; + + if ( unstableOnFocus ) { + unstableOnFocus(); + } + + // We know for certain that on focus, the old selection is invalid. It + // will be recalculated on `selectionchange`. + const index = undefined; + + onSelectionChange( index, index ); + + this.lastAztecEventType = 'focus'; + } + + onBlur( event ) { + this.isTouched = false; + + if ( this.props.onBlur ) { + this.props.onBlur( event ); + } + + this.lastAztecEventType = 'blur'; + } + + onSelectionChange( start, end ) { + const hasChanged = this.selectionStart !== start || this.selectionEnd !== end; + + this.selectionStart = start; + this.selectionEnd = end; + + // This is a manual selection change event if onChange was not triggered just before + // and we did not just trigger a text update + // `onChange` could be the last event and could have been triggered a long time ago so + // this approach is not perfectly reliable + const isManual = this.lastAztecEventType !== 'input' && this.props.value === this.value; + if ( hasChanged && isManual ) { + const value = this.createRecord(); + const activeFormats = getActiveFormats( value ); + this.setState( { activeFormats } ); + } + + this.props.onSelectionChange( start, end ); + } + + onSelectionChangeFromAztec( start, end, text, event ) { + // `end` can be less than `start` on iOS + // Let's fix that here so `rich-text/slice` can work properly + const realStart = Math.min( start, end ); + const realEnd = Math.max( start, end ); + + // check and dicsard stray event, where the text and selection is equal to the ones already cached + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + if ( contentWithoutRootTag === this.value && realStart === this.selectionStart && realEnd === this.selectionEnd ) { + return; + } + + this.comesFromAztec = true; + this.firedAfterTextChanged = true; // Selection change event always fires after the fact + + // update text before updating selection + // Make sure there are changes made to the content before upgrading it upward + this.onTextUpdate( event ); + + this.onSelectionChange( realStart, realEnd ); + + // Update lastEventCount to prevent Aztec from re-rendering the content it just sent + this.lastEventCount = event.nativeEvent.eventCount; + + this.lastAztecEventType = 'selection change'; + } + + isEmpty() { + return isEmpty( this.formatToValue( this.props.value ) ); + } + + formatToValue( value ) { + // Handle deprecated `children` and `node` sources. + if ( Array.isArray( value ) ) { + return create( { + html: childrenBlock.toHTML( value ), + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + } ); + } + + if ( this.props.format === 'string' ) { + return create( { + html: value, + multilineTag: this.multilineTag, + multilineWrapperTags: this.multilineWrapperTags, + } ); + } + + // Guard for blocks passing `null` in onSplit callbacks. May be removed + // if onSplit is revised to not pass a `null` value. + if ( value === null ) { + return create(); + } + + return value; + } + + shouldComponentUpdate( nextProps ) { + if ( nextProps.tagName !== this.props.tagName ) { + this.lastEventCount = undefined; + this.value = undefined; + return true; + } + + // TODO: Please re-introduce the check to avoid updating the content right after an `onChange` call. + // It was removed in https://github.com/WordPress/gutenberg/pull/12417 to fix undo/redo problem. + + // If the component is changed React side (undo/redo/merging/splitting/custom text actions) + // we need to make sure the native is updated as well. + + // Also, don't trust the "this.lastContent" as on Android, incomplete text events arrive + // with only some of the text, while the virtual keyboard's suggestion system does its magic. + // ** compare with this.lastContent for optimizing performance by not forcing Aztec with text it already has + // , but compare with props.value to not lose "half word" text because of Android virtual keyb autosuggestion behavior + if ( ( typeof nextProps.value !== 'undefined' ) && + ( typeof this.props.value !== 'undefined' ) && + ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) && + nextProps.value !== this.props.value ) { + // Gutenberg seems to try to mirror the caret state even on events that only change the content so, + // let's force caret update if state has selection set. + if ( typeof nextProps.selectionStart !== 'undefined' && typeof nextProps.selectionEnd !== 'undefined' ) { + this.needsSelectionUpdate = true; + } + + this.lastEventCount = undefined; // force a refresh on the native side + } + + if ( ! this.comesFromAztec ) { + if ( ( typeof nextProps.selectionStart !== 'undefined' ) && + ( typeof nextProps.selectionEnd !== 'undefined' ) && + nextProps.selectionStart !== this.props.selectionStart && + nextProps.selectionStart !== this.selectionStart && + nextProps.__unstableIsSelected ) { + this.needsSelectionUpdate = true; + this.lastEventCount = undefined; // force a refresh on the native side + } + } + + return true; + } + + componentDidMount() { + // Request focus if wrapping block is selected and parent hasn't inhibited the focus request. This method of focusing + // is trying to implement the web-side counterpart of BlockList's `focusTabbable` where the BlockList is focusing an + // inputbox by searching the DOM. We don't have the DOM in RN so, using the combination of blockIsSelected and __unstableMobileNoFocusOnMount + // to determine if we should focus the RichText. + if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { + this._editor.focus(); + this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); + } + } + + componentWillUnmount() { + if ( this._editor.isFocused() && this.props.shouldBlurOnUnmount ) { + this._editor.blur(); + } + } + + componentDidUpdate( prevProps ) { + if ( this.props.value !== this.value ) { + this.value = this.props.value; + this.lastEventCount = undefined; + } + const { + __unstableIsSelected: isSelected, + } = this.props; + + const { + __unstableIsSelected: prevIsSelected, + } = prevProps; + + if ( isSelected && ! prevIsSelected ) { + this._editor.focus(); + // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange + // if its internal value hasn't change. When created, default value is 0, 0 + this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); + } else if ( ! isSelected && prevIsSelected ) { + this._editor.blur(); + } + } + + willTrimSpaces( html ) { + // regex for detecting spaces around block element html tags + const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; + const leadingOrTrailingSpaces = new RegExp( `(\\s+)<\/?${ blockHtmlElements }>|<\/?${ blockHtmlElements }>(\\s+)`, 'g' ); + const matches = html.match( leadingOrTrailingSpaces ); + if ( matches && matches.length > 0 ) { + return true; + } + + return false; + } + + getHtmlToRender( record, tagName ) { + // Save back to HTML from React tree + const value = this.valueToFormat( record ); + + if ( value === undefined || value === '' ) { + this.lastEventCount = undefined; // force a refresh on the native side + return ''; + } else if ( tagName ) { + return `<${ tagName }>${ value }</${ tagName }>`; + } + return value; + } + + render() { + const { + tagName, + style, + __unstableIsSelected: isSelected, + children, + } = this.props; + + const record = this.getRecord(); + const html = this.getHtmlToRender( record, tagName ); + + let minHeight = styles[ 'rich-text' ].minHeight; + if ( style && style.minHeight ) { + minHeight = style.minHeight; + } + + const { + color: defaultPlaceholderTextColor, + } = styles[ 'rich-text-placeholder' ]; + + const { + color: defaultColor, + textDecorationColor: defaultTextDecorationColor, + fontFamily: defaultFontFamily, + } = styles[ 'rich-text' ]; + + let selection = null; + if ( this.needsSelectionUpdate ) { + this.needsSelectionUpdate = false; + selection = { start: this.props.selectionStart, end: this.props.selectionEnd }; + + // On AztecAndroid, setting the caret to an out-of-bounds position will crash the editor so, let's check for some cases. + if ( ! this.isIOS ) { + // Aztec performs some html text cleanup while parsing it so, its internal representation gets out-of-sync with the + // representation of the format-lib on the RN side. We need to avoid trying to set the caret position because it may + // be outside the text bounds and crash Aztec, at least on Android. + if ( this.willTrimSpaces( html ) ) { + // the html will get trimmed by the cleaning up functions in Aztec and caret position will get out-of-sync. + // So, skip forcing it, let Aztec just do its best and just log the fact. + console.warn( 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' ); + selection = null; + } else if ( this.props.selectionStart > record.text.length || this.props.selectionEnd > record.text.length ) { + console.warn( 'Oops, selection will land outside the text, skipping setting it...' ); + selection = null; + } + } + } + + if ( this.comesFromAztec ) { + this.comesFromAztec = false; + this.firedAfterTextChanged = false; + } + + return ( + <View> + { children && children( { + isSelected, + value: record, + onChange: this.onFormatChange, + } ) } + <RCTAztecView + ref={ ( ref ) => { + this._editor = ref; + + if ( this.props.setRef ) { + this.props.setRef( ref ); + } + } } + style={ { + ...style, + minHeight: Math.max( minHeight, this.state.height ), + } } + text={ { text: html, eventCount: this.lastEventCount, selection } } + placeholder={ this.props.placeholder } + placeholderTextColor={ this.props.placeholderTextColor || defaultPlaceholderTextColor } + deleteEnter={ this.props.deleteEnter } + onChange={ this.onChange } + onFocus={ this.onFocus } + onBlur={ this.onBlur } + onEnter={ this.onEnter } + onBackspace={ this.onBackspace } + onPaste={ this.onPaste } + activeFormats={ this.getActiveFormatNames( record ) } + onContentSizeChange={ this.onContentSizeChange } + onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } + onSelectionChange={ this.onSelectionChangeFromAztec } + blockType={ { tag: tagName } } + color={ defaultColor } + linkTextColor={ defaultTextDecorationColor } + maxImagesWidth={ 200 } + fontFamily={ this.props.fontFamily || defaultFontFamily } + fontSize={ this.props.fontSize || ( style && style.fontSize ) } + fontWeight={ this.props.fontWeight } + fontStyle={ this.props.fontStyle } + disableEditingMenu={ this.props.disableEditingMenu } + isMultiline={ this.isMultiline } + textAlign={ this.props.textAlign } + /> + { isSelected && <FormatEdit value={ record } onChange={ this.onFormatChange } /> } + </View> + ); + } +} + +RichText.defaultProps = { + format: 'string', + value: '', + tagName: 'div', +}; + +export default compose( [ + withSelect( ( select ) => ( { + formatTypes: select( 'core/rich-text' ).getFormatTypes(), + } ) ), +] )( RichText ); diff --git a/packages/block-editor/src/components/rich-text/style.native.scss b/packages/rich-text/src/component/style.native.scss similarity index 69% rename from packages/block-editor/src/components/rich-text/style.native.scss rename to packages/rich-text/src/component/style.native.scss index 77413c5be1f9e4..6481c415694127 100644 --- a/packages/block-editor/src/components/rich-text/style.native.scss +++ b/packages/rich-text/src/component/style.native.scss @@ -1,11 +1,11 @@ -.block-editor-rich-text { +.rich-text { font-family: $default-regular-font; min-height: $min-height-paragraph; color: $gray-900; text-decoration-color: $blue-500; } -.block-editor-rich-text-placeholder { +.rich-text-placeholder { color: $gray; } diff --git a/packages/block-editor/src/components/rich-text/test/index.js b/packages/rich-text/src/component/test/index.js similarity index 100% rename from packages/block-editor/src/components/rich-text/test/index.js rename to packages/rich-text/src/component/test/index.js diff --git a/packages/block-editor/src/components/rich-text/test/index.native.js b/packages/rich-text/src/component/test/index.native.js similarity index 100% rename from packages/block-editor/src/components/rich-text/test/index.native.js rename to packages/rich-text/src/component/test/index.native.js diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index 8f57a21e4ddf34..99e929ceb72225 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -24,7 +24,7 @@ export { removeLineSeparator as __unstableRemoveLineSeparator } from './remove-l export { insertObject } from './insert-object'; export { slice } from './slice'; export { split } from './split'; -export { apply as __unstableApply, toDom as __unstableToDom } from './to-dom'; +export { toDom as __unstableToDom } from './to-dom'; export { toHTMLString } from './to-html-string'; export { toggleFormat } from './toggle-format'; export { LINE_SEPARATOR as __UNSTABLE_LINE_SEPARATOR } from './special-characters'; @@ -32,5 +32,7 @@ export { unregisterFormatType } from './unregister-format-type'; export { indentListItems as __unstableIndentListItems } from './indent-list-items'; export { outdentListItems as __unstableOutdentListItems } from './outdent-list-items'; export { changeListType as __unstableChangeListType } from './change-list-type'; -export { updateFormats as __unstableUpdateFormats } from './update-formats'; -export { getActiveFormats as __unstableGetActiveFormats } from './get-active-formats'; +export { createElement as __unstableCreateElement } from './create-element'; + +export { default as RichText } from './component'; +export { default as __unstableFormatEdit } from './component/format-edit'; From 1414cf0ad1ec3d0f3e86a40815513c15938bb522 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Tue, 25 Jun 2019 19:50:54 -0400 Subject: [PATCH 392/664] Refactor: Add data component for editor initialization to replace __unstableInitialize action. (fixes #15403) (#15444) Create EditorInitializer component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. --- packages/edit-post/CHANGELOG.md | 7 +- .../components/editor-initialization/index.js | 37 +++ .../editor-initialization/listener-hooks.js | 106 ++++++++ .../test/listener-hooks.js | 255 ++++++++++++++++++ .../editor-initialization}/utils.js | 0 packages/edit-post/src/editor.js | 3 + packages/edit-post/src/index.js | 9 - packages/edit-post/src/store/actions.js | 79 ------ packages/edit-post/src/store/constants.js | 6 + packages/edit-post/src/store/controls.js | 16 -- packages/edit-post/src/store/test/actions.js | 178 ------------ 11 files changed, 413 insertions(+), 283 deletions(-) create mode 100644 packages/edit-post/src/components/editor-initialization/index.js create mode 100644 packages/edit-post/src/components/editor-initialization/listener-hooks.js create mode 100644 packages/edit-post/src/components/editor-initialization/test/listener-hooks.js rename packages/edit-post/src/{store => components/editor-initialization}/utils.js (100%) diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 55d3affac68770..52bbfd658b1b2c 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Refactor + +- Create EditorInitializer component and implement for various things to initialize as the editor is loaded. This replaces the `__unstableInitialize` refactor done in #14740. ([#15444](https://github.com/WordPress/gutenberg/pull/15444)) + ## 3.4.0 (2019-05-21) ### New Feature @@ -8,7 +14,6 @@ - convert `INIT` effect to controls & actions [#14740](https://github.com/WordPress/gutenberg/pull/14740) - ## 3.2.0 (2019-03-06) ### Polish diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js new file mode 100644 index 00000000000000..51cb90681f8ae8 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { useEffect } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + useAdjustSidebarListener, + useBlockSelectionListener, + useUpdatePostLinkListener, +} from './listener-hooks'; + +/** + * Data component used for initializing the editor and re-initializes + * when postId changes or on unmount. + * + * @param {number} postId The id of the post. + * @return {null} This is a data component so does not render any ui. + */ +export default function( { postId } ) { + useAdjustSidebarListener( postId ); + useBlockSelectionListener( postId ); + useUpdatePostLinkListener( postId ); + const { triggerGuide } = useDispatch( 'core/nux' ); + useEffect( () => { + triggerGuide( [ + 'core/editor.inserter', + 'core/editor.settings', + 'core/editor.preview', + 'core/editor.publish', + ] ); + }, [ triggerGuide ] ); + return null; +} diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js new file mode 100644 index 00000000000000..56d7761270eac4 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -0,0 +1,106 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect, useRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { + STORE_KEY, + VIEW_AS_LINK_SELECTOR, + VIEW_AS_PREVIEW_LINK_SELECTOR, +} from '../../store/constants'; + +/** + * This listener hook monitors for block selection and triggers the appropriate + * sidebar state. + * + * @param {number} postId The current post id. + */ +export const useBlockSelectionListener = ( postId ) => { + const { + hasBlockSelection, + isEditorSidebarOpened, + } = useSelect( + ( select ) => ( { + hasBlockSelection: !! select( + 'core/block-editor' + ).getBlockSelectionStart(), + isEditorSidebarOpened: select( STORE_KEY ).isEditorSidebarOpened(), + } ), + [ postId ] + ); + + const { openGeneralSidebar } = useDispatch( STORE_KEY ); + + useEffect( () => { + if ( ! isEditorSidebarOpened ) { + return; + } + if ( hasBlockSelection ) { + openGeneralSidebar( 'edit-post/block' ); + } else { + openGeneralSidebar( 'edit-post/document' ); + } + }, [ hasBlockSelection, isEditorSidebarOpened ] ); +}; + +/** + * This listener hook is used to monitor viewport size and adjust the sidebar + * accordingly. + * + * @param {number} postId The current post id. + */ +export const useAdjustSidebarListener = ( postId ) => { + const { isSmall, sidebarToReOpenOnExpand } = useSelect( + ( select ) => ( { + isSmall: select( 'core/viewport' ).isViewportMatch( '< medium' ), + sidebarToReOpenOnExpand: select( STORE_KEY ).getActiveGeneralSidebarName(), + } ), + [ postId ] + ); + + const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( STORE_KEY ); + + const previousOpenedSidebar = useRef( '' ); + + useEffect( () => { + if ( isSmall && sidebarToReOpenOnExpand ) { + previousOpenedSidebar.current = sidebarToReOpenOnExpand; + closeGeneralSidebar(); + } else if ( ! isSmall && previousOpenedSidebar.current ) { + openGeneralSidebar( previousOpenedSidebar.current ); + previousOpenedSidebar.current = ''; + } + }, [ isSmall, sidebarToReOpenOnExpand ] ); +}; + +/** + * This listener hook monitors any change in permalink and updates the view + * post link in the admin bar. + * + * @param {number} postId + */ +export const useUpdatePostLinkListener = ( postId ) => { + const { newPermalink } = useSelect( + ( select ) => ( { + newPermalink: select( 'core/editor' ).getCurrentPost().link, + } ), + [ postId ] + ); + const nodeToUpdate = useRef(); + + useEffect( () => { + nodeToUpdate.current = document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) || + document.querySelector( VIEW_AS_LINK_SELECTOR ); + }, [ postId ] ); + + useEffect( () => { + if ( ! newPermalink || ! nodeToUpdate.current ) { + return; + } + nodeToUpdate.current.setAttribute( 'href', newPermalink ); + }, [ newPermalink ] ); +}; diff --git a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js new file mode 100644 index 00000000000000..350a23f5b0ca39 --- /dev/null +++ b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js @@ -0,0 +1,255 @@ +/** + * External dependencies + */ +import TestRenderer, { act } from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { RegistryProvider } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + useBlockSelectionListener, + useAdjustSidebarListener, + useUpdatePostLinkListener, +} from '../listener-hooks'; +import { STORE_KEY } from '../../../store/constants'; + +describe( 'listener hook tests', () => { + const mockStores = { + 'core/block-editor': { + getBlockSelectionStart: jest.fn(), + }, + 'core/editor': { + getCurrentPost: jest.fn(), + }, + 'core/viewport': { + isViewportMatch: jest.fn(), + }, + [ STORE_KEY ]: { + isEditorSidebarOpened: jest.fn(), + openGeneralSidebar: jest.fn(), + closeGeneralSidebar: jest.fn(), + getActiveGeneralSidebarName: jest.fn(), + }, + }; + let subscribeTrigger; + const registry = { + select: jest.fn().mockImplementation( + ( storeName ) => mockStores[ storeName ] + ), + dispatch: jest.fn().mockImplementation( + ( storeName ) => mockStores[ storeName ] + ), + subscribe: ( subscription ) => { + subscribeTrigger = subscription; + }, + }; + const setMockReturnValue = ( store, functionName, value ) => { + mockStores[ store ][ functionName ] = jest.fn().mockReturnValue( value ); + }; + const getSpyedFunction = ( + store, + functionName + ) => mockStores[ store ][ functionName ]; + const renderComponent = ( testedHook, id, renderer = null ) => { + const TestComponent = ( { postId } ) => { + testedHook( postId ); + return null; + }; + const TestedOutput = <RegistryProvider value={ registry }> + <TestComponent postId={ id } /> + </RegistryProvider>; + return renderer === null ? + TestRenderer.create( TestedOutput ) : + renderer.update( TestedOutput ); + }; + afterEach( () => { + Object.values( mockStores ).forEach( ( storeMocks ) => { + Object.values( storeMocks ).forEach( ( mock ) => { + mock.mockClear(); + } ); + } ); + subscribeTrigger = undefined; + } ); + describe( 'useBlockSelectionListener', () => { + it( 'does nothing when editor sidebar is not open', () => { + setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', false ); + act( () => { + renderComponent( useBlockSelectionListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'isEditorSidebarOpened' ) + ).toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 0 ); + } ); + it( 'opens block sidebar if block is selected', () => { + setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); + setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', true ); + act( () => { + renderComponent( useBlockSelectionListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'edit-post/block' ); + } ); + it( 'opens document sidebar if block is not selected', () => { + setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); + setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', false ); + act( () => { + renderComponent( useBlockSelectionListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'edit-post/document' ); + } ); + } ); + describe( 'useAdjustSidebarListener', () => { + it( 'initializes and does nothing when viewport is not small', () => { + setMockReturnValue( 'core/viewport', 'isViewPortMatch', false ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'edit-post/document' ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).not.toHaveBeenCalled(); + } ); + it( 'does not close sidebar if viewport is small and there is no ' + + 'active sidebar name available', () => { + setMockReturnValue( 'core/viewport', 'isViewPortMatch', true ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', null ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).not.toHaveBeenCalled(); + } ); + it( 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available', () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'opens sidebar if viewport is not small, and there is a cached sidebar to ' + + 'reopen on expand', () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); + act( () => { + subscribeTrigger(); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'foo' ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + } ); + } ); + describe( 'useUpdatePostLinkListener', () => { + const setAttribute = jest.fn(); + const mockSelector = jest.fn(); + beforeEach( () => { + document.querySelector = mockSelector.mockReturnValue( { setAttribute } ); + } ); + afterEach( () => { + setAttribute.mockClear(); + mockSelector.mockClear(); + } ); + it( 'updates nothing if there is no view link available', () => { + mockSelector.mockImplementation( () => null ); + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'foo' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'updates nothing if there is no permalink', () => { + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: '' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + expect( setAttribute ).not.toHaveBeenCalled(); + } ); + it( 'only calls document query selector once across renders', () => { + act( () => { + const renderer = renderComponent( useUpdatePostLinkListener, 10 ); + renderComponent( useUpdatePostLinkListener, 20, renderer ); + } ); + expect( mockSelector ).toHaveBeenCalledTimes( 1 ); + act( () => { + subscribeTrigger(); + } ); + expect( mockSelector ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'only updates the permalink when it changes', () => { + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'foo' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + act( () => { + subscribeTrigger(); + } ); + expect( setAttribute ).toHaveBeenCalledTimes( 1 ); + } ); + it( 'updates the permalink when it changes', () => { + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'foo' } + ); + act( () => { + renderComponent( useUpdatePostLinkListener, 10 ); + } ); + setMockReturnValue( + 'core/editor', + 'getCurrentPost', + { link: 'bar' } + ); + act( () => { + subscribeTrigger(); + } ); + expect( setAttribute ).toHaveBeenCalledTimes( 2 ); + expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); + } ); + } ); +} ); diff --git a/packages/edit-post/src/store/utils.js b/packages/edit-post/src/components/editor-initialization/utils.js similarity index 100% rename from packages/edit-post/src/store/utils.js rename to packages/edit-post/src/components/editor-initialization/utils.js diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 88eb7c4a6113fd..92fad75ebef7e8 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -21,6 +21,7 @@ import { */ import preventEventDiscovery from './prevent-event-discovery'; import Layout from './components/layout'; +import EditorInitialization from './components/editor-initialization'; class Editor extends Component { constructor() { @@ -70,6 +71,7 @@ class Editor extends Component { hasFixedToolbar, focusMode, post, + postId, initialEdits, onError, hiddenBlockTypes, @@ -101,6 +103,7 @@ class Editor extends Component { { ...props } > <ErrorBoundary onError={ onError }> + <EditorInitialization postId={ postId } /> <Layout /> <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> </ErrorBoundary> diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index cf8070c8c7b4e4..66d8c753b46ad4 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -9,7 +9,6 @@ import '@wordpress/viewport'; import '@wordpress/notices'; import { registerCoreBlocks } from '@wordpress/block-library'; import { render, unmountComponentAtNode } from '@wordpress/element'; -import { dispatch } from '@wordpress/data'; /** * Internal dependencies @@ -76,14 +75,6 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening <!DOCTYPE html>. Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); } - dispatch( 'core/edit-post' ).__unstableInitialize(); - dispatch( 'core/nux' ).triggerGuide( [ - 'core/editor.inserter', - 'core/editor.settings', - 'core/editor.preview', - 'core/editor.publish', - ] ); - render( <Editor settings={ settings } diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index e3538cccddc64f..d771dc840d8049 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -3,13 +3,6 @@ */ import { castArray } from 'lodash'; -/** - * Internal dependencies - */ -import { __unstableSubscribe } from './controls'; -import { onChangeListener } from './utils'; -import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from './constants'; - /** * Returns an action object used in signalling that the user opened an editor sidebar. * @@ -238,75 +231,3 @@ export function metaBoxUpdatesSuccess() { type: 'META_BOX_UPDATES_SUCCESS', }; } - -/** - * Returns an action generator used to initialize some subscriptions for the - * post editor: - * - * - subscription for toggling the `edit-post/block` general sidebar when a - * block is selected. - * - subscription for hiding/showing the sidebar depending on size of viewport. - * - subscription for updating the "View Post" link in the admin bar when - * permalink is updated. - */ -export function* __unstableInitialize() { - // Select the block settings tab when the selected block changes - yield __unstableSubscribe( ( registry ) => onChangeListener( - () => !! registry.select( 'core/block-editor' ) - .getBlockSelectionStart(), - ( hasBlockSelection ) => { - if ( ! registry.select( 'core/edit-post' ).isEditorSidebarOpened() ) { - return; - } - if ( hasBlockSelection ) { - registry.dispatch( STORE_KEY ) - .openGeneralSidebar( 'edit-post/block' ); - } else { - registry.dispatch( STORE_KEY ) - .openGeneralSidebar( 'edit-post/document' ); - } - } - ) ); - // hide/show the sidebar depending on size of viewport. - yield __unstableSubscribe( ( registry ) => onChangeListener( - () => registry.select( 'core/viewport' ) - .isViewportMatch( '< medium' ), - ( () => { - let sidebarToReOpenOnExpand = null; - return ( isSmall ) => { - const { getActiveGeneralSidebarName } = registry.select( STORE_KEY ); - const { - closeGeneralSidebar: closeSidebar, - openGeneralSidebar: openSidebar, - } = registry.dispatch( STORE_KEY ); - if ( isSmall ) { - sidebarToReOpenOnExpand = getActiveGeneralSidebarName(); - if ( sidebarToReOpenOnExpand ) { - closeSidebar(); - } - } else if ( - sidebarToReOpenOnExpand && - ! getActiveGeneralSidebarName() - ) { - openSidebar( sidebarToReOpenOnExpand ); - } - }; - } )(), - true - ) ); - // Update View Post link in the admin bar when permalink is updated. - yield __unstableSubscribe( ( registry ) => onChangeListener( - () => registry.select( 'core/editor' ).getCurrentPost().link, - ( newPermalink ) => { - if ( ! newPermalink ) { - return; - } - const nodeToUpdate = document.querySelector( VIEW_AS_LINK_SELECTOR ); - if ( ! nodeToUpdate ) { - return; - } - nodeToUpdate.setAttribute( 'href', newPermalink ); - } - ) ); -} - diff --git a/packages/edit-post/src/store/constants.js b/packages/edit-post/src/store/constants.js index 60f80d914a5c7b..35acac0c5633a8 100644 --- a/packages/edit-post/src/store/constants.js +++ b/packages/edit-post/src/store/constants.js @@ -9,3 +9,9 @@ export const STORE_KEY = 'core/edit-post'; * @type {string} */ export const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; + +/** + * CSS selector string for the admin bar preview post link anchor tag. + * @type {string} + */ +export const VIEW_AS_PREVIEW_LINK_SELECTOR = '#wp-admin-bar-preview a'; diff --git a/packages/edit-post/src/store/controls.js b/packages/edit-post/src/store/controls.js index 462741a1734075..3a64e045f69666 100644 --- a/packages/edit-post/src/store/controls.js +++ b/packages/edit-post/src/store/controls.js @@ -21,28 +21,12 @@ export function select( storeName, selectorName, ...args ) { }; } -/** - * Calls a subscriber using the current state. - * - * @param {function} listenerCallback A callback for the subscriber that - * receives the registry. - * @return {Object} control descriptor. - */ -export function __unstableSubscribe( listenerCallback ) { - return { type: 'SUBSCRIBE', listenerCallback }; -} - const controls = { SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { return registry.select( storeName )[ selectorName ]( ...args ); } ), - SUBSCRIBE: createRegistryControl( - ( registry ) => ( { listenerCallback } ) => { - return registry.subscribe( listenerCallback( registry ) ); - } - ), }; export default controls; diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index a16e58f9cc18d8..07dc4b81ece682 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -15,9 +15,7 @@ import { toggleFeature, togglePinnedPluginItem, requestMetaBoxUpdates, - __unstableInitialize, } from '../actions'; -import { STORE_KEY, VIEW_AS_LINK_SELECTOR } from '../constants'; describe( 'actions', () => { describe( 'openGeneralSidebar', () => { @@ -135,180 +133,4 @@ describe( 'actions', () => { } ); } ); } ); - - describe( '__unstableInitialize', () => { - let fulfillment; - const reset = () => fulfillment = __unstableInitialize(); - const registryMock = { select: {}, dispatch: {} }; - describe( 'yields subscribe control descriptor for block settings', () => { - reset(); - const { value } = fulfillment.next(); - const listenerCallback = value.listenerCallback; - const isEditorSidebarOpened = jest.fn(); - const getBlockSelectionStart = jest.fn(); - const openSidebar = jest.fn(); - beforeEach( () => { - registryMock.select = ( store ) => { - const stores = { - 'core/block-editor': { getBlockSelectionStart }, - 'core/edit-post': { isEditorSidebarOpened }, - }; - return stores[ store ]; - }; - registryMock.dispatch = () => ( { openGeneralSidebar: openSidebar } ); - } ); - afterEach( () => { - isEditorSidebarOpened.mockClear(); - getBlockSelectionStart.mockClear(); - openSidebar.mockClear(); - } ); - it( 'returns subscribe control descriptor', () => { - expect( value.type ).toBe( 'SUBSCRIBE' ); - } ); - it( 'does nothing if sidebar is not opened', () => { - getBlockSelectionStart.mockReturnValue( true ); - isEditorSidebarOpened.mockReturnValue( false ); - const listener = listenerCallback( registryMock ); - getBlockSelectionStart.mockReturnValue( false ); - listener(); - expect( getBlockSelectionStart ).toHaveBeenCalled(); - expect( isEditorSidebarOpened ).toHaveBeenCalled(); - expect( openSidebar ).not.toHaveBeenCalled(); - } ); - it( 'opens block sidebar if block is selected', () => { - isEditorSidebarOpened.mockReturnValue( true ); - getBlockSelectionStart.mockReturnValue( false ); - const listener = listenerCallback( registryMock ); - getBlockSelectionStart.mockReturnValue( true ); - listener(); - expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/block' ); - } ); - it( 'opens document sidebar if block is not selected', () => { - isEditorSidebarOpened.mockReturnValue( true ); - getBlockSelectionStart.mockReturnValue( true ); - const listener = listenerCallback( registryMock ); - getBlockSelectionStart.mockReturnValue( false ); - listener(); - expect( openSidebar ).toHaveBeenCalledWith( 'edit-post/document' ); - } ); - } ); - describe( 'yields subscribe control descriptor for adjusting the ' + - 'sidebar', () => { - reset(); - fulfillment.next(); - const { value } = fulfillment.next(); - const listenerCallback = value.listenerCallback; - const isViewportMatch = jest.fn(); - const getActiveGeneralSidebarName = jest.fn(); - const willCloseGeneralSidebar = jest.fn(); - const willOpenGeneralSidebar = jest.fn(); - beforeEach( () => { - registryMock.select = ( store ) => { - const stores = { - 'core/viewport': { isViewportMatch }, - [ STORE_KEY ]: { getActiveGeneralSidebarName }, - }; - return stores[ store ]; - }; - registryMock.dispatch = ( store ) => { - const stores = { - [ STORE_KEY ]: { - closeGeneralSidebar: willCloseGeneralSidebar, - openGeneralSidebar: willOpenGeneralSidebar, - }, - }; - return stores[ store ]; - }; - registryMock.subscribe = jest.fn(); - isViewportMatch.mockReturnValue( true ); - } ); - afterEach( () => { - isViewportMatch.mockClear(); - getActiveGeneralSidebarName.mockClear(); - willCloseGeneralSidebar.mockClear(); - willOpenGeneralSidebar.mockClear(); - } ); - it( 'returns subscribe control descriptor', () => { - expect( value.type ).toBe( 'SUBSCRIBE' ); - } ); - it( 'initializes and does nothing when viewport is not small', () => { - isViewportMatch.mockReturnValue( false ); - listenerCallback( registryMock )(); - expect( isViewportMatch ).toHaveBeenCalled(); - expect( getActiveGeneralSidebarName ).not.toHaveBeenCalled(); - } ); - it( 'does not close sidebar if viewport is small and there is no ' + - 'active sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( false ); - listenerCallback( registryMock )(); - expect( willCloseGeneralSidebar ).not.toHaveBeenCalled(); - expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); - } ); - it( 'closes sidebar if viewport is small and there is an active ' + - 'sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); - listenerCallback( registryMock )(); - expect( willCloseGeneralSidebar ).toHaveBeenCalled(); - expect( willOpenGeneralSidebar ).not.toHaveBeenCalled(); - } ); - it( 'opens sidebar if viewport is not small, there is a cached sidebar to ' + - 'reopen on expand, and there is no current sidebar name available', () => { - getActiveGeneralSidebarName.mockReturnValue( 'someSidebar' ); - const listener = listenerCallback( registryMock ); - listener(); - isViewportMatch.mockReturnValue( false ); - getActiveGeneralSidebarName.mockReturnValue( false ); - listener(); - expect( willCloseGeneralSidebar ).toHaveBeenCalledTimes( 1 ); - expect( willOpenGeneralSidebar ).toHaveBeenCalledTimes( 1 ); - } ); - } ); - describe( 'yields subscribe control descriptor for updating the ' + - 'view post link when the permalink changes', () => { - reset(); - fulfillment.next(); - fulfillment.next(); - const { value } = fulfillment.next(); - const listenerCallback = value.listenerCallback; - const getCurrentPost = jest.fn(); - const setAttribute = jest.fn(); - beforeEach( () => { - document.querySelector = jest.fn().mockReturnValue( { setAttribute } ); - getCurrentPost.mockReturnValue( { link: 'foo' } ); - registryMock.select = ( store ) => { - const stores = { 'core/editor': { getCurrentPost } }; - return stores[ store ]; - }; - } ); - afterEach( () => { - setAttribute.mockClear(); - getCurrentPost.mockClear(); - } ); - it( 'returns expected control descriptor', () => { - expect( value.type ).toBe( 'SUBSCRIBE' ); - } ); - it( 'updates nothing if there is no new permalink', () => { - const listener = listenerCallback( registryMock ); - listener(); - expect( getCurrentPost ).toHaveBeenCalledTimes( 2 ); - expect( document.querySelector ).not.toHaveBeenCalled(); - expect( setAttribute ).not.toHaveBeenCalled(); - } ); - it( 'does not do anything if the node is not found', () => { - const listener = listenerCallback( registryMock ); - getCurrentPost.mockReturnValue( { link: 'bar' } ); - document.querySelector.mockReturnValue( false ); - listener(); - expect( document.querySelector ) - .toHaveBeenCalledWith( VIEW_AS_LINK_SELECTOR ); - expect( setAttribute ).not.toHaveBeenCalled(); - } ); - it( 'updates with the new permalink when node is found', () => { - const listener = listenerCallback( registryMock ); - getCurrentPost.mockReturnValue( { link: 'bar' } ); - listener(); - expect( setAttribute ).toHaveBeenCalledWith( 'href', 'bar' ); - } ); - } ); - } ); } ); From eedd79e1acc5134561a559d526065ec409306b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Wed, 26 Jun 2019 09:59:40 +0100 Subject: [PATCH 393/664] Add Eduardo as owner of rich text related files. (#16293) --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4a7a5af6e15205..22a1f4852b1434 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -77,9 +77,9 @@ /packages/plugins @youknowriad @gziolo @aduth @adamsilverstein # Rich Text -/packages/format-library @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao -/packages/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao -/packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao +/packages/format-library @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom # PHP /lib @youknowriad @gziolo @aduth @timothybjacobs From a55e52637a6f4b135705c3503d0cf077f1fccb9c Mon Sep 17 00:00:00 2001 From: andrei draganescu <andrei.draganescu@automattic.com> Date: Wed, 26 Jun 2019 13:56:29 +0200 Subject: [PATCH 394/664] fix image block regression causing bug (#16295) --- packages/block-library/src/image/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 1d4d27a7db29e1..faabe881a04c94 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -450,7 +450,7 @@ class ImageEdit extends Component { ); } - const classes = classnames( { + const classes = classnames( className, { 'is-transient': isBlobURL( url ), 'is-resized': !! width || !! height, 'is-focused': isSelected, From b88bfc142bbe631cb87de0f8086804fcb4c7e973 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Wed, 26 Jun 2019 14:20:27 +0200 Subject: [PATCH 395/664] Add Editor and Layout in @wordpress/edit-post (Ported from Gutenberg Mobile) (#16260) * Add Editor and Layout in @wordpress/edit-post (Ported from Gutenberg Mobile) * Remove flow check for Editor native * Remove flow attribute definition in Layout --- .../src/components/layout/index.native.js | 127 +++++++++++ .../src/components/layout/style.native.scss | 6 + packages/edit-post/src/editor.native.js | 204 ++++++++++++++++++ packages/edit-post/src/index.native.js | 12 +- packages/edit-post/src/test/editor.native.js | 47 ++++ 5 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 packages/edit-post/src/components/layout/index.native.js create mode 100644 packages/edit-post/src/components/layout/style.native.scss create mode 100644 packages/edit-post/src/editor.native.js create mode 100644 packages/edit-post/src/test/editor.native.js diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js new file mode 100644 index 00000000000000..6081dc73410609 --- /dev/null +++ b/packages/edit-post/src/components/layout/index.native.js @@ -0,0 +1,127 @@ +/** + * External dependencies + */ +import { SafeAreaView } from 'react-native'; +import SafeArea from 'react-native-safe-area'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { HTMLTextInput, ReadableContentView } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; +import VisualEditor from '../visual-editor'; + +class Layout extends Component { + constructor() { + super( ...arguments ); + + this.onSafeAreaInsetsUpdate = this.onSafeAreaInsetsUpdate.bind( this ); + this.onRootViewLayout = this.onRootViewLayout.bind( this ); + + this.state = { + rootViewHeight: 0, + safeAreaBottomInset: 0, + isFullyBordered: true, + }; + + SafeArea.getSafeAreaInsetsForRootView().then( this.onSafeAreaInsetsUpdate ); + } + + componentDidMount() { + this._isMounted = true; + SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + } + + componentWillUnmount() { + SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + this._isMounted = false; + } + + onSafeAreaInsetsUpdate( result ) { + const { safeAreaInsets } = result; + if ( this._isMounted && this.state.safeAreaBottomInset !== safeAreaInsets.bottom ) { + this.setState( { safeAreaBottomInset: safeAreaInsets.bottom } ); + } + } + + onRootViewLayout( event ) { + if ( this._isMounted ) { + this.setHeightState( event ); + this.setBorderStyleState(); + } + } + + setHeightState( event ) { + const { height } = event.nativeEvent.layout; + this.setState( { rootViewHeight: height }, this.props.onNativeEditorDidLayout ); + } + + setBorderStyleState() { + const isFullyBordered = ReadableContentView.isContentMaxWidth(); + if ( isFullyBordered !== this.state.isFullyBordered ) { + this.setState( { isFullyBordered } ); + } + } + + renderHTML() { + return ( + <HTMLTextInput + parentHeight={ this.state.rootViewHeight } + /> + ); + } + + renderVisual() { + const { + isReady, + } = this.props; + + if ( ! isReady ) { + return null; + } + + return ( + <VisualEditor + isFullyBordered={ this.state.isFullyBordered } + rootViewHeight={ this.state.rootViewHeight } + safeAreaBottomInset={ this.state.safeAreaBottomInset } + setTitleRef={ this.props.setTitleRef } + /> + ); + } + + render() { + const { + mode, + } = this.props; + + return ( + <SafeAreaView style={ styles.container } onLayout={ this.onRootViewLayout }> + { mode === 'text' ? this.renderHTML() : this.renderVisual() } + </SafeAreaView> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + __unstableIsEditorReady: isEditorReady, + } = select( 'core/editor' ); + const { + getEditorMode, + } = select( 'core/edit-post' ); + + return { + isReady: isEditorReady(), + mode: getEditorMode(), + }; + } ), +] )( Layout ); diff --git a/packages/edit-post/src/components/layout/style.native.scss b/packages/edit-post/src/components/layout/style.native.scss new file mode 100644 index 00000000000000..badc66b9b619fb --- /dev/null +++ b/packages/edit-post/src/components/layout/style.native.scss @@ -0,0 +1,6 @@ + +.container { + flex: 1; + justify-content: flex-start; + background-color: #fff; +} diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js new file mode 100644 index 00000000000000..714b9665ef590f --- /dev/null +++ b/packages/edit-post/src/editor.native.js @@ -0,0 +1,204 @@ +/** + * External dependencies + */ +import RNReactNativeGutenbergBridge, { + subscribeParentGetHtml, + subscribeParentToggleHTMLMode, + subscribeUpdateHtml, + subscribeSetFocusOnTitle, + subscribeSetTitle, + sendNativeEditorDidLayout, +} from 'react-native-gutenberg-bridge'; +import { isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { parse, serialize, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import Layout from './components/layout'; + +class Editor extends Component { + constructor( props ) { + super( ...arguments ); + + this.setTitleRef = this.setTitleRef.bind( this ); + + // TODO: use EditorProvider instead + this.post = props.post || { + id: 1, + title: { + raw: props.initialTitle, + }, + content: { + raw: props.initialHtml || '', + }, + type: 'draft', + }; + + props.setupEditor( this.post ); + + // make sure the post content is in sync with gutenberg store + // to avoid marking the post as modified when simply loaded + // For now, let's assume: serialize( parse( html ) ) !== html + this.post.content.raw = serialize( props.getEditorBlocks() ); + + if ( props.initialHtmlModeEnabled && props.mode === 'visual' ) { + // enable html mode if the initial mode the parent wants it but we're not already in it + this.toggleMode(); + } + } + + componentDidMount() { + this.subscriptionParentGetHtml = subscribeParentGetHtml( () => { + this.serializeToNativeAction(); + } ); + + this.subscriptionParentToggleHTMLMode = subscribeParentToggleHTMLMode( () => { + this.toggleMode(); + } ); + + this.subscriptionParentSetTitle = subscribeSetTitle( ( payload ) => { + this.props.editTitle( payload.title ); + } ); + + this.subscriptionParentUpdateHtml = subscribeUpdateHtml( ( payload ) => { + this.updateHtmlAction( payload.html ); + } ); + + this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( () => { + if ( this.postTitleRef ) { + this.postTitleRef.focus(); + } + } ); + } + + componentWillUnmount() { + if ( this.subscriptionParentGetHtml ) { + this.subscriptionParentGetHtml.remove(); + } + + if ( this.subscriptionParentToggleHTMLMode ) { + this.subscriptionParentToggleHTMLMode.remove(); + } + + if ( this.subscriptionParentSetTitle ) { + this.subscriptionParentSetTitle.remove(); + } + + if ( this.subscriptionParentUpdateHtml ) { + this.subscriptionParentUpdateHtml.remove(); + } + + if ( this.subscriptionParentSetFocusOnTitle ) { + this.subscriptionParentSetFocusOnTitle.remove(); + } + } + + serializeToNativeAction() { + if ( this.props.mode === 'text' ) { + this.updateHtmlAction( this.props.getEditedPostContent() ); + } + + const html = serialize( this.props.getEditorBlocks() ); + const title = this.props.getEditedPostAttribute( 'title' ); + + const hasChanges = title !== this.post.title.raw || html !== this.post.content.raw; + + RNReactNativeGutenbergBridge.provideToNative_Html( html, title, hasChanges ); + + if ( hasChanges ) { + this.post.title.raw = title; + this.post.content.raw = html; + } + } + + updateHtmlAction( html ) { + const parsed = parse( html ); + this.props.resetEditorBlocksWithoutUndoLevel( parsed ); + } + + toggleMode() { + const { mode, switchMode } = this.props; + // refresh html content first + this.serializeToNativeAction(); + switchMode( mode === 'visual' ? 'text' : 'visual' ); + } + + componentDidUpdate( prevProps ) { + if ( ! prevProps.isReady && this.props.isReady ) { + const blocks = this.props.getEditorBlocks(); + const isUnsupportedBlock = ( { name } ) => name === getUnregisteredTypeHandlerName(); + const unsupportedBlocks = blocks.filter( isUnsupportedBlock ); + const hasUnsupportedBlocks = ! isEmpty( unsupportedBlocks ); + + RNReactNativeGutenbergBridge.editorDidMount( hasUnsupportedBlocks ); + } + } + + setTitleRef( titleRef ) { + this.postTitleRef = titleRef; + } + + render() { + return ( + <Layout + setTitleRef={ this.setTitleRef } + onNativeEditorDidLayout={ sendNativeEditorDidLayout } + /> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + __unstableIsEditorReady: isEditorReady, + getEditorBlocks, + getEditedPostAttribute, + getEditedPostContent, + } = select( 'core/editor' ); + const { + getEditorMode, + } = select( 'core/edit-post' ); + + return { + mode: getEditorMode(), + isReady: isEditorReady(), + getEditorBlocks, + getEditedPostAttribute, + getEditedPostContent, + }; + } ), + withDispatch( ( dispatch ) => { + const { + editPost, + setupEditor, + resetEditorBlocks, + } = dispatch( 'core/editor' ); + const { + switchEditorMode, + } = dispatch( 'core/edit-post' ); + + return { + editTitle( title ) { + editPost( { title } ); + }, + resetEditorBlocksWithoutUndoLevel( blocks ) { + resetEditorBlocks( blocks, { + __unstableShouldCreateUndoLevel: false, + } ); + }, + setupEditor, + switchMode( mode ) { + switchEditorMode( mode ); + }, + }; + } ), +] )( Editor ); diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 6a2d926d2c193d..1ffdcb13477335 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -2,21 +2,28 @@ * WordPress dependencies */ import '@wordpress/core-data'; +import '@wordpress/block-editor'; +import '@wordpress/editor'; import '@wordpress/notices'; import { registerCoreBlocks } from '@wordpress/block-library'; import { unregisterBlockType } from '@wordpress/blocks'; +import '@wordpress/format-library'; /** * Internal dependencies */ import './store'; -export { default as VisualEditor } from './components/visual-editor'; +let blocksRegistered = false; /** * Initializes the Editor. */ export function initializeEditor() { + if ( blocksRegistered ) { + return; + } + // register and setup blocks registerCoreBlocks(); @@ -25,5 +32,8 @@ export function initializeEditor() { if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); } + + blocksRegistered = true; } +export { default as Editor } from './editor'; diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js new file mode 100644 index 00000000000000..44592b0fb4204f --- /dev/null +++ b/packages/edit-post/src/test/editor.native.js @@ -0,0 +1,47 @@ +/** + * External dependencies + */ +import renderer from 'react-test-renderer'; +import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { registerCoreBlocks } from '@wordpress/block-library'; + +/** + * Internal dependencies + */ +import '../store'; +import Editor from '../editor'; + +const unsupportedBlock = ` +<!-- wp:notablock --> +<p>Not supported</p> +<!-- /wp:notablock --> +`; + +describe( 'Editor', () => { + beforeAll( registerCoreBlocks ); + + it( 'detects unsupported block and sends hasUnsupportedBlocks true to native', () => { + RNReactNativeGutenbergBridge.editorDidMount = jest.fn(); + + const appContainer = renderEditorWith( unsupportedBlock ); + appContainer.unmount(); + + expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledTimes( 1 ); + expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledWith( true ); + } ); +} ); + +// Utilities +const renderEditorWith = ( content ) => { + return renderer.create( + <Editor + initialHtml={ content } + initialHtmlModeEnabled={ false } + initialTitle={ '' } + /> + ); +}; From 66775dc5ebf820c5ac7986815893ee2b5b454366 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 26 Jun 2019 13:30:09 +0100 Subject: [PATCH 396/664] Bump plugin version to 6.0.0 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 5a03abdad7c309..977083ad622016 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.0.0-rc.1 + * Version: 6.0.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 7b34929e4cd4c9..74f3cedd36cc00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.0.0-rc.1", + "version": "6.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 64a7a54a098d70..1087de7094828a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.0.0-rc.1", + "version": "6.0.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From c4fe20c79ba362f1ec5605d772c5d7af5e24a3f4 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 26 Jun 2019 09:32:10 -0400 Subject: [PATCH 397/664] Blocks: Remove unused asType internal parser utility (#16291) --- packages/blocks/src/api/parser.js | 39 -------------------------- packages/blocks/src/api/test/parser.js | 39 -------------------------- 2 files changed, 78 deletions(-) diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 162c5cec83c508..40c752c8305c6d 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -169,45 +169,6 @@ export function isAmbiguousStringSource( attributeSchema ) { return isStringSource && isSingleType; } -/** - * Returns value coerced to the specified JSON schema type string. - * - * @see http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.25 - * - * @param {*} value Original value. - * @param {string} type Type to coerce. - * - * @return {*} Coerced value. - */ -export function asType( value, type ) { - switch ( type ) { - case 'string': - return String( value ); - - case 'boolean': - return Boolean( value ); - - case 'object': - return Object( value ); - - case 'null': - return null; - - case 'array': - if ( Array.isArray( value ) ) { - return value; - } - - return Array.from( value ); - - case 'integer': - case 'number': - return Number( value ); - } - - return value; -} - /** * Returns an hpq matcher given a source object. * diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js index f6c1569a420436..8ad501739d7416 100644 --- a/packages/blocks/src/api/test/parser.js +++ b/packages/blocks/src/api/test/parser.js @@ -10,7 +10,6 @@ import deepFreeze from 'deep-freeze'; import { getBlockAttribute, getBlockAttributes, - asType, createBlockWithFallback, getMigratedBlock, default as parsePegjs, @@ -104,44 +103,6 @@ describe( 'block parser', () => { } ); } ); - describe( 'asType()', () => { - it( 'gracefully handles undefined type', () => { - expect( asType( 5 ) ).toBe( 5 ); - } ); - - it( 'gracefully handles unhandled type', () => { - expect( asType( 5, '__UNHANDLED__' ) ).toBe( 5 ); - } ); - - it( 'returns expected coerced values', () => { - const arr = []; - const obj = {}; - - expect( asType( '5', 'string' ) ).toBe( '5' ); - expect( asType( 5, 'string' ) ).toBe( '5' ); - - expect( asType( 5, 'integer' ) ).toBe( 5 ); - expect( asType( '5', 'integer' ) ).toBe( 5 ); - - expect( asType( 5, 'number' ) ).toBe( 5 ); - expect( asType( '5', 'number' ) ).toBe( 5 ); - - expect( asType( true, 'boolean' ) ).toBe( true ); - expect( asType( false, 'boolean' ) ).toBe( false ); - expect( asType( '5', 'boolean' ) ).toBe( true ); - expect( asType( 0, 'boolean' ) ).toBe( false ); - - expect( asType( null, 'null' ) ).toBe( null ); - expect( asType( 0, 'null' ) ).toBe( null ); - - expect( asType( arr, 'array' ) ).toBe( arr ); - expect( asType( new Set( [ 1, 2, 3 ] ), 'array' ) ).toEqual( [ 1, 2, 3 ] ); - - expect( asType( obj, 'object' ) ).toBe( obj ); - expect( asType( {}, 'object' ) ).toEqual( {} ); - } ); - } ); - describe( 'isOfType()', () => { it( 'gracefully handles unhandled type', () => { expect( isOfType( 5, '__UNHANDLED__' ) ).toBe( true ); From f4d5d500630e44e4f2b0cb160fed7c0ca2ee353a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 26 Jun 2019 09:32:47 -0400 Subject: [PATCH 398/664] ESLint Plugin: react-no-unsafe-timeout: Consider variable assignment as valid (#16292) * ESLint Plugin: react-no-unsafe-timeout: Consider variable assignment as valid * Components: Remove ESLint rule disabling for react-no-unsafe-timeout --- packages/components/src/snackbar/index.js | 2 -- packages/eslint-plugin/CHANGELOG.md | 1 + .../rules/__tests__/react-no-unsafe-timeout.js | 12 ++++++++++++ .../eslint-plugin/rules/react-no-unsafe-timeout.js | 5 ++++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js index a62924219a9b5c..e970a0fba5dc03 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.js @@ -24,8 +24,6 @@ function Snackbar( { onRemove = noop, }, ref ) { useEffect( () => { - // This rule doesn't account yet for React Hooks - // eslint-disable-next-line @wordpress/react-no-unsafe-timeout const timeoutHandle = setTimeout( () => { onRemove(); }, NOTICE_TIMEOUT ); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 28de7846b5f212..abe9728d926313 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -4,6 +4,7 @@ - Fixed custom regular expression for the `no-restricted-syntax` rule enforcing translate function arguments. [#15839](https://github.com/WordPress/gutenberg/pull/15839). - Fixed arguments checking of `_nx` for the `no-restricted-syntax` rule enforcing translate function arguments. [#15839](https://github.com/WordPress/gutenberg/pull/15839). +- Fixed false positive with `react-no-unsafe-timeout` which would wrongly flag errors when assigning `setTimeout` result to a variable (for example, in a `useEffect` hook). ## 2.2.0 (2019-05-21) diff --git a/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js b/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js index e6780e0332c48b..85046a73b56104 100644 --- a/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js +++ b/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js @@ -40,6 +40,18 @@ ruleTester.run( 'react-no-unsafe-timeout', rule, { { code: `class MyComponent extends Component { componentDidMount() { this.timeoutId = setTimeout(); } }`, }, + { + code: ` +function MyComponent() { + useEffect( () => { + const timeoutHandle = setTimeout( () => {} ); + + return () => clearTimeout( timeoutHandle ); + }, [] ); + + return null; +}`, + }, ], invalid: [ { diff --git a/packages/eslint-plugin/rules/react-no-unsafe-timeout.js b/packages/eslint-plugin/rules/react-no-unsafe-timeout.js index c211007a6ccb13..64ff537199caac 100644 --- a/packages/eslint-plugin/rules/react-no-unsafe-timeout.js +++ b/packages/eslint-plugin/rules/react-no-unsafe-timeout.js @@ -48,7 +48,10 @@ module.exports = { // If the result of a `setTimeout` call is assigned to a // variable, assume the timer ID is handled by a cancellation. - const hasAssignment = node.parent.type === 'AssignmentExpression'; + const hasAssignment = ( + node.parent.type === 'AssignmentExpression' || + node.parent.type === 'VariableDeclarator' + ); if ( hasAssignment ) { return; } From 3fc62bb26824a0a4350fb1e84f83577e53669f4a Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Wed, 26 Jun 2019 15:42:33 +0100 Subject: [PATCH 399/664] Add support for watching block.json files when running `npm run dev` (#16150) * Add support for watching block.json files when running `npm run dev` * Revert "Add support for watching block.json files when running `npm run dev`" This reverts commit d49efd6d6d5273491d221f39bfc8414e18850786. * Handle block.json transform in the build.js script * Add jsdoc comment * Fix path matching on Windows * Simplify block.json build transform --- bin/packages/build.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/bin/packages/build.js b/bin/packages/build.js index 9639f8b232f70e..e1a6bd7142f80d 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -64,6 +64,43 @@ function createStyleEntryTransform() { } ); } +/** + * Returns a stream transform which maps an individual block.json to the + * index.js that imports it. Presently, babel resolves the import of json + * files by inlining them as a JavaScript primitive in the importing file. + * This transform ensures the importing file is rebuilt. + * + * @return {Transform} Stream transform instance. + */ +function createBlockJsonEntryTransform() { + const blocks = new Set; + + return new Transform( { + objectMode: true, + async transform( file, encoding, callback ) { + const matches = /block-library[\/\\]src[\/\\](.*)[\/\\]block.json$/.exec( file ); + const blockName = matches ? matches[ 1 ] : undefined; + + // Only block.json files in the block-library folder are subject to this transform. + if ( ! blockName ) { + this.push( file ); + callback(); + return; + } + + // Only operate once per block, assuming entries are common. + if ( blockName && blocks.has( blockName ) ) { + callback(); + return; + } + + blocks.add( blockName ); + this.push( file.replace( 'block.json', 'index.js' ) ); + callback(); + }, + } ); +} + let onFileComplete = () => {}; let stream; @@ -72,7 +109,9 @@ if ( files.length ) { stream = new Readable( { encoding: 'utf8' } ); files.forEach( ( file ) => stream.push( file ) ); stream.push( null ); - stream = stream.pipe( createStyleEntryTransform() ); + stream = stream + .pipe( createStyleEntryTransform() ) + .pipe( createBlockJsonEntryTransform() ); } else { const bar = new ProgressBar( 'Build Progress: [:bar] :percent', { width: 30, From b2690fabe7cf415a926f1b5c25a0b4fead288870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Wed, 26 Jun 2019 16:45:50 +0200 Subject: [PATCH 400/664] RichText: fix inline toolbar position (#16299) --- packages/block-editor/src/components/rich-text/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index baca2921d1677f..8f96935d549895 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -112,8 +112,10 @@ function RichTextWraper( { <FormatToolbar /> </BlockFormatControls> ) } - { inlineToolbar && ( - <IsolatedEventContainer> + { isSelected && inlineToolbar && ( + <IsolatedEventContainer + className="editor-rich-text__inline-toolbar block-editor-rich-text__inline-toolbar" + > <FormatToolbar /> </IsolatedEventContainer> ) } From 89a8ac90b714e747bf57cf7e7deb16705357564c Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 26 Jun 2019 13:51:10 -0400 Subject: [PATCH 401/664] Blocks: Fix error from tag source of non-matching selector (#16290) * Blocks: Fix error from tag source of non-matching selector * Blocks: Add missing closing tag for tag source test case --- packages/blocks/src/api/parser.js | 2 +- packages/blocks/src/api/test/parser.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 40c752c8305c6d..b40532957c5f05 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -199,7 +199,7 @@ export function matcherFromSource( sourceConfig ) { case 'tag': return flow( [ prop( sourceConfig.selector, 'nodeName' ), - ( value ) => value.toLowerCase(), + ( nodeName ) => nodeName ? nodeName.toLowerCase() : undefined, ] ); default: // eslint-disable-next-line no-console diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js index 8ad501739d7416..8c0e50feee3eec 100644 --- a/packages/blocks/src/api/test/parser.js +++ b/packages/blocks/src/api/test/parser.js @@ -243,6 +243,32 @@ describe( 'block parser', () => { ); expect( value ).toBe( false ); } ); + + describe( 'source: tag', () => { + it( 'returns tag name of matching selector', () => { + const value = parseWithAttributeSchema( + '<div></div>', + { + source: 'tag', + selector: ':nth-child(1)', + } + ); + + expect( value ).toBe( 'div' ); + } ); + + it( 'returns undefined when no element matches selector', () => { + const value = parseWithAttributeSchema( + '<div></div>', + { + source: 'tag', + selector: ':nth-child(2)', + } + ); + + expect( value ).toBe( undefined ); + } ); + } ); } ); describe( 'getBlockAttribute', () => { From 0719922928f9c7e8764e80d1ecf648b1ed43d711 Mon Sep 17 00:00:00 2001 From: Airat Halitov <4050715+AiratHalitov@users.noreply.github.com> Date: Thu, 27 Jun 2019 00:31:51 +0500 Subject: [PATCH 402/664] Update PULL_REQUEST_TEMPLATE.md (#16312) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cb60c1c2f0354d..bef89730704cbb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -19,4 +19,4 @@ - [ ] My code follows the WordPress code style. <!-- Check code: `npm run lint`, Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/ --> - [ ] My code follows the accessibility standards. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/coding-standards/accessibility-coding-standards/ --> - [ ] My code has proper inline documentation. <!-- Guidelines: https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/ --> -- [ ] I've included developer documentation if appropriate. <!-- Handbook: https://wordpress.org/gutenberg/handbook/designers-developers/ --> +- [ ] I've included developer documentation if appropriate. <!-- Handbook: https://developer.wordpress.org/block-editor/ --> From 2b061c55ec154a98c97d1f9084cb61c0cd28cf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Thu, 27 Jun 2019 15:58:22 +0200 Subject: [PATCH 403/664] Copy All Content button: Hide if there is no content (#16286) --- .../plugins/copy-content-menu-item/index.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index af93ecb542ba6e..a46355cde7f969 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -8,27 +8,29 @@ import { withState, compose } from '@wordpress/compose'; function CopyContentMenuItem( { createNotice, editedPostContent, hasCopied, setState } ) { return ( - <ClipboardButton - text={ editedPostContent } - role="menuitem" - className="components-menu-item__button" - onCopy={ () => { - setState( { hasCopied: true } ); - createNotice( - 'info', - 'All content copied.', - { - isDismissible: true, - type: 'snackbar', - } - ); - } } - onFinishCopy={ () => setState( { hasCopied: false } ) } - > - { hasCopied ? - __( 'Copied!' ) : - __( 'Copy All Content' ) } - </ClipboardButton> + editedPostContent.length > 0 && ( + <ClipboardButton + text={ editedPostContent } + role="menuitem" + className="components-menu-item__button" + onCopy={ () => { + setState( { hasCopied: true } ); + createNotice( + 'info', + 'All content copied.', + { + isDismissible: true, + type: 'snackbar', + } + ); + } } + onFinishCopy={ () => setState( { hasCopied: false } ) } + > + { hasCopied ? + __( 'Copied!' ) : + __( 'Copy All Content' ) } + </ClipboardButton> + ) ); } From e36e448e31dab17e1bf8c01b33eff23ec0c11ece Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 27 Jun 2019 17:46:18 +0100 Subject: [PATCH 404/664] Update re-resizable dependency to the last version (#16325) --- package-lock.json | 16 ++++++++++++---- packages/components/package.json | 2 +- packages/components/src/resizable-box/index.js | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74f3cedd36cc00..77e3b65940e424 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3381,7 +3381,7 @@ "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", - "re-resizable": "^4.7.1", + "re-resizable": "^5.0.1", "react-click-outside": "^3.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", @@ -9375,6 +9375,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-memoize": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.1.tgz", + "integrity": "sha512-xdmw296PCL01tMOXx9mdJSmWY29jQgxyuZdq0rEHMu+Tpe1eOEtCycoG6chzlcrWsNgpZP7oL8RiQr7+G6Bl6g==" + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -18366,9 +18371,12 @@ } }, "re-resizable": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.7.1.tgz", - "integrity": "sha512-pLJkPbZCe+3ml+9Q15z+R69qYZDsluj0KwrdFb8kSNaqDzYAveDUblf7voHH9hNTdKIiIvP8iIdGFFKSgffVaQ==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-5.0.1.tgz", + "integrity": "sha512-Iy8v5li7bhNBDxCN1DbA4l6G2Hk8NCZtcExoI1D+5pfvKyQcH8LH2P5h3DGoEfHhs0uyyRC1Qx8bHBomfrmxgA==", + "requires": { + "fast-memoize": "^2.5.1" + } }, "react": { "version": "16.8.4", diff --git a/packages/components/package.json b/packages/components/package.json index 99c44a249b7e42..f82a996cda1e65 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,7 +40,7 @@ "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", - "re-resizable": "^4.7.1", + "re-resizable": "^5.0.1", "react-click-outside": "^3.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", diff --git a/packages/components/src/resizable-box/index.js b/packages/components/src/resizable-box/index.js index 13e98a7f4da474..fc63dd126a24e8 100644 --- a/packages/components/src/resizable-box/index.js +++ b/packages/components/src/resizable-box/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import ReResizableBox from 're-resizable'; +import { Resizable } from 're-resizable'; function ResizableBox( { className, ...props } ) { // Removes the inline styles in the drag handles. @@ -20,7 +20,7 @@ function ResizableBox( { className, ...props } ) { const cornerHandleClassName = 'components-resizable-box__corner-handle'; return ( - <ReResizableBox + <Resizable className={ classnames( 'components-resizable-box__container', className, From 79d8dd90afa7a975d11a5236ccc9dddf7c58d946 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Thu, 27 Jun 2019 12:41:50 -0700 Subject: [PATCH 405/664] Fix link to InnerBlocks (#16320) --- .../developers/block-api/block-registration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index 7986be28bee4bc..9d78c4247e4ec4 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -520,7 +520,7 @@ transforms: { * **Type:** `Array` -Blocks are able to be inserted into blocks that use [`InnerBlocks`](/packages/block-editor/src/components/inner-blocks/README.md) as nested content. Sometimes it is useful to restrict a block so that it is only available as a nested block. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. +Blocks are able to be inserted into blocks that use [`InnerBlocks`](https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md) as nested content. Sometimes it is useful to restrict a block so that it is only available as a nested block. For example, you might want to allow an 'Add to Cart' block to only be available within a 'Product' block. Setting `parent` lets a block require that it is only available when nested within the specified blocks. From c7215a36f8217a6891a7eaedba0322650a7e7aa9 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 28 Jun 2019 08:21:18 +0200 Subject: [PATCH 406/664] Scope the data-block CSS selector. (#16207) --- packages/block-library/src/editor.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 784850095f97d2..7e8652b23a3bf2 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -51,7 +51,7 @@ // Provide every block with a default base margin. This margin provides a consistent spacing // between blocks in the editor. -[data-block] { +.editor-styles-wrapper [data-block] { margin-top: $default-block-margin; margin-bottom: $default-block-margin; } From f383dbdf8d6d67ce9bc5647285dadedefdb7639a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 28 Jun 2019 11:07:17 +0100 Subject: [PATCH 407/664] Deploy the Gutenberg Playground to the Github Pages of the repository (#16345) --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.travis.yml b/.travis.yml index 60a40cec454eb7..21efa77d6bd774 100644 --- a/.travis.yml +++ b/.travis.yml @@ -160,3 +160,16 @@ jobs: env: WP_VERSION=latest SWITCH_TO_PHP=5.2 script: - ./bin/run-wp-unit-tests.sh + +before_deploy: + - npm install + - npm run playground:build + +deploy: + provider: pages + skip_cleanup: true + github_token: $GITHUB_TOKEN + keep_history: true + local_dir: playground/dist + on: + branch: master From 6970dddc1a76027078cf7eead02d40c1b15d1a6e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 28 Jun 2019 11:21:38 +0100 Subject: [PATCH 408/664] Remove: experimental status from blockEditor.transformStyles (#16126) --- packages/block-editor/README.md | 13 +++++++++++++ packages/block-editor/src/utils/index.js | 5 +---- .../src/utils/transform-styles/index.js | 2 +- packages/block-library/src/html/edit.js | 4 ++-- packages/editor/src/components/provider/index.js | 4 ++-- packages/editor/src/index.js | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index ff1a8579a0b016..f279d2994600d3 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -387,6 +387,19 @@ _Type_ - `Object` +<a name="transformStyles" href="#transformStyles">#</a> **transformStyles** + +Applies a series of CSS rule transforms to wrap selectors inside a given class and/or rewrite URLs depending on the parameters passed. + +_Parameters_ + +- _styles_ `Array`: CSS rules. +- _wrapperClassName_ `string`: Wrapper Class Name. + +_Returns_ + +- `Array`: converted rules. + <a name="URLInput" href="#URLInput">#</a> **URLInput** _Related_ diff --git a/packages/block-editor/src/utils/index.js b/packages/block-editor/src/utils/index.js index cb35b8d3420d15..95241bd4fa7673 100644 --- a/packages/block-editor/src/utils/index.js +++ b/packages/block-editor/src/utils/index.js @@ -1,4 +1 @@ -/** - * Internal dependencies - */ -export { default as __experimentalTransformStyles } from './transform-styles'; +export { default as transformStyles } from './transform-styles'; diff --git a/packages/block-editor/src/utils/transform-styles/index.js b/packages/block-editor/src/utils/transform-styles/index.js index 5d98c856ed1160..b0f1d66fe55111 100644 --- a/packages/block-editor/src/utils/transform-styles/index.js +++ b/packages/block-editor/src/utils/transform-styles/index.js @@ -16,7 +16,7 @@ import urlRewrite from './transforms/url-rewrite'; import wrap from './transforms/wrap'; /** - * Convert css rules. + * Applies a series of CSS rule transforms to wrap selectors inside a given class and/or rewrite URLs depending on the parameters passed. * * @param {Array} styles CSS rules. * @param {string} wrapperClassName Wrapper Class Name. diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 4ddc146ccf3d66..4f1ab45a073dfe 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -6,7 +6,7 @@ import { Component } from '@wordpress/element'; import { BlockControls, PlainText, - __experimentalTransformStyles, + transformStyles, } from '@wordpress/block-editor'; import { Disabled, SandBox } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; @@ -38,7 +38,7 @@ class HTMLEdit extends Component { this.setState( { styles: [ defaultStyles, - ...__experimentalTransformStyles( styles ), + ...transformStyles( styles ), ] } ); } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 0e959f80c7b866..cc9533a0a79a7b 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -11,7 +11,7 @@ import { compose } from '@wordpress/compose'; import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEditorProvider, __experimentalTransformStyles } from '@wordpress/block-editor'; +import { BlockEditorProvider, transformStyles } from '@wordpress/block-editor'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; @@ -112,7 +112,7 @@ class EditorProvider extends Component { return; } - const updatedStyles = __experimentalTransformStyles( this.props.settings.styles, '.editor-styles-wrapper' ); + const updatedStyles = transformStyles( this.props.settings.styles, '.editor-styles-wrapper' ); map( updatedStyles, ( updatedCSS ) => { if ( updatedCSS ) { diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index eb54a38859b135..67de1923a013d9 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -22,4 +22,4 @@ export { storeConfig } from './store'; /* * Backward compatibility */ -export { __experimentalTransformStyles as transformStyles } from '@wordpress/block-editor'; +export { transformStyles } from '@wordpress/block-editor'; From 95e91cef5c04bb5c03f400b7fca79a0cab1aa4e7 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 28 Jun 2019 12:04:28 +0100 Subject: [PATCH 409/664] Fix the Gutenberg Playground public URL --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 21efa77d6bd774..9e42ed6163f3a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -163,7 +163,7 @@ jobs: before_deploy: - npm install - - npm run playground:build + - npm run playground:build -- --public-url '/gutenberg' deploy: provider: pages From ffa4a27077e426e652abdf8390497a16dd9ad62c Mon Sep 17 00:00:00 2001 From: Jonny Harris <spacedmonkey@users.noreply.github.com> Date: Fri, 28 Jun 2019 16:44:54 +0200 Subject: [PATCH 410/664] Add error message from api request. (#15657) * Add error message from api request. * Use sprintf for error message. * Add comment * Fix tests for new error message * More linting fixes * Fix phpunit tests * Improve the tests, to reflect message in error. * Fix text of message in e2e. * Update packages/editor/src/store/utils/notice-builder.js Change comment. Co-Authored-By: Andrew Duthie <andrew@andrewduthie.com> --- .../e2e-tests/specs/change-detection.test.js | 2 +- packages/editor/src/store/test/actions.js | 2 +- .../editor/src/store/utils/notice-builder.js | 17 +++++++++++------ .../src/store/utils/test/notice-builder.js | 10 +++++----- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/e2e-tests/specs/change-detection.test.js b/packages/e2e-tests/specs/change-detection.test.js index 7c9dab21d4d794..30895ce5590dea 100644 --- a/packages/e2e-tests/specs/change-detection.test.js +++ b/packages/e2e-tests/specs/change-detection.test.js @@ -199,7 +199,7 @@ describe( 'Change detection', () => { // Ensure save update fails and presents button. page.waitForXPath( - '//*[contains(@class, "components-notice") and contains(@class, "is-error")]/*[text()="Updating failed"]' + '//*[contains(@class, "components-notice") and contains(@class, "is-error")]/*[text()="Updating failed. Error message: The response is not a valid JSON response."]' ), page.waitForSelector( '.editor-post-save-draft' ), ] ); diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index ab698ea82a7315..dd78100a4385db 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -337,7 +337,7 @@ describe( 'Post generator actions', () => { dispatch( 'core/notices', 'createErrorNotice', - ...[ 'Updating failed', { id: 'SAVE_POST_NOTICE_ID' } ] + ...[ 'Updating failed.', { id: 'SAVE_POST_NOTICE_ID' } ] ) ); }, diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index 9732cff6fa7cfd..eb8172e9801dda 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -97,14 +97,19 @@ export function getNotificationArgumentsForSaveFail( data ) { // If the post was being published, we show the corresponding publish error message // Unless we publish an "updating failed" message const messages = { - publish: __( 'Publishing failed' ), - private: __( 'Publishing failed' ), - future: __( 'Scheduling failed' ), + publish: __( 'Publishing failed.' ), + private: __( 'Publishing failed.' ), + future: __( 'Scheduling failed.' ), }; - const noticeMessage = ! isPublished && publishStatus.indexOf( edits.status ) !== -1 ? + let noticeMessage = ! isPublished && publishStatus.indexOf( edits.status ) !== -1 ? messages[ edits.status ] : - __( 'Updating failed' ); + __( 'Updating failed.' ); + // Check if message string contains HTML. Notice text is currently only + // supported as plaintext, and stripping the tags may muddle the meaning. + if ( error.message && ! ( /<\/?[^>]*>/.test( error.message ) ) ) { + noticeMessage = sprintf( __( '%1$s Error message: %2$s' ), noticeMessage, error.message ); + } return [ noticeMessage, { id: SAVE_POST_NOTICE_ID, } ]; diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js index e67215113c1f19..3e6abab360bca7 100644 --- a/packages/editor/src/store/utils/test/notice-builder.js +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -93,7 +93,7 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { } ); } ); describe( 'getNotificationArgumentsForSaveFail()', () => { - const error = { code: '42' }; + const error = { code: '42', message: 'Something went wrong.' }; const post = { status: 'publish' }; const edits = { status: 'publish' }; const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID }; @@ -108,25 +108,25 @@ describe( 'getNotificationArgumentsForSaveFail()', () => { 'when post is not published and edits is published', '', [ 'draft', 'publish' ], - [ 'Publishing failed', defaultExpectedAction ], + [ 'Publishing failed. Error message: Something went wrong.', defaultExpectedAction ], ], [ 'when post is published and edits is privately published', '', [ 'draft', 'private' ], - [ 'Publishing failed', defaultExpectedAction ], + [ 'Publishing failed. Error message: Something went wrong.', defaultExpectedAction ], ], [ 'when post is published and edits is scheduled to be published', '', [ 'draft', 'future' ], - [ 'Scheduling failed', defaultExpectedAction ], + [ 'Scheduling failed. Error message: Something went wrong.', defaultExpectedAction ], ], [ 'when post is published and edits is published', '', [ 'publish', 'publish' ], - [ 'Updating failed', defaultExpectedAction ], + [ 'Updating failed. Error message: Something went wrong.', defaultExpectedAction ], ], ].forEach( ( [ description, From 95c515af269ee6047b289ff71ecb19ff4a026e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 28 Jun 2019 18:04:11 +0200 Subject: [PATCH 411/664] Change placeholder instruction for consistency (#16339) --- packages/block-library/src/image/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index faabe881a04c94..b22d63da20cb9f 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -415,7 +415,7 @@ class ImageEdit extends Component { const src = isExternal ? url : undefined; const labels = { title: ! url ? __( 'Image' ) : __( 'Edit image' ), - instructions: __( 'Upload an image, pick one from your media library, or add one with a URL.' ), + instructions: __( 'Upload an image file, pick one from your media library, or add one with a URL.' ), }; const mediaPreview = ( !! url && <img alt={ __( 'Edit image' ) } From a53a38bd4b3d2d9b28a727f66c6b0bf37c287dd2 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Fri, 28 Jun 2019 19:24:28 +0200 Subject: [PATCH 412/664] [RNMobile] Native mobile release v1.8.0 (#16344) * Revert "Re-enable Video block on Android after Android X migration (#16215)" This reverts commit 284bb1781457941eb38a2b080d926ffdcc416e01. * Don't forcefully cancel media upload on img/video unmount Manually reverts d554ba3a11d500141bcb05303426def8b432471c --- .../block-list/block-mobile-toolbar.native.js | 7 ++----- packages/block-library/src/image/edit.native.js | 7 ++++--- packages/block-library/src/video/edit.native.js | 7 ++++--- packages/edit-post/src/index.native.js | 10 ++++++++++ 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js index e5ac71ee27ed34..96395c41c835f2 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js +++ b/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js @@ -55,15 +55,12 @@ export default compose( order: getBlockIndex( clientId ), }; } ), - withDispatch( ( dispatch, { clientId, rootClientId, onDelete } ) => { + withDispatch( ( dispatch, { clientId, rootClientId } ) => { const { removeBlock } = dispatch( 'core/block-editor' ); return { - onDelete() { + onDelete: () => { Keyboard.dismiss(); removeBlock( clientId, rootClientId ); - if ( onDelete ) { - onDelete( clientId ); - } }, }; } ), diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index d7ade08d4f5c94..cb0cc5a9302630 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -8,7 +8,6 @@ import { mediaUploadSync, requestImageFailedRetryDialog, requestImageUploadCancelDialog, - requestImageUploadCancel, } from 'react-native-gutenberg-bridge'; import { isEmpty } from 'lodash'; @@ -31,6 +30,7 @@ import { } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; +import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -92,8 +92,9 @@ class ImageEdit extends React.Component { } componentWillUnmount() { - if ( this.state.isUploadInProgress ) { - requestImageUploadCancel( this.props.attributes.id ); + // this action will only exist if the user pressed the trash button on the block holder + if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { + doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); } } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index c7d041cd725552..1b7fc518313440 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -11,7 +11,6 @@ import { mediaUploadSync, requestImageFailedRetryDialog, requestImageUploadCancelDialog, - requestImageUploadCancel, } from 'react-native-gutenberg-bridge'; /** @@ -32,6 +31,7 @@ import { } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; +import { doAction, hasAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -70,8 +70,9 @@ class VideoEdit extends React.Component { } componentWillUnmount() { - if ( this.state.isUploadInProgress ) { - requestImageUploadCancel( this.props.attributes.id ); + // this action will only exist if the user pressed the trash button on the block holder + if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { + doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); } } diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 1ffdcb13477335..1240c84ccb7e2f 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { Platform } from 'react-native'; + /** * WordPress dependencies */ @@ -31,6 +36,11 @@ export function initializeEditor() { // eslint-disable-next-line no-undef if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); + + // Disable Video block except for iOS for now. + if ( Platform.OS !== 'ios' ) { + unregisterBlockType( 'core/video' ); + } } blocksRegistered = true; From 0a3c5c094bea378c48d8b57fc46bb4dbcd4379d1 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 28 Jun 2019 15:54:15 -0400 Subject: [PATCH 413/664] i18n: Bump Tannin dependency to 1.1.0 (#16337) --- package-lock.json | 40 +++++++++++++++++++------------------- packages/i18n/package.json | 2 +- packages/i18n/src/index.js | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77e3b65940e424..70048538460260 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2910,31 +2910,31 @@ } }, "@tannin/compile": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.1.tgz", - "integrity": "sha512-ymd9icvnkQin8UG4eRU3+xBc7gqTn/Kv5+EMY3ALWVwIl6j/7McWbCkxB8MgU40UaHJk8kLCk06wiKszXLdXWQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.3.tgz", + "integrity": "sha512-OkPHvaM/hIHdSco3+ZO1hzkOtfEddn5a0veWft2aDLvKnbdj9VusiLKNdEE9by3hCZIIcb9aWF+iBorhfrQOfw==", "requires": { - "@tannin/evaluate": "^1.0.0", - "@tannin/postfix": "^1.0.0" + "@tannin/evaluate": "^1.1.1", + "@tannin/postfix": "^1.0.2" } }, "@tannin/evaluate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.0.0.tgz", - "integrity": "sha512-gO7YbJsD8sj5/nqUbFZv71Meu2++D9n4DZov/cWwp3YJbBwKShPlWwwlXr/0vz4vuxm/gys+3NiGbZkmhlXf0Q==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.1.1.tgz", + "integrity": "sha512-ALuSZHjrLHGnw0WxsHDHde74FJ2WW0Ck4rg3QBxFBCmxd6Wsac+e0HXfJ++Qion15LIOCmFhyVpWzawMgeBA8Q==" }, "@tannin/plural-forms": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.0.1.tgz", - "integrity": "sha512-SXutT+XLbMOECvmWDBSqIOHhS5hzWG9875HCFGKYgp8ghGPrJ4HZ325Xc0hsRThdjgrWMEQixlbpWl4SXOQTig==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.0.3.tgz", + "integrity": "sha512-IUr9+FiCnzCiB9aRio3FVNR8TNL9SmX2zkV6tmfWWwSclX4uTCykoGsDhTGKK+sZnMrdPCTmb/OxbtGNdVyV4g==", "requires": { - "@tannin/compile": "^1.0.0" + "@tannin/compile": "^1.0.3" } }, "@tannin/postfix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.0.tgz", - "integrity": "sha512-59/mWwU7sXHfoU2kI3RcWRki2Jjbz5nEVJNBN4MUyIhPjXTebAcZqgsQACvlk+sjKVOTMEMHcrFrKQbaxz/1Dw==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz", + "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g==" }, "@types/babel__core": { "version": "7.1.1", @@ -3649,7 +3649,7 @@ "lodash": "^4.17.11", "memize": "^1.0.5", "sprintf-js": "^1.1.1", - "tannin": "^1.0.1" + "tannin": "^1.1.0" } }, "@wordpress/is-shallow-equal": { @@ -21154,11 +21154,11 @@ } }, "tannin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.0.1.tgz", - "integrity": "sha512-dDtnwHQ63bS/Gz0ZLY+E+JCdRoTZkmoKDoC64y3hzAD2X2qrp8jSuWNUjtiYHA48mtj4Ens9xl4knAOm1t+rfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tannin/-/tannin-1.1.0.tgz", + "integrity": "sha512-LxhcXqpMHEOVeVKmuG5aCPPsTXFlO373vrWkqN7FSJBVLS6lFOAg8ZGzIyGhrOf7Ho3xB4jdGedY1gi/8J1FCA==", "requires": { - "@tannin/plural-forms": "^1.0.0" + "@tannin/plural-forms": "^1.0.3" } }, "tapable": { diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 721061946e221b..ccd1c1ee78a4fb 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -29,7 +29,7 @@ "lodash": "^4.17.11", "memize": "^1.0.5", "sprintf-js": "^1.1.1", - "tannin": "^1.0.1" + "tannin": "^1.1.0" }, "publishConfig": { "access": "public" diff --git a/packages/i18n/src/index.js b/packages/i18n/src/index.js index 50869f49efd395..dc179517312014 100644 --- a/packages/i18n/src/index.js +++ b/packages/i18n/src/index.js @@ -13,7 +13,7 @@ import sprintfjs from 'sprintf-js'; */ const DEFAULT_LOCALE_DATA = { '': { - plural_forms: 'plural=(n!=1)', + plural_forms: ( n ) => n === 1 ? 0 : 1, }, }; From b0ed6f564f5abc33b2b3e0a84217c86f56333f12 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Sat, 29 Jun 2019 09:34:39 +0100 Subject: [PATCH 414/664] Add logic to prevent ungroup option when the group block has no inner blocks (#16332) --- packages/e2e-tests/specs/block-grouping.test.js | 7 +++++++ .../components/convert-to-group-buttons/convert-button.js | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/block-grouping.test.js index 57300ec2b9de66..a1b25f055edde9 100644 --- a/packages/e2e-tests/specs/block-grouping.test.js +++ b/packages/e2e-tests/specs/block-grouping.test.js @@ -102,6 +102,13 @@ describe( 'Block Grouping', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'does not allow ungrouping a group block that has no children', async () => { + await insertBlock( 'Group' ); + await clickBlockToolbarButton( 'More options' ); + const ungroupButtons = await page.$x( '//button[text()="Ungroup"]' ); + expect( ungroupButtons ).toHaveLength( 0 ); + } ); } ); describe( 'Container Block availability', () => { diff --git a/packages/editor/src/components/convert-to-group-buttons/convert-button.js b/packages/editor/src/components/convert-to-group-buttons/convert-button.js index 6a7299ed259c98..2aa2fc9dcd6b19 100644 --- a/packages/editor/src/components/convert-to-group-buttons/convert-button.js +++ b/packages/editor/src/components/convert-to-group-buttons/convert-button.js @@ -72,8 +72,8 @@ export default compose( [ ! isSingleContainerBlock ); - // Do we have a single Group Block selected? - const isUngroupable = isSingleContainerBlock; + // Do we have a single Group Block selected and does that group have inner blocks? + const isUngroupable = isSingleContainerBlock && !! blocksSelection[ 0 ].innerBlocks.length; return { isGroupable, From 462d1a58f3e249252800c0c8e0df56794f4edb7c Mon Sep 17 00:00:00 2001 From: jodamo5 <josh@duoplus.co.nz> Date: Sun, 30 Jun 2019 01:07:04 +1200 Subject: [PATCH 415/664] Expand explanation of uses for dynamic blocks (#16228) * Expand explanation of uses for dynamic blocks Gives clearer explanation of dynamic blocks uses. This update especially explains the need to use dynamic blocks if developers want their block updates to be immediately reflected on the front end of the site, instead of Gutenberg's validation process applying. * Apply suggestions to documentation All suggestions have been accepted. Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> * Update docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md Final suggestion accepted. Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> --- .../block-tutorial/creating-dynamic-blocks.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index 73448eadf0d023..f21c3b3f09988e 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -1,6 +1,17 @@ # Creating dynamic blocks -Dynamic blocks are blocks that can change their content even if the post is not saved. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published. +Dynamic blocks are blocks that build their structure and content on the fly when the block is rendered on the front end. + +There are two primary uses for dynamic blocks: + +1. Blocks where content should change even if a post has not been updated. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published. +2. Blocks where updates to the code (HTML, CSS, JS) should be immediately shown on the front end of the website. For example, if you update the HTML structure of a block by adding a new class, adding a div, or changing the layout in any other way, if you want these changes to be applied immediately on all occurrences of that block across the site, a dynamic block should be used. (If a dynamic block is not used then when block code is updated Guterberg's [validation process](https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#validation) applies, causing users to see the validation message, "This block appears to have been modified externally"). + +For many dynamic blocks, the `save` callback function should be returned as `null`, which tells the editor to save only the [block attributes](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/) to the database. These attributes are then passed into the server-side rendering callback, so you can decide how to display the block on the front end of your site. When you return `null`, the editor will skip the block markup validation process, avoiding issues with frequently-changing markup. + +You can also save an HTML representation of the block. If you provide a server-side rendering callback, this HTML will be replaced with the output of your callback, but will be rendered if your block is deactivated or your render callback is removed. + +Block attributes can be used for any content or setting you want to save for that block. In the first example above, with the latest posts block, the number of latest posts you want to show could be saved as an attribute. Or in the second example, attributes can be used for each piece of content you want to show in the front end - such as heading text, paragraph text, an image, a URL, etc. The following code example shows how to create a dynamic block that shows only the last post as a link. From 08cda3f0dd23595e685d67209e4b4f811d2f824d Mon Sep 17 00:00:00 2001 From: Nicola Heald <nicola@notnowlewis.com> Date: Mon, 1 Jul 2019 09:21:47 +0100 Subject: [PATCH 416/664] Remove notnownikki from code owners (#16353) --- .github/CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 22a1f4852b1434..4391fa3fd2b83f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ # Documentation -/docs @youknowriad @chrisvanpatten @ajitbohra @notnownikki -/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra @notnownikki -/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra @notnownikki +/docs @youknowriad @chrisvanpatten @ajitbohra +/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra +/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra # Data /packages/api-fetch @youknowriad @aduth @nerrad @mmtr @@ -10,7 +10,7 @@ /packages/redux-routine @youknowriad @aduth @nerrad # Blocks -/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan @notnownikki +/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan # Editor /packages/annotations @youknowriad @aduth @atimmer @ellatrix @@ -29,7 +29,7 @@ # Tooling /bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/docs/tool @youknowriad @gziolo @chrisvanpatten @ajitbohra @nosolosw @notnownikki +/docs/tool @youknowriad @gziolo @chrisvanpatten @ajitbohra @nosolosw /packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw From 496a02c223d730bdeaffc8d068814fd9e03f8555 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 1 Jul 2019 13:55:18 -0400 Subject: [PATCH 417/664] Allow inner block template options to wrap (#16371) * Allow inner block template options to wrap on mobile. * Remove nowrap rule. --- .../src/components/inner-blocks/style.scss | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index 0f8576fd5ecf34..e7bd479e04e02e 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -39,19 +39,23 @@ .block-editor-inner-blocks__template-picker-options.block-editor-inner-blocks__template-picker-options { display: flex; + justify-content: center; flex-direction: row; - flex-wrap: nowrap; + flex-wrap: wrap; width: 100%; - margin: $grid-size-large 0; + margin: $grid-size-small 0; list-style: none; > li { list-style: none; - flex-basis: 100%; + margin: $grid-size; flex-shrink: 1; - margin: 0 $grid-size; max-width: 100px; } + + .block-editor-inner-blocks__template-picker-option { + padding: $grid-size; + } } .block-editor-inner-blocks__template-picker-option { From 672bd9e06de74fec0fcddb81d083e792f800d42e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 1 Jul 2019 16:44:21 -0400 Subject: [PATCH 418/664] isShallowEqual: Account for implicit undefined of second object (#16329) * isShallowEqual: Remove redundant inverse test case Previous test will account for this by calling `isShallowEqual` in both arguments arrangements * isShallowEqual: Account for implicit undefined of second object * is-shallow-equal: Document implicit undefined distinction * is-shallow-equal: Add CHANGELOG entry for implicit undefined fix --- packages/is-shallow-equal/CHANGELOG.md | 6 ++ packages/is-shallow-equal/README.md | 88 ++++++++++++------------- packages/is-shallow-equal/objects.js | 14 +++- packages/is-shallow-equal/test/index.js | 6 +- 4 files changed, 65 insertions(+), 49 deletions(-) diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index 76e2a7317c2245..df25b00178ac17 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Bug Fixes + +- Resolved an issue where an explicit `undefined` value in the first object may wrongly report as being shallow equal when the two objects are otherwise of equal length. ([#16329](https://github.com/WordPress/gutenberg/pull/16329)) + ## 1.2.0 (2019-03-06) ### New Feature diff --git a/packages/is-shallow-equal/README.md b/packages/is-shallow-equal/README.md index 3c53ed3261e445..04421cfa1408f2 100644 --- a/packages/is-shallow-equal/README.md +++ b/packages/is-shallow-equal/README.md @@ -68,56 +68,56 @@ In particular, it should… ## Benchmarks -The following results were produced under Node v8.11.1 (LTS) on a MacBook Pro (Late 2016) 2.9 GHz Intel Core i7. - ->`@wordpress/is-shallow-equal (type specific) (object, equal) x 4,902,162 ops/sec ±0.40% (89 runs sampled)` ->`@wordpress/is-shallow-equal (type specific) (object, same) x 558,234,287 ops/sec ±0.28% (92 runs sampled)` ->`@wordpress/is-shallow-equal (type specific) (object, unequal) x 5,062,890 ops/sec ±0.71% (90 runs sampled)` ->`@wordpress/is-shallow-equal (type specific) (array, equal) x 70,419,519 ops/sec ±0.56% (86 runs sampled)` ->`@wordpress/is-shallow-equal (type specific) (array, same) x 561,159,444 ops/sec ±0.33% (91 runs sampled)` ->`@wordpress/is-shallow-equal (type specific) (array, unequal) x 37,299,061 ops/sec ±0.89% (86 runs sampled)` +The following results were produced under Node v10.15.3 (LTS) on a MacBook Pro (Late 2016) 2.9 GHz Intel Core i7. + +>`@wordpress/is-shallow-equal (type specific) (object, equal) x 4,519,009 ops/sec ±1.09% (90 runs sampled)` +>`@wordpress/is-shallow-equal (type specific) (object, same) x 795,527,700 ops/sec ±0.24% (93 runs sampled)` +>`@wordpress/is-shallow-equal (type specific) (object, unequal) x 4,841,640 ops/sec ±0.94% (93 runs sampled)` +>`@wordpress/is-shallow-equal (type specific) (array, equal) x 106,393,795 ops/sec ±0.16% (94 runs sampled)` +>`@wordpress/is-shallow-equal (type specific) (array, same) x 800,741,511 ops/sec ±0.22% (95 runs sampled)` +>`@wordpress/is-shallow-equal (type specific) (array, unequal) x 49,178,977 ops/sec ±1.99% (82 runs sampled)` > ->`@wordpress/is-shallow-equal (object, equal) x 4,449,938 ops/sec ±0.34% (91 runs sampled)` ->`@wordpress/is-shallow-equal (object, same) x 516,101,448 ops/sec ±0.64% (90 runs sampled)` ->`@wordpress/is-shallow-equal (object, unequal) x 4,925,231 ops/sec ±0.28% (91 runs sampled)` ->`@wordpress/is-shallow-equal (array, equal) x 30,432,490 ops/sec ±0.80% (86 runs sampled)` ->`@wordpress/is-shallow-equal (array, same) x 505,206,883 ops/sec ±0.37% (93 runs sampled)` ->`@wordpress/is-shallow-equal (array, unequal) x 33,590,955 ops/sec ±0.96% (86 runs sampled)` +>`@wordpress/is-shallow-equal (object, equal) x 4,449,367 ops/sec ±0.31% (91 runs sampled)` +>`@wordpress/is-shallow-equal (object, same) x 796,677,179 ops/sec ±0.23% (94 runs sampled)` +>`@wordpress/is-shallow-equal (object, unequal) x 4,989,529 ops/sec ±0.30% (91 runs sampled)` +>`@wordpress/is-shallow-equal (array, equal) x 44,840,546 ops/sec ±1.18% (89 runs sampled)` +>`@wordpress/is-shallow-equal (array, same) x 794,344,723 ops/sec ±0.24% (91 runs sampled)` +>`@wordpress/is-shallow-equal (array, unequal) x 49,860,115 ops/sec ±1.73% (85 runs sampled)` > ->`shallowequal (object, equal) x 3,407,788 ops/sec ±0.46% (93 runs sampled)` ->`shallowequal (object, same) x 494,715,603 ops/sec ±0.42% (91 runs sampled)` ->`shallowequal (object, unequal) x 3,575,393 ops/sec ±0.54% (93 runs sampled)` ->`shallowequal (array, equal) x 1,530,453 ops/sec ±0.32% (92 runs sampled)` ->`shallowequal (array, same) x 489,793,575 ops/sec ±0.60% (90 runs sampled)` ->`shallowequal (array, unequal) x 1,534,574 ops/sec ±0.32% (90 runs sampled)` +>`shallowequal (object, equal) x 3,702,126 ops/sec ±0.87% (92 runs sampled)` +>`shallowequal (object, same) x 796,649,597 ops/sec ±0.21% (92 runs sampled)` +>`shallowequal (object, unequal) x 4,027,885 ops/sec ±0.31% (96 runs sampled)` +>`shallowequal (array, equal) x 1,684,977 ops/sec ±0.37% (94 runs sampled)` +>`shallowequal (array, same) x 794,287,091 ops/sec ±0.26% (91 runs sampled)` +>`shallowequal (array, unequal) x 1,738,554 ops/sec ±0.29% (91 runs sampled)` > ->`shallow-equal (type specific) (object, equal) x 4,708,043 ops/sec ±0.30% (92 runs sampled)` ->`shallow-equal (type specific) (object, same) x 537,831,873 ops/sec ±0.42% (88 runs sampled)` ->`shallow-equal (type specific) (object, unequal) x 4,859,249 ops/sec ±0.28% (90 runs sampled)` ->`shallow-equal (type specific) (array, equal) x 63,985,372 ops/sec ±0.54% (91 runs sampled)` ->`shallow-equal (type specific) (array, same) x 540,675,335 ops/sec ±0.43% (89 runs sampled)` ->`shallow-equal (type specific) (array, unequal) x 34,613,490 ops/sec ±0.81% (90 runs sampled)` +>`shallow-equal (type specific) (object, equal) x 4,669,656 ops/sec ±0.34% (92 runs sampled)` +>`shallow-equal (type specific) (object, same) x 799,610,214 ops/sec ±0.20% (95 runs sampled)` +>`shallow-equal (type specific) (object, unequal) x 4,908,591 ops/sec ±0.49% (93 runs sampled)` +>`shallow-equal (type specific) (array, equal) x 104,711,254 ops/sec ±0.65% (91 runs sampled)` +>`shallow-equal (type specific) (array, same) x 798,454,281 ops/sec ±0.29% (94 runs sampled)` +>`shallow-equal (type specific) (array, unequal) x 48,764,338 ops/sec ±1.48% (84 runs sampled)` > ->`is-equal-shallow (object, equal) x 2,798,059 ops/sec ±0.42% (93 runs sampled)` ->`is-equal-shallow (object, same) x 2,844,934 ops/sec ±0.39% (93 runs sampled)` ->`is-equal-shallow (object, unequal) x 3,223,288 ops/sec ±0.57% (92 runs sampled)` ->`is-equal-shallow (array, equal) x 1,060,093 ops/sec ±0.32% (93 runs sampled)` ->`is-equal-shallow (array, same) x 1,058,977 ops/sec ±0.31% (94 runs sampled)` ->`is-equal-shallow (array, unequal) x 1,697,517 ops/sec ±0.28% (91 runs sampled)` +>`is-equal-shallow (object, equal) x 5,068,750 ops/sec ±0.28% (92 runs sampled)` +>`is-equal-shallow (object, same) x 17,231,997 ops/sec ±0.42% (92 runs sampled)` +>`is-equal-shallow (object, unequal) x 5,524,878 ops/sec ±0.41% (92 runs sampled)` +>`is-equal-shallow (array, equal) x 1,067,063 ops/sec ±0.40% (92 runs sampled)` +>`is-equal-shallow (array, same) x 1,074,356 ops/sec ±0.20% (94 runs sampled)` +>`is-equal-shallow (array, unequal) x 1,758,859 ops/sec ±0.44% (92 runs sampled)` > ->`shallow-equals (object, equal) x 4,457,325 ops/sec ±0.40% (92 runs sampled)` ->`shallow-equals (object, same) x 4,509,250 ops/sec ±0.48% (92 runs sampled)` ->`shallow-equals (object, unequal) x 4,856,327 ops/sec ±0.41% (94 runs sampled)` ->`shallow-equals (array, equal) x 44,915,371 ops/sec ±2.18% (79 runs sampled)` ->`shallow-equals (array, same) x 38,514,418 ops/sec ±1.25% (83 runs sampled)` ->`shallow-equals (array, unequal) x 24,319,893 ops/sec ±0.96% (84 runs sampled)` +>`shallow-equals (object, equal) x 8,380,550 ops/sec ±0.31% (90 runs sampled)` +>`shallow-equals (object, same) x 27,583,073 ops/sec ±0.60% (91 runs sampled)` +>`shallow-equals (object, unequal) x 8,954,268 ops/sec ±0.71% (92 runs sampled)` +>`shallow-equals (array, equal) x 104,437,640 ops/sec ±0.22% (96 runs sampled)` +>`shallow-equals (array, same) x 141,850,542 ops/sec ±0.25% (93 runs sampled)` +>`shallow-equals (array, unequal) x 47,964,211 ops/sec ±1.51% (84 runs sampled)` > ->`fbjs/lib/shallowEqual (object, equal) x 3,388,692 ops/sec ±0.72% (92 runs sampled)` ->`fbjs/lib/shallowEqual (object, same) x 139,559,732 ops/sec ±4.45% (32 runs sampled)` ->`fbjs/lib/shallowEqual (object, unequal) x 3,480,571 ops/sec ±0.51% (90 runs sampled)` ->`fbjs/lib/shallowEqual (array, equal) x 1,517,044 ops/sec ±0.42% (91 runs sampled)` ->`fbjs/lib/shallowEqual (array, same) x 134,032,009 ops/sec ±2.82% (46 runs sampled)` ->`fbjs/lib/shallowEqual (array, unequal) x 1,532,376 ops/sec ±0.41% (91 runs sampled)` +>`fbjs/lib/shallowEqual (object, equal) x 3,366,709 ops/sec ±0.35% (93 runs sampled)` +>`fbjs/lib/shallowEqual (object, same) x 794,825,194 ops/sec ±0.24% (94 runs sampled)` +>`fbjs/lib/shallowEqual (object, unequal) x 3,612,268 ops/sec ±0.37% (94 runs sampled)` +>`fbjs/lib/shallowEqual (array, equal) x 1,613,800 ops/sec ±0.23% (90 runs sampled)` +>`fbjs/lib/shallowEqual (array, same) x 794,861,384 ops/sec ±0.24% (93 runs sampled)` +>`fbjs/lib/shallowEqual (array, unequal) x 1,648,398 ops/sec ±0.77% (92 runs sampled)` You can run the benchmarks yourselves by cloning the repository, installing dependencies, and running the `benchmark/index.js` script: diff --git a/packages/is-shallow-equal/objects.js b/packages/is-shallow-equal/objects.js index 12df62d2a04075..ebe1105b196156 100644 --- a/packages/is-shallow-equal/objects.js +++ b/packages/is-shallow-equal/objects.js @@ -11,7 +11,7 @@ var keys = Object.keys; * @return {boolean} Whether the two objects are shallow equal. */ function isShallowEqualObjects( a, b ) { - var aKeys, bKeys, i, key; + var aKeys, bKeys, i, key, aValue; if ( a === b ) { return true; @@ -28,7 +28,17 @@ function isShallowEqualObjects( a, b ) { while ( i < aKeys.length ) { key = aKeys[ i ]; - if ( a[ key ] !== b[ key ] ) { + aValue = a[ key ]; + + if ( + // In iterating only the keys of the first object after verifying + // equal lengths, account for the case that an explicit `undefined` + // value in the first is implicitly undefined in the second. + // + // Example: isShallowEqualObjects( { a: undefined }, { b: 5 } ) + ( aValue === undefined && ! b.hasOwnProperty( key ) ) || + aValue !== b[ key ] + ) { return false; } diff --git a/packages/is-shallow-equal/test/index.js b/packages/is-shallow-equal/test/index.js index 8a7e80cf02156d..3ec86ed3529287 100644 --- a/packages/is-shallow-equal/test/index.js +++ b/packages/is-shallow-equal/test/index.js @@ -46,9 +46,9 @@ describe( 'isShallowEqual', () => { expect( isShallowEqual( b, a ) ).toBe( false ); } ); - it( 'returns false if b object has different key than a', () => { - const a = { foo: 1, baz: 2 }; - const b = { foo: 1, bar: 2 }; + it( 'returns false if a object has undefined key not in b', () => { + const a = { foo: undefined }; + const b = { bar: 2 }; expect( isShallowEqual( a, b ) ).toBe( false ); expect( isShallowEqual( b, a ) ).toBe( false ); From a84e9410a0cd46185a02bbde75e9b43e32e4c854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 2 Jul 2019 09:37:57 +0200 Subject: [PATCH 419/664] PHPCS: Use strict comparisons (#16381) * Use strict comparisons * Add StrictInArray --- lib/client-assets.php | 4 ++-- packages/block-library/src/latest-posts/index.php | 4 ++-- packages/block-library/src/rss/index.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 5e4d2651bb603d..724cf26f9c4023 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -628,7 +628,7 @@ function gutenberg_extend_block_editor_preload_paths( $preload_paths, $post ) { $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; $autosaves_path = sprintf( '/wp/v2/%s/%d/autosaves?context=edit', $rest_base, $post->ID ); - if ( ! in_array( $autosaves_path, $preload_paths ) ) { + if ( ! in_array( $autosaves_path, $preload_paths, true ) ) { $preload_paths[] = $autosaves_path; } } @@ -644,7 +644,7 @@ function gutenberg_extend_block_editor_preload_paths( $preload_paths, $post ) { */ $blocks_path = array( '/wp/v2/blocks', 'OPTIONS' ); - if ( ! in_array( $blocks_path, $preload_paths ) ) { + if ( ! in_array( $blocks_path, $preload_paths, true ) ) { $preload_paths[] = $blocks_path; } diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index d90f692644bacb..de5ba210fb137e 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -51,7 +51,7 @@ function render_block_core_latest_posts( $attributes ) { } if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent'] - && isset( $attributes['displayPostContentRadio'] ) && 'excerpt' == $attributes['displayPostContentRadio'] ) { + && isset( $attributes['displayPostContentRadio'] ) && 'excerpt' === $attributes['displayPostContentRadio'] ) { $post_excerpt = $post->post_excerpt; if ( ! ( $post_excerpt ) ) { $post_excerpt = $post->post_content; @@ -77,7 +77,7 @@ function render_block_core_latest_posts( $attributes ) { } if ( isset( $attributes['displayPostContent'] ) && $attributes['displayPostContent'] - && isset( $attributes['displayPostContentRadio'] ) && 'full_post' == $attributes['displayPostContentRadio'] ) { + && isset( $attributes['displayPostContentRadio'] ) && 'full_post' === $attributes['displayPostContentRadio'] ) { $list_items_markup .= sprintf( '<div class="wp-block-latest-posts__post-full-content">%1$s</div>', wp_kses_post( html_entity_decode( $post->post_content, ENT_QUOTES, get_option( 'blog_charset' ) ) ) diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php index 1bf217be3a9b59..53879d5bb2716b 100644 --- a/packages/block-library/src/rss/index.php +++ b/packages/block-library/src/rss/index.php @@ -69,7 +69,7 @@ function render_block_core_rss( $attributes ) { $excerpt = esc_attr( wp_trim_words( $excerpt, $attributes['excerptLength'], ' [&hellip;]' ) ); // Change existing [...] to [&hellip;]. - if ( '[...]' == substr( $excerpt, -5 ) ) { + if ( '[...]' === substr( $excerpt, -5 ) ) { $excerpt = substr( $excerpt, 0, -5 ) . '[&hellip;]'; } From c5f3c445ae858ba38a23d3f2981bcc8258565a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 2 Jul 2019 10:10:50 +0200 Subject: [PATCH 420/664] Remove merged files from phpcs config (#16380) --- phpcs.xml.dist | 4 ---- 1 file changed, 4 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6b1456e982e296..dc774992d75fb9 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -35,10 +35,6 @@ <exclude-pattern>./packages/block-serialization-spec-parser/parser.php</exclude-pattern> <exclude-pattern>./build</exclude-pattern> - <rule ref="PHPCompatibility.PHP.NewKeywords.t_namespaceFound"> - <exclude-pattern>lib/class-wp-rest-block-renderer-controller.php</exclude-pattern> - <exclude-pattern>lib/class-wp-rest-search-controller.php</exclude-pattern> - </rule> <!-- These special comments are markers for the build process --> <rule ref="Squiz.Commenting.InlineComment.WrongStyle"> <exclude-pattern>gutenberg.php</exclude-pattern> From e7f9c23dcc0e75db7bd1d397f049f9a46a0ca11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 2 Jul 2019 11:15:27 +0200 Subject: [PATCH 421/664] Upgrade composer dependencies (WPCS) (#16387) --- composer.json | 8 +-- composer.lock | 167 +++++++++++++++++++++++++------------------------ phpcs.xml.dist | 6 +- 3 files changed, 93 insertions(+), 88 deletions(-) diff --git a/composer.json b/composer.json index 864a34d9c092f7..e869eb3ccc7fff 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,10 @@ "issues": "https://github.com/WordPress/gutenberg/issues" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", - "squizlabs/php_codesniffer": "^3.1", - "wimg/php-compatibility": "^8", - "wp-coding-standards/wpcs": "^1.0.0" + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "squizlabs/php_codesniffer": "^3.4.2", + "phpcompatibility/php-compatibility": "^9.2.0", + "wp-coding-standards/wpcs": "^2.1.1" }, "require": { "composer/installers": "~1.0" diff --git a/composer.lock b/composer.lock index a7c30dab21f451..e6bdc5bf4f1473 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "14614d0ab7be7d1f70eeae5bcd5f125d", + "content-hash": "78aec8b4f7bcfa500026de9ddb1abace", "packages": [ { "name": "composer/installers", - "version": "v1.5.0", + "version": "v1.6.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "049797d727261bf27f2690430d935067710049c2" + "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2", - "reference": "049797d727261bf27f2690430d935067710049c2", + "url": "https://api.github.com/repos/composer/installers/zipball/cfcca6b1b60bc4974324efb5783c13dca6932b5b", + "reference": "cfcca6b1b60bc4974324efb5783c13dca6932b5b", "shasum": "" }, "require": { @@ -124,35 +124,33 @@ "zend", "zikula" ], - "time": "2017-12-29T09:13:20+00:00" + "time": "2018-08-27T06:10:37+00:00" } ], "packages-dev": [ { "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.4.4", + "version": "v0.5.0", "source": { "type": "git", "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08" + "reference": "e749410375ff6fb7a040a68878c656c2e610b132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08", - "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/e749410375ff6fb7a040a68878c656c2e610b132", + "reference": "e749410375ff6fb7a040a68878c656c2e610b132", "shasum": "" }, "require": { "composer-plugin-api": "^1.0", "php": "^5.3|^7", - "squizlabs/php_codesniffer": "*" + "squizlabs/php_codesniffer": "^2|^3" }, "require-dev": { "composer/composer": "*", - "wimg/php-compatibility": "^8.0" - }, - "suggest": { - "dealerdirect/qa-tools": "All the PHP QA tools you'll need" + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" }, "type": "composer-plugin", "extra": { @@ -170,13 +168,13 @@ "authors": [ { "name": "Franck Nijhof", - "email": "f.nijhof@dealerdirect.nl", - "homepage": "http://workingatdealerdirect.eu", - "role": "Developer" + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", - "homepage": "http://workingatdealerdirect.eu", + "homepage": "http://www.dealerdirect.com", "keywords": [ "PHPCodeSniffer", "PHP_CodeSniffer", @@ -194,135 +192,142 @@ "stylecheck", "tests" ], - "time": "2017-12-06T16:27:17+00:00" + "time": "2018-10-26T13:21:45+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.3.1", + "name": "phpcompatibility/php-compatibility", + "version": "9.2.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "628a481780561150481a9ec74709092b9759b3ec" + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/628a481780561150481a9ec74709092b9759b3ec", - "reference": "628a481780561150481a9ec74709092b9759b3ec", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/3db1bf1e28123fd574a4ae2e9a84072826d51b5e", + "reference": "3db1bf1e28123fd574a4ae2e9a84072826d51b5e", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, + "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "LGPL-3.0-or-later" ], "authors": [ { - "name": "Greg Sherwood", + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + }, + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", "role": "lead" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", "keywords": [ + "compatibility", "phpcs", "standards" ], - "time": "2018-07-26T23:47:18+00:00" + "time": "2019-06-27T19:58:56+00:00" }, { - "name": "wimg/php-compatibility", - "version": "8.2.0", + "name": "squizlabs/php_codesniffer", + "version": "3.4.2", "source": { "type": "git", - "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", - "reference": "eaf613c1a8265bcfd7b0ab690783f2aef519f78a" + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/eaf613c1a8265bcfd7b0ab690783f2aef519f78a", - "reference": "eaf613c1a8265bcfd7b0ab690783f2aef519f78a", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", + "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8", "shasum": "" }, "require": { - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" - }, - "conflict": { - "squizlabs/php_codesniffer": "2.6.2" + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" - }, - "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", - "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, - "type": "phpcodesniffer-standard", - "autoload": { - "psr-4": { - "PHPCompatibility\\": "PHPCompatibility/" + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0-or-later" + "BSD-3-Clause" ], "authors": [ { - "name": "Wim Godden", + "name": "Greg Sherwood", "role": "lead" } ], - "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP version compatibility.", - "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ - "compatibility", "phpcs", "standards" ], - "time": "2018-07-17T13:42:26+00:00" + "time": "2019-04-10T23:49:02+00:00" }, { "name": "wp-coding-standards/wpcs", - "version": "1.0.0", + "version": "2.1.1", "source": { "type": "git", - "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git", - "reference": "539c6d74e6207daa22b7ea754d6f103e9abb2755" + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "bd9c33152115e6741e3510ff7189605b35167908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/539c6d74e6207daa22b7ea754d6f103e9abb2755", - "reference": "539c6d74e6207daa22b7ea754d6f103e9abb2755", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/bd9c33152115e6741e3510ff7189605b35167908", + "reference": "bd9c33152115e6741e3510ff7189605b35167908", "shasum": "" }, "require": { - "php": ">=5.3", - "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.3.1" }, "require-dev": { - "phpcompatibility/php-compatibility": "*" + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." }, "type": "phpcodesniffer-standard", "notification-url": "https://packagist.org/downloads/", @@ -341,7 +346,7 @@ "standards", "wordpress" ], - "time": "2018-07-25T18:10:35+00:00" + "time": "2019-05-21T02:50:00+00:00" } ], "aliases": [], diff --git a/phpcs.xml.dist b/phpcs.xml.dist index dc774992d75fb9..1a8d5adb1183ba 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -67,13 +67,13 @@ </rule> <!-- Ignore snake case error in parser --> - <rule ref="WordPress.NamingConventions.ValidVariableName.NotSnakeCase"> + <rule ref="WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase"> <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> </rule> - <rule ref="WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar"> + <rule ref="WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase"> <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> </rule> - <rule ref="WordPress.NamingConventions.ValidVariableName.MemberNotSnakeCase"> + <rule ref="WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase"> <exclude-pattern>./packages/block-serialization-default-parser/parser.php</exclude-pattern> </rule> <!-- Ignore filename error since it requires WP core build process change --> From 6d3312ae7d5ce4632032092a74b61e18cbb8dd5b Mon Sep 17 00:00:00 2001 From: Rua Haszard <haszari@gmail.com> Date: Wed, 3 Jul 2019 00:39:52 +1200 Subject: [PATCH 422/664] use theme color for all shadows for focus state in primary button (#16275) * use theme color for all shadows for focus state in primary button * use lighter focus ring shade as per design feedback props @haszari --- packages/components/src/button/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 69776322ede4ee..ba13650e492dab 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -90,7 +90,7 @@ box-shadow: inset 0 -1px 0 color(theme(button) shade(50%)), 0 0 0 1px $white, - 0 0 0 3px $blue-medium-focus; + 0 0 0 3px color(theme(button) shade(5%)); } &:active:enabled { From 472e255fb626b599643f68f4f401ec30cb1c9c4b Mon Sep 17 00:00:00 2001 From: Ryan Welcher <me@ryanwelcher.com> Date: Wed, 3 Jul 2019 09:20:01 -0400 Subject: [PATCH 423/664] Introduces PluginSettingsSidebar slotfill to the Document sidebar. (#13361) --- packages/e2e-tests/plugins/plugins-api.php | 13 +++ .../plugins/plugins-api/document-setting.js | 21 ++++ .../__snapshots__/plugins-api.test.js.snap | 6 + .../specs/plugins/plugins-api.test.js | 8 ++ packages/edit-post/README.md | 54 +++++++++ .../plugin-document-setting-panel/index.js | 106 ++++++++++++++++++ .../sidebar/settings-sidebar/index.js | 2 + packages/edit-post/src/index.js | 1 + 8 files changed, 211 insertions(+) create mode 100644 packages/e2e-tests/plugins/plugins-api/document-setting.js create mode 100644 packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js diff --git a/packages/e2e-tests/plugins/plugins-api.php b/packages/e2e-tests/plugins/plugins-api.php index a1c91596faea52..1accfb3fc718da 100644 --- a/packages/e2e-tests/plugins/plugins-api.php +++ b/packages/e2e-tests/plugins/plugins-api.php @@ -73,6 +73,19 @@ function enqueue_plugins_api_plugin_scripts() { filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/annotations-sidebar.js' ), true ); + + wp_enqueue_script( + 'gutenberg-test-plugins-api-document-setting', + plugins_url( 'plugins-api/document-setting.js', __FILE__ ), + array( + 'wp-edit-post', + 'wp-element', + 'wp-i18n', + 'wp-plugins', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'plugins-api/document-setting.js' ), + true + ); } add_action( 'init', 'enqueue_plugins_api_plugin_scripts' ); diff --git a/packages/e2e-tests/plugins/plugins-api/document-setting.js b/packages/e2e-tests/plugins/plugins-api/document-setting.js new file mode 100644 index 00000000000000..b244800c25809f --- /dev/null +++ b/packages/e2e-tests/plugins/plugins-api/document-setting.js @@ -0,0 +1,21 @@ +( function() { + var el = wp.element.createElement; + var __ = wp.i18n.__; + var registerPlugin = wp.plugins.registerPlugin; + var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; + + function MyDocumentSettingPlugin() { + return el( + PluginDocumentSettingPanel, + { + className: 'my-document-setting-plugin', + title: 'My Custom Panel' + }, + __( 'My Document Setting Panel' ) + ); + } + + registerPlugin( 'my-document-setting-plugin', { + render: MyDocumentSettingPlugin + } ); +} )(); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap index a3dda4545d67d9..0d8533597b0411 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap @@ -1,3 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = ` +" +My Custom Panel +" +`; + exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-toggled\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-star-filled\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; diff --git a/packages/e2e-tests/specs/plugins/plugins-api.test.js b/packages/e2e-tests/specs/plugins/plugins-api.test.js index d264bb736ed082..904fcaf0914e0d 100644 --- a/packages/e2e-tests/specs/plugins/plugins-api.test.js +++ b/packages/e2e-tests/specs/plugins/plugins-api.test.js @@ -76,4 +76,12 @@ describe( 'Using Plugins API', () => { expect( pluginSidebarClosed ).toBeNull(); } ); } ); + + describe( 'Document Setting Custom Panel', () => { + it( 'Should render a custom panel inside Document Setting sidebar', async () => { + await openDocumentSettingsSidebar(); + const pluginDocumentSettingsText = await page.$eval( '.edit-post-sidebar .my-document-setting-plugin', ( el ) => el.innerText ); + expect( pluginDocumentSettingsText ).toMatchSnapshot(); + } ); + } ); } ); diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index 4c2c8e46b2cfbc..6b895c79e75fb5 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -100,6 +100,60 @@ _Returns_ - `WPElement`: The WPElement to be rendered. +<a name="PluginDocumentSettingPanel" href="#PluginDocumentSettingPanel">#</a> **PluginDocumentSettingPanel** + +Renders items below the Status & Availability panel in the Document Sidebar. + +_Usage_ + +```js +// Using ES5 syntax +var el = wp.element.createElement; +var __ = wp.i18n.__; +var registerPlugin = wp.plugins.registerPlugin; +var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; + +function MyDocumentSettingPlugin() { + return el( + PluginDocumentSettingPanel, + { + className: 'my-document-setting-plugin', + }, + __( 'My Document Setting Panel' ) + ); +} + +registerPlugin( 'my-document-setting-plugin', { + render: MyDocumentSettingPlugin +} ); +``` + +```jsx +// Using ESNext syntax +const { registerPlugin } = wp.plugins; +const { PluginDocumentSettingPanel } = wp.editPost; + +const MyDocumentSettingTest = () => ( + <PluginDocumentSettingPanel className="my-document-setting-plugin"> + <p>My Document Setting Panel</p> + </PluginDocumentSetting> +); + + registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } ); +``` + +_Parameters_ + +- _props_ `Object`: Component properties. +- _props.name_ `[string]`: The machine-friendly name for the panel. +- _props.className_ `[string]`: An optional class name added to the row. +- _props.title_ `[string]`: The title of the panel +- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + +_Returns_ + +- `WPElement`: The WPElement to be rendered. + <a name="PluginMoreMenuItem" href="#PluginMoreMenuItem">#</a> **PluginMoreMenuItem** Renders a menu item in `Plugins` group in `More Menu` drop down, and can be used to as a button or link depending on the props provided. diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js new file mode 100644 index 00000000000000..a4ba4b924273ba --- /dev/null +++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js @@ -0,0 +1,106 @@ +/** + * Defines as extensibility slot for the Settings sidebar + */ + +/** + * WordPress dependencies + */ +import { createSlotFill, PanelBody } from '@wordpress/components'; +import { compose } from '@wordpress/compose'; +import { withPluginContext } from '@wordpress/plugins'; +import { withDispatch, withSelect } from '@wordpress/data'; + +export const { Fill, Slot } = createSlotFill( 'PluginDocumentSettingPanel' ); + +const PluginDocumentSettingFill = ( { isEnabled, opened, onToggle, className, title, icon, children } ) => { + if ( ! isEnabled ) { + return null; + } + return ( + <Fill> + <PanelBody + className={ className } + title={ title } + icon={ icon } + opened={ opened } + onToggle={ onToggle } + > + { children } + </PanelBody> + </Fill> + ); +}; + +/** + * Renders items below the Status & Availability panel in the Document Sidebar. + * + * @param {Object} props Component properties. + * @param {string} [props.name] The machine-friendly name for the panel. + * @param {string} [props.className] An optional class name added to the row. + * @param {string} [props.title] The title of the panel + * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + * + * @example <caption>ES5</caption> + * ```js + * // Using ES5 syntax + * var el = wp.element.createElement; + * var __ = wp.i18n.__; + * var registerPlugin = wp.plugins.registerPlugin; + * var PluginDocumentSettingPanel = wp.editPost.PluginDocumentSettingPanel; + * + * function MyDocumentSettingPlugin() { + * return el( + * PluginDocumentSettingPanel, + * { + * className: 'my-document-setting-plugin', + * }, + * __( 'My Document Setting Panel' ) + * ); + * } + * + * registerPlugin( 'my-document-setting-plugin', { + * render: MyDocumentSettingPlugin + * } ); + * ``` + * + * @example <caption>ESNext</caption> + * ```jsx + * // Using ESNext syntax + * const { registerPlugin } = wp.plugins; + * const { PluginDocumentSettingPanel } = wp.editPost; + * + * const MyDocumentSettingTest = () => ( + * <PluginDocumentSettingPanel className="my-document-setting-plugin"> + * <p>My Document Setting Panel</p> + * </PluginDocumentSetting> + * ); + * + * registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } ); + * ``` + * + * @return {WPElement} The WPElement to be rendered. + */ +const PluginDocumentSettingPanel = compose( + withPluginContext( ( context, ownProps ) => { + return { + icon: ownProps.icon || context.icon, + panelName: `${ context.name }/${ ownProps.name }`, + }; + } ), + withSelect( ( select, { panelName } ) => { + return ( + { + opened: select( 'core/edit-post' ).isEditorPanelOpened( panelName ), + isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( panelName ), + } + ); + } ), + withDispatch( ( dispatch, { panelName } ) => ( { + onToggle() { + return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( panelName ); + }, + } ) ), +)( PluginDocumentSettingFill ); + +PluginDocumentSettingPanel.Slot = Slot; +export default PluginDocumentSettingPanel; diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index 81e8a0ba87eb03..49bb45eec0ce38 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -20,6 +20,7 @@ import PostLink from '../post-link'; import DiscussionPanel from '../discussion-panel'; import PageAttributes from '../page-attributes'; import MetaBoxes from '../../meta-boxes'; +import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; const SettingsSidebar = ( { sidebarName } ) => ( <Sidebar @@ -31,6 +32,7 @@ const SettingsSidebar = ( { sidebarName } ) => ( { sidebarName === 'edit-post/document' && ( <> <PostStatus /> + <PluginDocumentSettingPanel.Slot /> <LastRevision /> <PostLink /> <PostTaxonomies /> diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 66d8c753b46ad4..8027695313a6f0 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -88,6 +88,7 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) } export { default as PluginBlockSettingsMenuItem } from './components/block-settings-menu/plugin-block-settings-menu-item'; +export { default as PluginDocumentSettingPanel } from './components/sidebar/plugin-document-setting-panel'; export { default as PluginMoreMenuItem } from './components/header/plugin-more-menu-item'; export { default as PluginPostPublishPanel } from './components/sidebar/plugin-post-publish-panel'; export { default as PluginPostStatusInfo } from './components/sidebar/plugin-post-status-info'; From f8f8708372c595babeac3e3cf4ff63d8e903dca6 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 3 Jul 2019 16:36:10 +0100 Subject: [PATCH 424/664] Document the plugin release tool (#16366) --- docs/contributors/release.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/contributors/release.md b/docs/contributors/release.md index bfbc84636b3a50..c9d4a99715dd9c 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -6,18 +6,40 @@ To release Gutenberg, you need commit access to the [WordPress.org plugin reposi ## Plugin Releases -### Versioning +### Schedule We release a new major version approximately every two weeks. The current and next versions are [tracked in GitHub milestones](https://github.com/WordPress/gutenberg/milestones), along with each version's tagging date. -### Release Candidates - On the date of the current milestone, we publish a release candidate and make it available for plugin authors and users to test. If any regressions are found with a release candidate, a new release candidate can be published. The date in the milestone is the date of **tagging the release candidate**. On this date, all remaining PRs on the milestone are moved automatically to the next release. Release candidates should be versioned incrementally, starting with `-rc.1`, then `-rc.2`, and so on. +Two days after the first release candidate, the stable version is created based on the last release candidate and any necessary regression fixes. + +Once the stable version is released, a post [like this](https://make.wordpress.org/core/2019/06/26/whats-new-in-gutenberg-26th-june/) describing the changes and performing a performance audit should be published. + +If critical bugs are discovered on stable versions of the plugin, patch versions can be released at any time. + +### Release Tool + +The plugin release process is entirely automated. To release the RC version of the plugin, run the following command and follow the instructions: (Note that at the time of writing, the tool doesn't support releasing multiple consecutive RC releases) + +```bash +./bin/commander.js rc +``` + +To release a stable version, run: + +```bash +./bin.commander.js stable +``` + +It is possible to run the "stable" release CLI in a consecutive way to release patch releases following the first stable release. + +### Manual Release Process + #### Creating the first Release Candidate Releasing the first release candidate for this milestone (`x.x`) involves: @@ -71,10 +93,6 @@ git push origin release/x.x Here's an example [release candidate page](https://github.com/WordPress/gutenberg/releases/tag/v4.6.0-rc.1); yours should look like that when you're finished. -##### Publishing the Call For Testing - -Ping @karmatosed in the `[#core-test](https://wordpress.slack.com/messages/C03B0H5J0)` team to publish a call for testing post. Here's an [example call for testing post.](https://make.wordpress.org/test/2019/01/04/call-for-testing-gutenberg-4-8/) - #### Creating Release Candidate Patches (done via `git cherry-pick`) If a bug is found in a release candidate and a fix is committed to `master`, we should include that fix in a new release candidate. To do this you'll need to use `git cherry-pick` to add these changes to the milestone's release branch. This way only fixes are added to the release candidate and not all the new code that has landed on `master` since tagging: From eba2fc8ba1ffbd5997cf47b3aa7002f8a5597e9f Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Wed, 3 Jul 2019 18:18:41 +0100 Subject: [PATCH 425/664] Implement a block reordering animation (#16065) --- package-lock.json | 1 + packages/block-editor/package.json | 1 + .../src/components/block-list/block.js | 9 ++ .../src/components/block-list/index.js | 21 ++++- .../components/block-list/moving-animation.js | 85 +++++++++++++++++++ .../components/ignore-nested-events/index.js | 6 +- .../src/hooks/use-reduced-motion/index.js | 9 +- .../e2e-tests/specs/block-deletion.test.js | 4 +- 8 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/moving-animation.js diff --git a/package-lock.json b/package-lock.json index 70048538460260..54148d3de97777 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3285,6 +3285,7 @@ "classnames": "^2.2.5", "dom-scroll-into-view": "^1.2.1", "lodash": "^4.17.10", + "react-spring": "^8.0.19", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index da701fc0c08669..5add09430524e1 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -44,6 +44,7 @@ "classnames": "^2.2.5", "dom-scroll-into-view": "^1.2.1", "lodash": "^4.17.10", + "react-spring": "^8.0.19", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index aac68e2e6faa88..3556b829ba188d 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -3,6 +3,7 @@ */ import classnames from 'classnames'; import { get, reduce, size, first, last } from 'lodash'; +import { animated } from 'react-spring/web.cjs'; /** * WordPress dependencies @@ -50,6 +51,7 @@ import InserterWithShortcuts from '../inserter-with-shortcuts'; import Inserter from '../inserter'; import useHoveredArea from './hover-area'; import { isInsideRootBlock } from '../../utils/dom'; +import useMovingAnimation from './moving-animation'; /** * Prevents default dragging behavior within a block to allow for multi- @@ -97,6 +99,8 @@ function BlockListBlock( { toggleSelection, onShiftSelection, onSelectionStart, + animateOnChange, + enableAnimation, } ) { // Random state used to rerender the component if needed, ideally we don't need this const [ , updateRerenderState ] = useState( {} ); @@ -247,6 +251,9 @@ function BlockListBlock( { } }, [ isFirstMultiSelected ] ); + // Block Reordering animation + const style = useMovingAnimation( wrapper, isSelected || isPartOfMultiSelection, enableAnimation, animateOnChange ); + // Other event handlers /** @@ -458,6 +465,8 @@ function BlockListBlock( { tabIndex="0" aria-label={ blockLabel } childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } + tagName={ animated.div } + style={ style } { ...blockWrapperProps } > { shouldShowInsertionPoint && ( diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 79cc1b29273375..3e65a52fd3b2ec 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -3,7 +3,6 @@ */ import { findLast, - map, invert, mapValues, sortBy, @@ -29,6 +28,12 @@ import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; import { getBlockDOMNode } from '../../utils/dom'; +/** + * If the block count exceeds the threshold, we disable the reordering animation + * to avoid laginess. + */ +const BLOCK_ANIMATION_THRESHOLD = 200; + const forceSyncUpdates = ( WrappedComponent ) => ( props ) => { return ( <AsyncModeProvider value={ false }> @@ -36,6 +41,7 @@ const forceSyncUpdates = ( WrappedComponent ) => ( props ) => { </AsyncModeProvider> ); }; + class BlockList extends Component { constructor( props ) { super( props ); @@ -198,11 +204,12 @@ class BlockList extends Component { multiSelectedBlockClientIds, hasMultiSelection, renderAppender, + enableAnimation, } = this.props; return ( <div className="editor-block-list__layout block-editor-block-list__layout"> - { map( blockClientIds, ( clientId ) => { + { blockClientIds.map( ( clientId ) => { const isBlockInSelection = hasMultiSelection ? multiSelectedBlockClientIds.includes( clientId ) : selectedBlockClientId === clientId; @@ -214,11 +221,17 @@ class BlockList extends Component { isBlockInSelection={ isBlockInSelection } > <BlockListBlock + rootClientId={ rootClientId } clientId={ clientId } blockRef={ this.setBlockRef } onSelectionStart={ this.onSelectionStart } - rootClientId={ rootClientId } isDraggable={ isDraggable } + + // This prop is explicitely computed and passed down + // to avoid being impacted by the async mode + // otherwise there might be a small delay to trigger the animation. + animateOnChange={ blockClientIds } + enableAnimation={ enableAnimation } /> </BlockAsyncModeProvider> ); @@ -248,6 +261,7 @@ export default compose( [ getSelectedBlockClientId, getMultiSelectedBlockClientIds, hasMultiSelection, + getGlobalBlockCount, } = select( 'core/block-editor' ); const { rootClientId } = ownProps; @@ -261,6 +275,7 @@ export default compose( [ selectedBlockClientId: getSelectedBlockClientId(), multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), hasMultiSelection: hasMultiSelection(), + enableAnimation: getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js new file mode 100644 index 00000000000000..e0fdf75ed6d6b3 --- /dev/null +++ b/packages/block-editor/src/components/block-list/moving-animation.js @@ -0,0 +1,85 @@ +/** + * External dependencies + */ +import { useSpring, interpolate } from 'react-spring/web.cjs'; + +/** + * WordPress dependencies + */ +import { useState, useLayoutEffect } from '@wordpress/element'; +import { useReducedMotion } from '@wordpress/compose'; + +/** + * Hook used to compute the styles required to move a div into a new position. + * + * The way this animation works is the following: + * - It first renders the element as if there was no animation. + * - It takes a snapshot of the position of the block to use it + * as a destination point for the animation. + * - It restores the element to the previous position using a CSS transform + * - It uses the "resetAnimation" flag to reset the animation + * from the beginning in order to animate to the new destination point. + * + * @param {Object} ref Reference to the element to animate. + * @param {boolean} isSelected Whether it's the current block or not. + * @param {boolean} enableAnimation Enable/Disable animation. + * @param {*} triggerAnimationOnChange Variable used to trigger the animation if it changes. + * + * @return {Object} Style object. + */ +function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationOnChange ) { + const prefersReducedMotion = useReducedMotion() || ! enableAnimation; + const [ resetAnimation, setResetAnimation ] = useState( false ); + const [ transform, setTransform ] = useState( { x: 0, y: 0 } ); + + const previous = ref.current ? ref.current.getBoundingClientRect() : null; + useLayoutEffect( () => { + if ( prefersReducedMotion ) { + return; + } + ref.current.style.transform = 'none'; + const destination = ref.current.getBoundingClientRect(); + const newTransform = { + x: previous ? previous.left - destination.left : 0, + y: previous ? previous.top - destination.top : 0, + }; + ref.current.style.transform = `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; + setResetAnimation( true ); + setTransform( newTransform ); + }, [ triggerAnimationOnChange ] ); + useLayoutEffect( () => { + if ( resetAnimation ) { + setResetAnimation( false ); + } + }, [ resetAnimation ] ); + const animationProps = useSpring( { + from: transform, + to: { + x: 0, + y: 0, + }, + reset: resetAnimation, + config: { mass: 5, tension: 2000, friction: 200 }, + immediate: prefersReducedMotion, + } ); + + return { + transformOrigin: 'center', + transform: interpolate( + [ + animationProps.x, + animationProps.y, + ], + ( x, y ) => x === 0 && y === 0 ? undefined : `translate3d(${ x }px,${ y }px,0)` + ), + zIndex: interpolate( + [ + animationProps.x, + animationProps.y, + ], + ( x, y ) => ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1` + ), + }; +} + +export default useMovingAnimation; diff --git a/packages/block-editor/src/components/ignore-nested-events/index.js b/packages/block-editor/src/components/ignore-nested-events/index.js index cb707785bcb65e..42f902b3e61c95 100644 --- a/packages/block-editor/src/components/ignore-nested-events/index.js +++ b/packages/block-editor/src/components/ignore-nested-events/index.js @@ -6,7 +6,7 @@ import { reduce } from 'lodash'; /** * WordPress dependencies */ -import { Component, forwardRef } from '@wordpress/element'; +import { Component, forwardRef, createElement } from '@wordpress/element'; /** * Component which renders a div with passed props applied except the optional @@ -66,7 +66,7 @@ export class IgnoreNestedEvents extends Component { } render() { - const { childHandledEvents = [], forwardedRef, ...props } = this.props; + const { childHandledEvents = [], forwardedRef, tagName = 'div', ...props } = this.props; const eventHandlers = reduce( [ ...childHandledEvents, @@ -96,7 +96,7 @@ export class IgnoreNestedEvents extends Component { return result; }, {} ); - return <div ref={ forwardedRef } { ...props } { ...eventHandlers } />; + return createElement( tagName, { ref: forwardedRef, ...props, ...eventHandlers } ); } } diff --git a/packages/compose/src/hooks/use-reduced-motion/index.js b/packages/compose/src/hooks/use-reduced-motion/index.js index 7b1e22792d37c5..a8b4cf0569b841 100644 --- a/packages/compose/src/hooks/use-reduced-motion/index.js +++ b/packages/compose/src/hooks/use-reduced-motion/index.js @@ -3,13 +3,20 @@ */ import useMediaQuery from '../use-media-query'; +/** + * Whether or not the user agent is Internet Explorer. + * + * @type {boolean} + */ +const IS_IE = window.navigator.userAgent.indexOf( 'Trident' ) >= 0; + /** * Hook returning whether the user has a preference for reduced motion. * * @return {boolean} Reduced motion preference value. */ const useReducedMotion = - process.env.FORCE_REDUCED_MOTION ? + process.env.FORCE_REDUCED_MOTION || IS_IE ? () => true : () => useMediaQuery( '(prefers-reduced-motion: reduce)' ); diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index 02cc0a0f5720ee..f5cfe7aec0da4d 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -110,8 +110,8 @@ describe( 'block deletion -', () => { // Click on something that's not a block. await page.click( '.editor-post-title' ); - // Click on the third (image) block so that its wrapper is selected and backspace to delete it. - await page.click( '.block-editor-block-list__block:nth-child(3) .components-placeholder__label' ); + // Click on the image block so that its wrapper is selected and backspace to delete it. + await page.click( '.wp-block[data-type="core/image"] .components-placeholder__label' ); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); From 9052d4c26fff89907d7e7abec745a3d0079dd178 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 3 Jul 2019 13:24:46 -0400 Subject: [PATCH 426/664] Editor: Resolve Publish button busy state styling regression (#16303) * Components: Avoid assigning default styling if explicitly primary * Editor: Remove redundant assignment of publish button as large * Components: Add CHANGELOG entry for busy styling fix * Components: Always apply is-default class if isDefault prop passed * Components: Add test case to protect regression on isPrimary, isLarge/isSmall combination --- packages/components/CHANGELOG.md | 5 ++++- packages/components/src/button/index.js | 2 +- packages/components/src/button/test/index.js | 11 ++++++++--- .../src/components/post-publish-button/index.js | 1 - 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index b54dcec4b412bc..a2ce1b818505e4 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,9 +1,12 @@ -## Next release +## Master ### New Features - Added a new `popoverProps` prop to the `Dropdown` component which allows users of the `Dropdown` component to pass props directly to the `PopOver` component. +### Bug Fixes + +- The `Button` component will no longer assign default styling (`is-default` class) when explicitly assigned as primary (the `isPrimary` prop). This should resolve potential conflicts affecting a combination of `isPrimary`, `isDefault`, and `isLarge` / `isSmall`, where the busy animation would appear with incorrect coloring. ## 8.0.0 (2019-06-12) diff --git a/packages/components/src/button/index.js b/packages/components/src/button/index.js index 3d6f9a2cb251c8..da3441e7f2982a 100644 --- a/packages/components/src/button/index.js +++ b/packages/components/src/button/index.js @@ -28,7 +28,7 @@ export function Button( props, ref ) { const classes = classnames( 'components-button', className, { 'is-button': isDefault || isPrimary || isLarge || isSmall, - 'is-default': isDefault || isLarge || isSmall, + 'is-default': isDefault || ( ! isPrimary && ( isLarge || isSmall ) ), 'is-primary': isPrimary, 'is-large': isLarge, 'is-small': isSmall, diff --git a/packages/components/src/button/test/index.js b/packages/components/src/button/test/index.js index 6140b00b56b7c5..05e3580bfacbe6 100644 --- a/packages/components/src/button/test/index.js +++ b/packages/components/src/button/test/index.js @@ -27,14 +27,14 @@ describe( 'Button', () => { expect( button.type() ).toBe( 'button' ); } ); - it( 'should render a button element with button-primary class', () => { + it( 'should render a button element with is-primary class', () => { const button = shallow( <Button isPrimary /> ); expect( button.hasClass( 'is-large' ) ).toBe( false ); expect( button.hasClass( 'is-primary' ) ).toBe( true ); expect( button.hasClass( 'is-button' ) ).toBe( true ); } ); - it( 'should render a button element with button-large class', () => { + it( 'should render a button element with is-large class', () => { const button = shallow( <Button isLarge /> ); expect( button.hasClass( 'is-large' ) ).toBe( true ); expect( button.hasClass( 'is-default' ) ).toBe( true ); @@ -42,7 +42,12 @@ describe( 'Button', () => { expect( button.hasClass( 'is-primary' ) ).toBe( false ); } ); - it( 'should render a button element with button-small class', () => { + it( 'should render a button element without is-default if primary', () => { + const button = shallow( <Button isPrimary isLarge /> ); + expect( button.hasClass( 'is-default' ) ).toBe( false ); + } ); + + it( 'should render a button element with is-small class', () => { const button = shallow( <Button isSmall /> ); expect( button.hasClass( 'is-default' ) ).toBe( true ); expect( button.hasClass( 'is-button' ) ).toBe( true ); diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index 0418f9b17c6062..ff3bedf5a79fb5 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -92,7 +92,6 @@ export class PostPublishButton extends Component { 'aria-disabled': isButtonDisabled, className: 'editor-post-publish-button', isBusy: isSaving && isPublished, - isLarge: true, isPrimary: true, onClick: onClickButton, }; From c7e5b403509f7bd0d577da48dc5f582b9f9f97a2 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Thu, 4 Jul 2019 04:49:55 +1000 Subject: [PATCH 427/664] Make top bar accessible when zoomed in. (#16250) * Make top bar and tables accessible when zoomed in. * Remove min-width for scrollability in IE * Add explanatory comment for display reset. * Revert table changes * Re-align top bar elements. * Unstick top bar only when zoomed in. * Address PR feedback. --- assets/stylesheets/_breakpoints.scss | 1 + assets/stylesheets/_mixins.scss | 6 ++++++ .../edit-post/src/components/header/style.scss | 18 ++++++++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/assets/stylesheets/_breakpoints.scss b/assets/stylesheets/_breakpoints.scss index 066d7d8f250e5e..3dd164c99753a5 100644 --- a/assets/stylesheets/_breakpoints.scss +++ b/assets/stylesheets/_breakpoints.scss @@ -10,6 +10,7 @@ $break-large: 960px; // admin sidebar auto folds $break-medium: 782px; // adminbar goes big $break-small: 600px; $break-mobile: 480px; +$break-zoomed-in: 280px; // All media queries currently in WordPress: // diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index d72c0b5b3ecbfc..5e92ec128eaaf5 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -44,6 +44,12 @@ } } +@mixin break-zoomed-in() { + @media (min-width: #{ ($break-zoomed-in) }) { + @content; + } +} + /** * Long content fade mixin diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index 6acb7b5ab8a353..a94783dab8f727 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -1,19 +1,24 @@ .edit-post-header { - height: $header-height; padding: $grid-size-small 2px; border-bottom: $border-width solid $light-gray-500; background: $white; display: flex; - flex-direction: row; - align-items: stretch; + flex-wrap: wrap; justify-content: space-between; + align-items: center; + // The header should never be wider than the viewport, or buttons might be hidden. Especially relevant at high zoom levels. Related to https://core.trac.wordpress.org/ticket/47603#ticket. + max-width: 100vw; z-index: z-index(".edit-post-header"); left: 0; right: 0; - // Stick the toolbar to the top, because the admin bar is not fixed on mobile. - top: 0; - position: sticky; + // Make toolbar sticky on larger breakpoints + @include break-zoomed-in { + height: $header-height; + top: 0; + position: sticky; + flex-wrap: nowrap; + } // On mobile the main content area has to scroll, otherwise you can invoke the overscroll bounce on the non-scrolling container. @include break-small { @@ -66,6 +71,7 @@ .edit-post-header__settings { display: inline-flex; align-items: center; + flex-wrap: wrap; } .edit-post-header .components-button { From 2ec93993c764715589701efec8f190fd67c4597c Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Thu, 4 Jul 2019 04:53:17 +1000 Subject: [PATCH 428/664] Fix/sticky warning reflow (#16255) * Make top bar and tables accessible when zoomed in. * Remove min-width for scrollability in IE * Show sticky notice close button at high zoom. * Revert accidental feature branch merge. * Change width to max-width. * Add comment for width and reset top value. * Add trac ticket link to comment. --- packages/components/src/notice/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index 93c7e1f31c182f..6c9b4715915639 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -60,6 +60,7 @@ } .components-notice-list { - min-width: 300px; + // The notice should never be wider than the viewport, or the close button might be hidden. Especially relevant at high zoom levels. Related to https://core.trac.wordpress.org/ticket/47603#ticket. + max-width: 100vw; z-index: z-index(".components-notice-list"); } From 2582944ed7b06fd1076b921df61cca89accfcd1b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 3 Jul 2019 20:13:25 +0100 Subject: [PATCH 429/664] Add link UI with auto complete to image block. (#15570) --- .../src/components/url-popover/index.js | 120 ++++++- .../src/components/url-popover/style.scss | 34 ++ .../test/__snapshots__/index.js.snap | 70 ++-- packages/block-library/src/image/edit.js | 320 ++++++++++++++---- packages/format-library/src/link/inline.js | 76 +---- 5 files changed, 439 insertions(+), 181 deletions(-) diff --git a/packages/block-editor/src/components/url-popover/index.js b/packages/block-editor/src/components/url-popover/index.js index e549c3007fb088..d3c7c874c6892a 100644 --- a/packages/block-editor/src/components/url-popover/index.js +++ b/packages/block-editor/src/components/url-popover/index.js @@ -1,12 +1,24 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { - Popover, + ExternalLink, IconButton, + Popover, } from '@wordpress/components'; +import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import URLInput from '../url-input'; class URLPopover extends Component { constructor() { @@ -27,6 +39,7 @@ class URLPopover extends Component { render() { const { + additionalControls, children, renderSettings, position = 'bottom center', @@ -47,21 +60,30 @@ class URLPopover extends Component { position={ position } { ...popoverProps } > - <div className="editor-url-popover__row block-editor-url-popover__row"> - { children } - { !! renderSettings && ( - <IconButton - className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" - icon="arrow-down-alt2" - label={ __( 'Link Settings' ) } - onClick={ this.toggleSettingsVisibility } - aria-expanded={ isSettingsExpanded } - /> + <div className="block-editor-url-popover__input-container"> + <div className="editor-url-popover__row block-editor-url-popover__row"> + { children } + { !! renderSettings && ( + <IconButton + className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" + icon="arrow-down-alt2" + label={ __( 'Link Settings' ) } + onClick={ this.toggleSettingsVisibility } + aria-expanded={ isSettingsExpanded } + /> + ) } + </div> + { showSettings && ( + <div className="editor-url-popover__row block-editor-url-popover__row editor-url-popover__settings block-editor-url-popover__settings"> + { renderSettings() } + </div> ) } </div> - { showSettings && ( - <div className="editor-url-popover__row block-editor-url-popover__row editor-url-popover__settings block-editor-url-popover__settings"> - { renderSettings() } + { additionalControls && ! showSettings && ( + <div + className="block-editor-url-popover__additional-controls" + > + { additionalControls } </div> ) } </Popover> @@ -69,6 +91,76 @@ class URLPopover extends Component { } } +const LinkEditor = ( { + autocompleteRef, + className, + onChangeInputValue, + value, + ...props +} ) => ( + <form + className={ classnames( + 'block-editor-url-popover__link-editor', + className + ) } + { ...props } + > + <URLInput + value={ value } + onChange={ onChangeInputValue } + autocompleteRef={ autocompleteRef } + /> + <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> + + </form> +); + +URLPopover.__experimentalLinkEditor = LinkEditor; + +const LinkViewerUrl = ( { url, urlLabel, className } ) => { + const linkClassName = classnames( + className, + 'block-editor-url-popover__link-viewer-url' + ); + + if ( ! url ) { + return <span className={ linkClassName }></span>; + } + + return ( + <ExternalLink + className={ linkClassName } + href={ url } + > + { urlLabel || filterURLForDisplay( safeDecodeURI( url ) ) } + </ExternalLink> + ); +}; + +const LinkViewer = ( { + className, + url, + urlLabel, + editLink, + linkClassName, + ...props +} ) => { + return ( + <div + className={ classnames( + 'block-editor-url-popover__link-viewer', + className + ) } + { ...props } + > + <LinkViewerUrl url={ url } urlLabel={ urlLabel } className={ linkClassName } /> + <IconButton icon="edit" label={ __( 'Edit' ) } onClick={ editLink } /> + </div> + ); +}; + +URLPopover.__experimentalLinkViewer = LinkViewer; + /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/url-popover/README.md */ diff --git a/packages/block-editor/src/components/url-popover/style.scss b/packages/block-editor/src/components/url-popover/style.scss index 0b98f11afcd311..268908dd01478f 100644 --- a/packages/block-editor/src/components/url-popover/style.scss +++ b/packages/block-editor/src/components/url-popover/style.scss @@ -1,3 +1,15 @@ +.block-editor-url-popover__additional-controls { + border-top: $border-width solid $light-gray-500; +} + +.block-editor-url-popover__additional-controls > div[role="menu"] .components-icon-button:not(:disabled):not([aria-disabled="true"]):not(.is-default) > svg { + box-shadow: none; +} + +.block-editor-url-popover__additional-controls div[role="menu"] > .components-icon-button { + padding-left: 2px; +} + .block-editor-url-popover__row { display: flex; } @@ -50,10 +62,32 @@ } .block-editor-url-popover__settings { + display: block; padding: $panel-padding; border-top: $border-width solid $light-gray-500; + .components-base-control:last-child, .components-base-control:last-child .components-base-control__field { margin-bottom: 0; } } + +.block-editor-url-popover__link-editor, +.block-editor-url-popover__link-viewer { + display: flex; +} + +.block-editor-url-popover__link-viewer-url { + margin: $grid-size - $border-width; + flex-grow: 1; + flex-shrink: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 150px; + max-width: 500px; + + &.has-invalid-link { + color: $alert-red; + } +} diff --git a/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap index c835e54540c257..e7ba18eccd1ad2 100644 --- a/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/url-popover/test/__snapshots__/index.js.snap @@ -7,18 +7,22 @@ exports[`URLPopover matches the snapshot in its default state 1`] = ` position="bottom center" > <div - className="editor-url-popover__row block-editor-url-popover__row" + className="block-editor-url-popover__input-container" > - <div> - Editor + <div + className="editor-url-popover__row block-editor-url-popover__row" + > + <div> + Editor + </div> + <ForwardRef(IconButton) + aria-expanded={false} + className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" + icon="arrow-down-alt2" + label="Link Settings" + onClick={[Function]} + /> </div> - <ForwardRef(IconButton) - aria-expanded={false} - className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" - icon="arrow-down-alt2" - label="Link Settings" - onClick={[Function]} - /> </div> </Popover> `; @@ -30,24 +34,28 @@ exports[`URLPopover matches the snapshot when the settings are toggled open 1`] position="bottom center" > <div - className="editor-url-popover__row block-editor-url-popover__row" + className="block-editor-url-popover__input-container" > - <div> - Editor + <div + className="editor-url-popover__row block-editor-url-popover__row" + > + <div> + Editor + </div> + <ForwardRef(IconButton) + aria-expanded={true} + className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" + icon="arrow-down-alt2" + label="Link Settings" + onClick={[Function]} + /> </div> - <ForwardRef(IconButton) - aria-expanded={true} - className="editor-url-popover__settings-toggle block-editor-url-popover__settings-toggle" - icon="arrow-down-alt2" - label="Link Settings" - onClick={[Function]} - /> - </div> - <div - className="editor-url-popover__row block-editor-url-popover__row editor-url-popover__settings block-editor-url-popover__settings" - > - <div> - Settings + <div + className="editor-url-popover__row block-editor-url-popover__row editor-url-popover__settings block-editor-url-popover__settings" + > + <div> + Settings + </div> </div> </div> </Popover> @@ -60,10 +68,14 @@ exports[`URLPopover matches the snapshot when there are no settings 1`] = ` position="bottom center" > <div - className="editor-url-popover__row block-editor-url-popover__row" + className="block-editor-url-popover__input-container" > - <div> - Editor + <div + className="editor-url-popover__row block-editor-url-popover__row" + > + <div> + Editor + </div> </div> </div> </Popover> diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index b22d63da20cb9f..a50b8601aa55dd 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -3,6 +3,7 @@ */ import classnames from 'classnames'; import { + find, get, isEmpty, map, @@ -17,7 +18,10 @@ import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { Button, ButtonGroup, + ExternalLink, IconButton, + MenuItem, + NavigableMenu, PanelBody, Path, Rect, @@ -30,9 +34,16 @@ import { ToggleControl, Toolbar, withNotices, - ExternalLink, } from '@wordpress/components'; import { compose } from '@wordpress/compose'; +import { + LEFT, + RIGHT, + UP, + DOWN, + BACKSPACE, + ENTER, +} from '@wordpress/keycodes'; import { withSelect } from '@wordpress/data'; import { BlockAlignmentToolbar, @@ -40,9 +51,15 @@ import { BlockIcon, InspectorControls, MediaPlaceholder, + URLPopover, RichText, } from '@wordpress/block-editor'; -import { Component } from '@wordpress/element'; +import { + Component, + useCallback, + useState, + useRef, +} from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; import { withViewportMatch } from '@wordpress/viewport'; @@ -95,6 +112,155 @@ const isTemporaryImage = ( id, url ) => ! id && isBlobURL( url ); */ const isExternalImage = ( id, url ) => url && ! id && ! isBlobURL( url ); +const stopPropagation = ( event ) => { + event.stopPropagation(); +}; + +const stopPropagationRelevantKeys = ( event ) => { + if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. + event.stopPropagation(); + } +}; + +const ImageURLInputUI = ( { + advancedOptions, + linkDestination, + mediaLinks, + onChangeUrl, + url, +} ) => { + const [ isOpen, setIsOpen ] = useState( false ); + const openLinkUI = useCallback( () => { + setIsOpen( true ); + } ); + + const [ isEditingLink, setIsEditingLink ] = useState( false ); + const [ urlInput, setUrlInput ] = useState( null ); + + const startEditLink = useCallback( () => { + if ( linkDestination === LINK_DESTINATION_MEDIA || + linkDestination === LINK_DESTINATION_ATTACHMENT + ) { + setUrlInput( '' ); + } + setIsEditingLink( true ); + } ); + const stopEditLink = useCallback( () => { + setIsEditingLink( false ); + } ); + + const closeLinkUI = useCallback( () => { + setUrlInput( null ); + stopEditLink(); + setIsOpen( false ); + } ); + + const autocompleteRef = useRef( null ); + + const onClickOutside = useCallback( () => { + return ( event ) => { + // The autocomplete suggestions list renders in a separate popover (in a portal), + // so onClickOutside fails to detect that a click on a suggestion occurred in the + // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and + // return to avoid the popover being closed. + const autocompleteElement = autocompleteRef.current; + if ( autocompleteElement && autocompleteElement.contains( event.target ) ) { + return; + } + setIsOpen( false ); + setUrlInput( null ); + stopEditLink(); + }; + } ); + + const onSubmitLinkChange = useCallback( () => { + return ( event ) => { + if ( urlInput ) { + onChangeUrl( urlInput ); + } + stopEditLink(); + setUrlInput( null ); + event.preventDefault(); + }; + } ); + + const onLinkRemove = useCallback( () => { + closeLinkUI(); + onChangeUrl( '' ); + } ); + const linkEditorValue = urlInput !== null ? urlInput : url; + + const urlLabel = ( + find( mediaLinks, [ 'linkDestination', linkDestination ] ) || {} + ).title; + return ( + <> + <IconButton + icon="admin-links" + className="components-toolbar__control" + label={ url ? __( 'Edit Link' ) : __( 'Insert Link' ) } + aria-expanded={ isOpen } + onClick={ openLinkUI } + /> + { isOpen && ( + <URLPopover + onClickOutside={ onClickOutside() } + onClose={ closeLinkUI } + renderSettings={ () => advancedOptions } + additionalControls={ ! linkEditorValue && ( + <NavigableMenu> + { + map( mediaLinks, ( link ) => ( + <MenuItem + key={ link.linkDestination } + icon={ link.icon } + onClick={ () => { + setUrlInput( null ); + onChangeUrl( link.url ); + stopEditLink(); + } } + > + { link.title } + </MenuItem> + ) ) + } + </NavigableMenu> + ) } + > + { ( ! url || isEditingLink ) && ( + <URLPopover.__experimentalLinkEditor + className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" + value={ linkEditorValue } + onChangeInputValue={ setUrlInput } + onKeyDown={ stopPropagationRelevantKeys } + onKeyPress={ stopPropagation } + onSubmit={ onSubmitLinkChange() } + autocompleteRef={ autocompleteRef } + /> + ) } + { ( url && ! isEditingLink ) && ( + <> + <URLPopover.__experimentalLinkViewer + className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" + onKeyPress={ stopPropagation } + url={ url } + editLink={ startEditLink } + urlLabel={ urlLabel } + /> + <IconButton + icon="no" + label={ __( 'Remove Link' ) } + onClick={ onLinkRemove } + /> + </> + ) } + </URLPopover> + ) } + </> + ); +}; + class ImageEdit extends Component { constructor( { attributes } ) { super( ...arguments ); @@ -108,15 +274,15 @@ class ImageEdit extends Component { this.updateWidth = this.updateWidth.bind( this ); this.updateHeight = this.updateHeight.bind( this ); this.updateDimensions = this.updateDimensions.bind( this ); - this.onSetCustomHref = this.onSetCustomHref.bind( this ); + this.onSetHref = this.onSetHref.bind( this ); this.onSetLinkClass = this.onSetLinkClass.bind( this ); this.onSetLinkRel = this.onSetLinkRel.bind( this ); - this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); this.onSetNewTab = this.onSetNewTab.bind( this ); this.getFilename = this.getFilename.bind( this ); this.toggleIsEditing = this.toggleIsEditing.bind( this ); this.onUploadError = this.onUploadError.bind( this ); this.onImageError = this.onImageError.bind( this ); + this.getLinkDestinations = this.getLinkDestinations.bind( this ); this.state = { captionFocused: false, @@ -199,25 +365,6 @@ class ImageEdit extends Component { } ); } - onSetLinkDestination( value ) { - let href; - - if ( value === LINK_DESTINATION_NONE ) { - href = undefined; - } else if ( value === LINK_DESTINATION_MEDIA ) { - href = ( this.props.image && this.props.image.source_url ) || this.props.attributes.url; - } else if ( value === LINK_DESTINATION_ATTACHMENT ) { - href = this.props.image && this.props.image.link; - } else { - href = this.props.attributes.href; - } - - this.props.setAttributes( { - linkDestination: value, - href, - } ); - } - onSelectURL( newURL ) { const { url } = this.props.attributes; @@ -244,7 +391,28 @@ class ImageEdit extends Component { } } - onSetCustomHref( value ) { + onSetHref( value ) { + const linkDestinations = this.getLinkDestinations(); + const { attributes } = this.props; + const { linkDestination } = attributes; + let linkDestinationInput; + if ( ! value ) { + linkDestinationInput = LINK_DESTINATION_NONE; + } else { + linkDestinationInput = ( + find( linkDestinations, ( destination ) => { + return destination.url === value; + } ) || + { linkDestination: LINK_DESTINATION_CUSTOM } + ).linkDestination; + } + if ( linkDestination !== linkDestinationInput ) { + this.props.setAttributes( { + linkDestination: linkDestinationInput, + href: value, + } ); + return; + } this.props.setAttributes( { href: value } ); } @@ -337,12 +505,21 @@ class ImageEdit extends Component { } } - getLinkDestinationOptions() { + getLinkDestinations() { return [ - { value: LINK_DESTINATION_NONE, label: __( 'None' ) }, - { value: LINK_DESTINATION_MEDIA, label: __( 'Media File' ) }, - { value: LINK_DESTINATION_ATTACHMENT, label: __( 'Attachment Page' ) }, - { value: LINK_DESTINATION_CUSTOM, label: __( 'Custom URL' ) }, + { + linkDestination: LINK_DESTINATION_MEDIA, + title: __( 'Media File' ), + url: ( this.props.image && this.props.image.source_url ) || + this.props.attributes.url, + icon, + }, + { + linkDestination: LINK_DESTINATION_ATTACHMENT, + title: __( 'Attachment Page' ), + url: this.props.image && this.props.image.link, + icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0 0h24v24H0V0z" fill="none" /><Path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z" /></SVG>, + }, ]; } @@ -400,15 +577,47 @@ class ImageEdit extends Component { onChange={ this.updateAlignment } /> { url && ( - <Toolbar> - <IconButton - className={ classnames( 'components-icon-button components-toolbar__control', { 'is-active': this.state.isEditing } ) } - label={ __( 'Edit image' ) } - aria-pressed={ this.state.isEditing } - onClick={ this.toggleIsEditing } - icon={ editImageIcon } - /> - </Toolbar> + <> + <Toolbar> + <IconButton + className={ classnames( 'components-icon-button components-toolbar__control', { 'is-active': this.state.isEditing } ) } + label={ __( 'Edit image' ) } + aria-pressed={ this.state.isEditing } + onClick={ this.toggleIsEditing } + icon={ editImageIcon } + /> + </Toolbar> + <Toolbar> + <ImageURLInputUI + url={ href || '' } + onChangeUrl={ this.onSetHref } + mediaLinks={ this.getLinkDestinations() } + linkDestination={ linkDestination } + advancedOptions={ + <> + <ToggleControl + label={ __( 'Open in New Tab' ) } + onChange={ this.onSetNewTab } + checked={ linkTarget === '_blank' } /> + <TextControl + label={ __( 'Link CSS Class' ) } + value={ linkClass || '' } + onKeyPress={ stopPropagation } + onKeyDown={ stopPropagationRelevantKeys } + onChange={ this.onSetLinkClass } + /> + <TextControl + label={ __( 'Link Rel' ) } + value={ rel || '' } + onChange={ this.onSetLinkRel } + onKeyPress={ stopPropagation } + onKeyDown={ stopPropagationRelevantKeys } + /> + </> + } + /> + </Toolbar> + </> ) } </BlockControls> ); @@ -458,7 +667,7 @@ class ImageEdit extends Component { } ); const isResizable = [ 'wide', 'full' ].indexOf( align ) === -1 && isLargeViewport; - const isLinkURLInputReadOnly = linkDestination !== LINK_DESTINATION_CUSTOM; + const imageSizeOptions = this.getImageSizeOptions(); const getInspectorControls = ( imageWidth, imageHeight ) => ( @@ -539,39 +748,6 @@ class ImageEdit extends Component { </div> ) } </PanelBody> - <PanelBody title={ __( 'Link Settings' ) }> - <SelectControl - label={ __( 'Link To' ) } - value={ linkDestination } - options={ this.getLinkDestinationOptions() } - onChange={ this.onSetLinkDestination } - /> - { linkDestination !== LINK_DESTINATION_NONE && ( - <> - <TextControl - label={ __( 'Link URL' ) } - value={ href || '' } - onChange={ this.onSetCustomHref } - placeholder={ ! isLinkURLInputReadOnly ? 'https://' : undefined } - readOnly={ isLinkURLInputReadOnly } - /> - <ToggleControl - label={ __( 'Open in New Tab' ) } - onChange={ this.onSetNewTab } - checked={ linkTarget === '_blank' } /> - <TextControl - label={ __( 'Link CSS Class' ) } - value={ linkClass || '' } - onChange={ this.onSetLinkClass } - /> - <TextControl - label={ __( 'Link Rel' ) } - value={ rel || '' } - onChange={ this.onSetLinkRel } - /> - </> - ) } - </PanelBody> </InspectorControls> ); diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index c4b64e9e3c45ce..564ae795fd484b 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -1,22 +1,15 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { Component, createRef, useMemo } from '@wordpress/element'; import { - ExternalLink, - IconButton, ToggleControl, withSpokenMessages, } from '@wordpress/components'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { getRectangleFromRange } from '@wordpress/dom'; -import { prependHTTP, safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; +import { prependHTTP } from '@wordpress/url'; import { create, insert, @@ -25,7 +18,7 @@ import { getTextContent, slice, } from '@wordpress/rich-text'; -import { URLInput, URLPopover } from '@wordpress/block-editor'; +import { URLPopover } from '@wordpress/block-editor'; /** * Internal dependencies @@ -38,45 +31,6 @@ function isShowingInput( props, state ) { return props.addingLink || state.editLink; } -const LinkEditor = ( { value, onChangeInputValue, onKeyDown, submitLink, autocompleteRef } ) => ( - // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - <form - className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" - onKeyPress={ stopKeyPropagation } - onKeyDown={ onKeyDown } - onSubmit={ submitLink } - > - <URLInput - value={ value } - onChange={ onChangeInputValue } - autocompleteRef={ autocompleteRef } - /> - <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> - </form> - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ -); - -const LinkViewerUrl = ( { url } ) => { - const prependedURL = prependHTTP( url ); - const linkClassName = classnames( 'editor-format-toolbar__link-container-value block-editor-format-toolbar__link-container-value', { - 'has-invalid-link': ! isValidHref( prependedURL ), - } ); - - if ( ! url ) { - return <span className={ linkClassName }></span>; - } - - return ( - <ExternalLink - className={ linkClassName } - href={ url } - > - { filterURLForDisplay( safeDecodeURI( url ) ) } - </ExternalLink> - ); -}; - const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { const anchorRect = useMemo( () => { const selection = window.getSelection(); @@ -111,21 +65,6 @@ const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { return <URLPopover anchorRect={ anchorRect } { ...props } />; }; -const LinkViewer = ( { url, editLink } ) => { - return ( - // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar - /* eslint-disable jsx-a11y/no-static-element-interactions */ - <div - className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" - onKeyPress={ stopKeyPropagation } - > - <LinkViewerUrl url={ url } /> - <IconButton icon="edit" label={ __( 'Edit' ) } onClick={ editLink } /> - </div> - /* eslint-enable jsx-a11y/no-static-element-interactions */ - ); -}; - class InlineLinkUI extends Component { constructor() { super( ...arguments ); @@ -271,17 +210,22 @@ class InlineLinkUI extends Component { ) } > { showInput ? ( - <LinkEditor + <URLPopover.__experimentalLinkEditor + className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" value={ inputValue } onChangeInputValue={ this.onChangeInputValue } onKeyDown={ this.onKeyDown } - submitLink={ this.submitLink } + onKeyPress={ stopKeyPropagation } + onSubmit={ this.submitLink } autocompleteRef={ this.autocompleteRef } /> ) : ( - <LinkViewer + <URLPopover.__experimentalLinkViewer + className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" + onKeyPress={ stopKeyPropagation } url={ url } editLink={ this.editLink } + linkClassName={ isValidHref( prependHTTP( url ) ) ? undefined : 'has-invalid-link' } /> ) } </URLPopoverAtLink> From 2bb3d5ae0dce4e29fd7e04f7f44409abde0571ee Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 4 Jul 2019 10:31:35 +0100 Subject: [PATCH 430/664] Avoid a console warning when running performance tests (#16409) --- packages/e2e-tests/specs/performance.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-tests/specs/performance.test.js b/packages/e2e-tests/specs/performance.test.js index 1d547f8075139c..c63ffb11560879 100644 --- a/packages/e2e-tests/specs/performance.test.js +++ b/packages/e2e-tests/specs/performance.test.js @@ -34,7 +34,7 @@ describe( 'Performance', () => { } } ); - dispatch( 'core/editor' ).resetBlocks( blocks ); + dispatch( 'core/block-editor' ).resetBlocks( blocks ); }, html ); await saveDraft(); From fe96dadee92990f106d0e8fd0b09837203260177 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 4 Jul 2019 11:10:56 +0100 Subject: [PATCH 431/664] Fix: Block icon on the block switcher may become unreadable. (#16390) --- packages/block-editor/src/components/block-switcher/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index 88b0e984fb7319..b80a586023045a 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -23,7 +23,6 @@ // When the block switcher does not have any transformations, we show it but as disabled. // The background and opacity change helps make the icon legible, despite being disabled. .components-button.block-editor-block-switcher__no-switcher-icon:disabled { - background: $light-gray-200; border-radius: 0; opacity: 0.84; @@ -32,6 +31,7 @@ // and should be overridden when disabled. .block-editor-block-icon.has-colors { color: $dark-gray-500 !important; + background: $light-gray-200 !important; } } From 72697ce81e9052b132cc5f335a193fc1fa1ef60d Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Thu, 4 Jul 2019 11:30:12 +0100 Subject: [PATCH 432/664] Run registerBlockType hook over deprecated settings (#16348) * Run registerBlockType hook over deprecated settings * Update group block deprecation tests * Add test for applying registerBlockType filter to deprecated settings * Add id to group block deprecation test * Update documentation * Update changelog --- .../developers/filters/block-filters.md | 2 +- packages/blocks/CHANGELOG.md | 6 ++++ packages/blocks/src/api/registration.js | 5 +++ packages/blocks/src/api/test/registration.js | 36 +++++++++++++++++++ .../blocks/core__group__deprecated.html | 2 +- .../blocks/core__group__deprecated.json | 5 +-- .../core__group__deprecated.parsed.json | 4 +-- .../core__group__deprecated.serialized.html | 4 +-- 8 files changed, 56 insertions(+), 8 deletions(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index 99ba4cc0a0d49f..3904d6eb3cf283 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -59,7 +59,7 @@ Extending blocks can involve more than just providing alternative styles, in thi #### `blocks.registerBlockType` -Used to filter the block settings. It receives the block settings and the name of the block the registered block as arguments. +Used to filter the block settings. It receives the block settings and the name of the block the registered block as arguments. Since v6.1.0 this filter is also applied to each of a block's deprecated settings. _Example:_ diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 8f50c3899591da..d38762e51694a2 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Bug Fixes + +- The `'blocks.registerBlockType'` filter is now applied to each of a block's deprecated settings as well as the block's main settings. Ensures `supports` settings like `anchor` work for deprecations. + ## 6.3.0 (2019-05-21) ### New Feature diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 978cac89d52f63..83dcc057c94e65 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -99,6 +99,11 @@ export function registerBlockType( name, settings ) { } settings = applyFilters( 'blocks.registerBlockType', settings, name ); + + if ( settings.deprecated ) { + settings.deprecated = settings.deprecated.map( ( deprecation ) => applyFilters( 'blocks.registerBlockType', deprecation, name ) ); + } + if ( ! isPlainObject( settings ) ) { console.error( 'Block settings must be a valid object.' diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 90d1c4760d1ee4..cd8c9692c21a56 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -337,6 +337,42 @@ describe( 'blocks', () => { expect( console ).toHaveErroredWith( 'Block settings must be a valid object.' ); expect( block ).toBeUndefined(); } ); + + it( 'should apply the blocks.registerBlockType filter to each of the deprecated settings as well as the main block settings', () => { + const blockSettingsWithDeprecations = { + ...defaultBlockSettings, + deprecated: [ + { + save() { + return 1; + }, + }, + { + save() { + return 2; + }, + }, + ], + }; + + addFilter( 'blocks.registerBlockType', 'core/blocks/without-title', ( settings ) => { + return { + ...settings, + attributes: { + ...settings.attributes, + id: { + type: 'string', + }, + }, + }; + } ); + + const block = registerBlockType( 'my-plugin/fancy-block-13', blockSettingsWithDeprecations ); + + expect( block.attributes.id ).toEqual( { type: 'string' } ); + expect( block.deprecated[ 0 ].attributes.id ).toEqual( { type: 'string' } ); + expect( block.deprecated[ 1 ].attributes.id ).toEqual( { type: 'string' } ); + } ); } ); } ); diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html index e57b273d388e51..bd111993515f16 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.html @@ -1,5 +1,5 @@ <!-- wp:group {"backgroundColor":"lighter-blue","align":"full"} --> -<div class="wp-block-group alignfull has-lighter-blue-background-color has-background"> +<div class="wp-block-group alignfull has-lighter-blue-background-color has-background" id="test-id"> <!-- wp:paragraph --> <p>test</p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json index 89116067ef6f6b..c86392f55adb97 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.json @@ -5,7 +5,8 @@ "isValid": true, "attributes": { "backgroundColor": "lighter-blue", - "className": "alignfull" + "align": "full", + "anchor": "test-id" }, "innerBlocks": [ { @@ -20,6 +21,6 @@ "originalContent": "<p>test</p>" } ], - "originalContent": "<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\">\n\t\n</div>" + "originalContent": "<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\" id=\"test-id\">\n\t\n</div>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json index 38723b5e2457a5..20116913b718f1 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.parsed.json @@ -16,9 +16,9 @@ ] } ], - "innerHTML": "\n<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\">\n\t\n</div>\n", + "innerHTML": "\n<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\" id=\"test-id\">\n\t\n</div>\n", "innerContent": [ - "\n<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\">\n\t", + "\n<div class=\"wp-block-group alignfull has-lighter-blue-background-color has-background\" id=\"test-id\">\n\t", null, "\n</div>\n" ] diff --git a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html index 58aca8fd2e3508..891c48e13ef040 100644 --- a/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__group__deprecated.serialized.html @@ -1,5 +1,5 @@ -<!-- wp:group {"backgroundColor":"lighter-blue","className":"alignfull"} --> -<div class="wp-block-group has-lighter-blue-background-color has-background alignfull"><div class="wp-block-group__inner-container"><!-- wp:paragraph --> +<!-- wp:group {"backgroundColor":"lighter-blue","align":"full"} --> +<div class="wp-block-group alignfull has-lighter-blue-background-color has-background" id="test-id"><div class="wp-block-group__inner-container"><!-- wp:paragraph --> <p>test</p> <!-- /wp:paragraph --></div></div> <!-- /wp:group --> From 9de54316e0315daac9717e8a9d21e8a00b49d9f7 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Thu, 4 Jul 2019 11:43:24 +0100 Subject: [PATCH 433/664] Fix adding/deleting table columns across sections (#16410) * Handle adding columns across table sections * Handle deleting table columns across table sections * Add e2e tests * Update changelog --- packages/block-library/CHANGELOG.md | 8 +- packages/block-library/src/table/edit.js | 8 +- packages/block-library/src/table/state.js | 80 ++- .../block-library/src/table/test/state.js | 477 +++++++++++++++++- .../blocks/__snapshots__/table.test.js.snap | 12 + packages/e2e-tests/specs/blocks/table.test.js | 39 +- 6 files changed, 594 insertions(+), 30 deletions(-) diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index d33c0a1ec92767..e61bddfbbf2de0 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,4 +1,10 @@ -## ## 2.6.0 (2019-06-12) +## Master (unreleased) + +### Bug Fixes + +- Fixed insertion of columns in the table block, which now inserts columns for all table sections ([#16410](https://github.com/WordPress/gutenberg/pull/16410)) + +## 2.6.0 (2019-06-12) - Fixed an issue with creating upgraded embed blocks that are not registered ([#15883](https://github.com/WordPress/gutenberg/issues/15883)). diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index bdc6cfc253cebd..97d94335afb85a 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -37,6 +37,7 @@ import { insertColumn, deleteColumn, toggleSection, + isEmptyTableSection, } from './state'; import icon from './icon'; @@ -241,11 +242,10 @@ export class TableEdit extends Component { } const { attributes, setAttributes } = this.props; - const { section, columnIndex } = selectedCell; + const { columnIndex } = selectedCell; this.setState( { selectedCell: null } ); setAttributes( insertColumn( attributes, { - section, columnIndex: columnIndex + delta, } ) ); } @@ -352,7 +352,7 @@ export class TableEdit extends Component { * @return {Object} React element for the section. */ renderSection( { type, rows } ) { - if ( ! rows.length ) { + if ( isEmptyTableSection( rows ) ) { return null; } @@ -416,7 +416,7 @@ export class TableEdit extends Component { } = this.props; const { initialRowCount, initialColumnCount } = this.state; const { hasFixedLayout, head, body, foot } = attributes; - const isEmpty = ! head.length && ! body.length && ! foot.length; + const isEmpty = isEmptyTableSection( head ) && isEmptyTableSection( body ) && isEmptyTableSection( foot ); const Section = this.renderSection; if ( isEmpty ) { diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 7c649d5dcdb097..4aab2f3ee3d54b 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { times, get } from 'lodash'; +import { times, get, mapValues, every } from 'lodash'; /** * Creates a table state. @@ -122,21 +122,33 @@ export function deleteRow( state, { * @return {Object} New table state. */ export function insertColumn( state, { - section, columnIndex, } ) { - return { - [ section ]: state[ section ].map( ( row ) => ( { - cells: [ - ...row.cells.slice( 0, columnIndex ), - { - content: '', - tag: section === 'head' ? 'th' : 'td', - }, - ...row.cells.slice( columnIndex ), - ], - } ) ), - }; + return mapValues( state, ( section, sectionName ) => { + // Bail early if the table section is empty. + if ( isEmptyTableSection( section ) ) { + return section; + } + + return section.map( ( row ) => { + // Bail early if the row is empty or it's an attempt to insert past + // the last possible index of the array. + if ( isEmptyRow( row ) || row.cells.length < columnIndex ) { + return row; + } + + return { + cells: [ + ...row.cells.slice( 0, columnIndex ), + { + content: '', + tag: sectionName === 'head' ? 'th' : 'td', + }, + ...row.cells.slice( columnIndex ), + ], + }; + } ); + } ); } /** @@ -149,14 +161,18 @@ export function insertColumn( state, { * @return {Object} New table state. */ export function deleteColumn( state, { - section, columnIndex, } ) { - return { - [ section ]: state[ section ].map( ( row ) => ( { - cells: row.cells.filter( ( cell, index ) => index !== columnIndex ), - } ) ).filter( ( row ) => row.cells.length ), - }; + return mapValues( state, ( section ) => { + // Bail early if the table section is empty. + if ( isEmptyTableSection( section ) ) { + return section; + } + + return section.map( ( row ) => ( { + cells: row.cells.length >= columnIndex ? row.cells.filter( ( cell, index ) => index !== columnIndex ) : row.cells, + } ) ).filter( ( row ) => row.cells.length ); + } ); } /** @@ -169,7 +185,7 @@ export function deleteColumn( state, { */ export function toggleSection( state, section ) { // Section exists, replace it with an empty row to remove it. - if ( state[ section ] && state[ section ].length ) { + if ( ! isEmptyTableSection( state[ section ] ) ) { return { [ section ]: [] }; } @@ -179,3 +195,25 @@ export function toggleSection( state, section ) { // Section doesn't exist, insert an empty row to create the section. return insertRow( state, { section, rowIndex: 0, columnCount } ); } + +/** + * Determines whether a table section is empty. + * + * @param {Object} sectionRows Table section state. + * + * @return {boolean} True if the table section is empty, false otherwise. + */ +export function isEmptyTableSection( sectionRows ) { + return ! sectionRows || ! sectionRows.length || every( sectionRows, isEmptyRow ); +} + +/** + * Determines whether a table row is empty. + * + * @param {Object} row Table row state. + * + * @return {boolean} True if the table section is empty, false otherwise. + */ +export function isEmptyRow( row ) { + return ! ( row.cells && row.cells.length ); +} diff --git a/packages/block-library/src/table/test/state.js b/packages/block-library/src/table/test/state.js index 63904298be2bba..cce7fa9836bc70 100644 --- a/packages/block-library/src/table/test/state.js +++ b/packages/block-library/src/table/test/state.js @@ -14,6 +14,8 @@ import { insertColumn, deleteColumn, toggleSection, + isEmptyTableSection, + isEmptyRow, } from '../state'; const table = deepFreeze( { @@ -250,9 +252,46 @@ describe( 'insertRow', () => { } ); describe( 'insertColumn', () => { - it( 'should insert column', () => { + it( 'inserts before existing content by default', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: 'test', + tag: 'th', + }, + ], + }, + ], + }; + + const state = insertColumn( tableWithHead, { + columnIndex: 0, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: 'test', + tag: 'th', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'inserts a column for table sections that have existing cells', () => { const state = insertColumn( tableWithContent, { - section: 'body', columnIndex: 2, } ); @@ -311,7 +350,6 @@ describe( 'insertColumn', () => { }; const state = insertColumn( tableWithHead, { - section: 'head', columnIndex: 1, } ); @@ -334,6 +372,230 @@ describe( 'insertColumn', () => { expect( state ).toEqual( expected ); } ); + + it( 'avoids adding cells to empty rows', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + { + cells: [], + }, + ], + }; + + const state = insertColumn( tableWithHead, { + columnIndex: 0, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + { + cells: [], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'adds cells across table sections that already have cells', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + const state = insertColumn( tableWithHead, { + columnIndex: 1, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'adds cells only to rows that have enough cells when rows have an unequal number of cells', () => { + const tableWithHead = { + head: [ + { + cells: [ + { + content: '0', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '0', + tag: 'td', + }, + { + content: '1', + tag: 'td', + }, + { + content: '2', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '0', + tag: 'td', + }, + ], + }, + ], + }; + + const state = insertColumn( tableWithHead, { + columnIndex: 3, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '0', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '0', + tag: 'td', + }, + { + content: '1', + tag: 'td', + }, + { + content: '2', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '0', + tag: 'td', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); } ); describe( 'deleteRow', () => { @@ -427,6 +689,144 @@ describe( 'deleteColumn', () => { expect( state ).toEqual( expected ); } ); + + it( 'deletes columns across table sections', () => { + const tableWithOneColumn = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + { + cells: [ + { + content: 'test', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + const state = deleteColumn( tableWithOneColumn, { + section: 'body', + columnIndex: 0, + } ); + + const expected = { + head: [], + body: [], + foot: [], + }; + + expect( state ).toEqual( expected ); + } ); + + it( 'deletes columns across table sections when there are missing columns', () => { + const tableWithOneColumn = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + { + content: '', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + const state = deleteColumn( tableWithOneColumn, { + section: 'body', + columnIndex: 1, + } ); + + const expected = { + head: [ + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, + ], + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ], + foot: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ], + }; + + expect( state ).toEqual( expected ); + } ); } ); describe( 'toggleSection', () => { @@ -525,3 +925,74 @@ describe( 'toggleSection', () => { expect( state ).toEqual( expected ); } ); } ); + +describe( 'isEmptyTableSection', () => { + it( 'considers a section empty if it has no rows', () => { + const tableSection = []; + expect( isEmptyTableSection( tableSection ) ).toBe( true ); + } ); + + it( 'considers a section empty if it has a single row with no cells', () => { + const tableSection = [ + { + cells: [], + }, + ]; + + expect( isEmptyTableSection( tableSection ) ).toBe( true ); + } ); + + it( 'considers a section empty if it has multiple empty rows', () => { + const tableSection = [ + { + cells: [], + }, + { + cells: [], + }, + ]; + + expect( isEmptyTableSection( tableSection ) ).toBe( true ); + } ); + + it( 'considers a section not empty if it has a mixture of empty and non-empty rows', () => { + const tableSection = [ + { + cells: [], + }, + { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }, + ]; + + expect( isEmptyTableSection( tableSection ) ).toBe( false ); + } ); +} ); + +describe( 'isEmptyRow', () => { + it( 'considers a row empty if it has undefined cells', () => { + expect( isEmptyRow( {} ) ).toBe( true ); + } ); + + it( 'considers a row empty if it has a zero length array of cells', () => { + expect( isEmptyRow( { cells: [] } ) ).toBe( true ); + } ); + + it( 'considers a row not empty if it has a cell', () => { + const row = { + cells: [ + { + content: '', + tag: 'td', + }, + ], + }; + + expect( isEmptyRow( row ) ).toBe( false ); + } ); +} ); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap index 955d6f95e06fd4..abafd184d19109 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -1,5 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Table allows adding and deleting columns across the table header, body and footer 1`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table> +<!-- /wp:table -->" +`; + +exports[`Table allows adding and deleting columns across the table header, body and footer 2`] = ` +"<!-- wp:table --> +<table class=\\"wp-block-table\\"><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr><tr><td></td></tr></tbody><tfoot><tr><td></td></tr></tfoot></table> +<!-- /wp:table -->" +`; + exports[`Table allows header and footer rows to be switched on and off 1`] = ` "<!-- wp:table --> <table class=\\"wp-block-table\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table> diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js index 0e3dd0bf5cad42..dd24f06b24adaf 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -1,7 +1,12 @@ /** * WordPress dependencies */ -import { createNewPost, insertBlock, getEditedPostContent } from '@wordpress/e2e-test-utils'; +import { + clickBlockToolbarButton, + createNewPost, + getEditedPostContent, + insertBlock, +} from '@wordpress/e2e-test-utils'; const createButtonSelector = "//div[@data-type='core/table']//button[text()='Create Table']"; @@ -115,4 +120,36 @@ describe( 'Table', () => { // Expect the table to have only a body with written content. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'allows adding and deleting columns across the table header, body and footer', async () => { + await insertBlock( 'Table' ); + + // Create the table. + const createButton = await page.$x( createButtonSelector ); + await createButton[ 0 ].click(); + + // Toggle on the switches and add some content. + const headerSwitch = await page.$x( "//label[text()='Header section']" ); + const footerSwitch = await page.$x( "//label[text()='Footer section']" ); + await headerSwitch[ 0 ].click(); + await footerSwitch[ 0 ].click(); + + // Add a column. + await clickBlockToolbarButton( 'Edit table' ); + const addColumnAfterButton = await page.$x( "//button[text()='Add Column After']" ); + await addColumnAfterButton[ 0 ].click(); + + // Expect the table to have 3 columns across the header, body and footer. + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.click( '.wp-block-table__cell-content' ); + + // Delete a column. + await clickBlockToolbarButton( 'Edit table' ); + const deleteColumnButton = await page.$x( "//button[text()='Delete Column']" ); + await deleteColumnButton[ 0 ].click(); + + // Expect the table to have 2 columns across the header, body and footer. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 10416fc8a64a6be6c62ad67fefb01a0a62871790 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 4 Jul 2019 14:16:43 +0100 Subject: [PATCH 434/664] Fix: Image resizer doesn't work when one dimension is changed (#16398) --- packages/block-library/src/image/edit.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index a50b8601aa55dd..270b168abc142b 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -851,12 +851,10 @@ class ImageEdit extends Component { <> { getInspectorControls( imageWidth, imageHeight ) } <ResizableBox - size={ - width && height ? { - width, - height, - } : undefined - } + size={ { + width, + height, + } } minWidth={ minWidth } maxWidth={ maxWidthBuffer } minHeight={ minHeight } From 05b9e95a63ff1b278f9d6cfb1d63a84b54d1cbda Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Thu, 4 Jul 2019 11:57:58 -0400 Subject: [PATCH 435/664] Customizer: Add widget blocks section. (#16204) * Customizer: Add widget blocks section. * Customizer: Fix incompatibility with legacy widgets sanitization. * Customizer: Set `@since` versions for new PHP methods. Also remove the `.vscode` entry in .gitignore. * Customizer: Refactor new PHP code so that it can be loaded conditionally. Also add missing TODOs. * Customizer: Integrate widget blocks live preview and saving. * Customizer: Refactor widget blocks editor into a new component tree. * Customizer: Allow block toolbars to overflow into preview. * Customizer: Fix PHP notices due to missing widget controls. * Customizer: Remove notices and overflow hack. Block toolbar should wrap instead. * Customizer: Optimize and clean up changes. - Remove extra whitespace in widget names. - Add JSDocs and improve comments. - Add missing default parameter. - Flatten CSS selectors. - Throttle live preview updates. * Customizer: Sync Changesets Widget area edits made in the Customizer are synced to Customizer changesets as an object, encoded as a JSON string, where the keys are widget area IDs and the values are serialized block content. This file takes care of that syncing using the 2-way data binding supported by `WP_Customize_Control`s. The process is as follows: - On load, the client checks if the current changeset has widget areas that it can parse and use to hydrate the store. It will load all widget areas for the current theme, but if the changeset has content for a given area, it will replace its actual published content with the changeset's. - On edit, the client updates the 2-way bound input with a new object that maps widget area IDs and the values are serialized block content, encoded as a JSON string. - On publish, a PHP action will parse the JSON string in the changeset and update all the widget areas in it, to store the new content. * Customizer: Make widget blocks compatible with Changeset share URLs. * Customizer: Add server registered blocks back in. The getter function is now defined? * Customizer: Fix legacy compatibility issues. - Guard for undefined functions. - Add "experimental" warning to section title. - Don't let customizer syncing overwrite legacy widget previews until after the first edit. * Widget Blocks Manager: Define own noop function. * Customizer: Update `@since` version numbers. --- ...-experimental-wp-widget-blocks-manager.php | 38 +++++- ...ass-wp-customize-widget-blocks-control.php | 40 ++++++ lib/customizer.php | 122 +++++++++++++++++ lib/load.php | 1 + lib/widgets-page.php | 28 +++- .../index.js | 34 +++++ .../style.scss | 18 +++ .../sync-customizer.js | 126 ++++++++++++++++++ packages/edit-widgets/src/index.js | 23 +++- packages/edit-widgets/src/style.scss | 6 + 10 files changed, 423 insertions(+), 13 deletions(-) create mode 100644 lib/class-wp-customize-widget-blocks-control.php create mode 100644 lib/customizer.php create mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js create mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss create mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js diff --git a/lib/class-experimental-wp-widget-blocks-manager.php b/lib/class-experimental-wp-widget-blocks-manager.php index f2f26e5ef43728..82e9ef0f8b12b9 100644 --- a/lib/class-experimental-wp-widget-blocks-manager.php +++ b/lib/class-experimental-wp-widget-blocks-manager.php @@ -308,9 +308,14 @@ public static function output_blocks_widget( $options, $arguments ) { } /** - * Registers of a widget that should represent a set of blocks and returns its id. + * Noop block widget control output function for the necessary call to `wp_register_widget_control`. + */ + public static function output_blocks_widget_control() {} + + /** + * Registers a widget that should represent a set of blocks and returns its ID. * - * @param array $blocks Array of blocks. + * @param array $blocks Array of blocks. */ public static function convert_blocks_to_widget( $blocks ) { $widget_id = 'blocks-widget-' . md5( self::serialize_blocks( $blocks ) ); @@ -320,7 +325,7 @@ public static function convert_blocks_to_widget( $blocks ) { } wp_register_sidebar_widget( $widget_id, - __( 'Blocks Area ', 'gutenberg' ), + __( 'Blocks Area', 'gutenberg' ), 'Experimental_WP_Widget_Blocks_Manager::output_blocks_widget', array( 'classname' => 'widget-area', @@ -330,6 +335,12 @@ public static function convert_blocks_to_widget( $blocks ) { 'blocks' => $blocks, ) ); + wp_register_widget_control( + $widget_id, + __( 'Blocks Area', 'gutenberg' ), + 'Experimental_WP_Widget_Blocks_Manager::output_blocks_widget_control', + array( 'id_base' => 'blocks-widget' ) + ); return $widget_id; } @@ -340,20 +351,34 @@ public static function convert_blocks_to_widget( $blocks ) { */ public static function swap_out_sidebars_blocks_for_block_widgets( $sidebars_widgets_input ) { global $sidebars_widgets; + global $wp_customize; if ( null === self::$unfiltered_sidebar_widgets ) { self::$unfiltered_sidebar_widgets = $sidebars_widgets; } + $changeset_data = null; + if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) { + $changeset_data = $wp_customize->changeset_data(); + if ( isset( $changeset_data['gutenberg_widget_blocks']['value'] ) ) { + $changeset_data = json_decode( $changeset_data['gutenberg_widget_blocks']['value'] ); + } + } + $filtered_sidebar_widgets = array(); foreach ( $sidebars_widgets_input as $sidebar_id => $item ) { - if ( ! is_numeric( $item ) ) { + $changeset_value = $changeset_data && isset( $changeset_data->$sidebar_id ) + ? $changeset_data->$sidebar_id + : null; + + if ( ! is_numeric( $item ) && ! $changeset_value ) { $filtered_sidebar_widgets[ $sidebar_id ] = $item; continue; } $filtered_widgets = array(); $last_set_of_blocks = array(); - $post = get_post( $item ); - $blocks = parse_blocks( $post->post_content ); + $blocks = parse_blocks( + $changeset_value ? $changeset_value : get_post( $item )->post_content + ); foreach ( $blocks as $block ) { if ( ! isset( $block['blockName'] ) ) { @@ -379,6 +404,7 @@ public static function swap_out_sidebars_blocks_for_block_widgets( $sidebars_wid $filtered_sidebar_widgets[ $sidebar_id ] = $filtered_widgets; } $sidebars_widgets = $filtered_sidebar_widgets; + return $filtered_sidebar_widgets; } } diff --git a/lib/class-wp-customize-widget-blocks-control.php b/lib/class-wp-customize-widget-blocks-control.php new file mode 100644 index 00000000000000..e92fef67c1577d --- /dev/null +++ b/lib/class-wp-customize-widget-blocks-control.php @@ -0,0 +1,40 @@ +<?php +/** + * Customizer Widget Blocks Section: WP_Customize_Widget_Blocks_Control class. + * + * @package gutenberg + * @since 6.1.0 + */ + +/** + * Class that renders the Customizer control for editing widgets with Gutenberg. + * + * @since 6.1.0 + */ +class WP_Customize_Widget_Blocks_Control extends WP_Customize_Control { + /** + * Enqueue control related scripts/styles. + * + * @since 6.1.0 + */ + public function enqueue() { + gutenberg_widgets_init( 'gutenberg_customizer' ); + } + + /** + * Render the control's content. + * + * @since 6.1.0 + */ + public function render_content() { + ?> + <input + id="_customize-input-gutenberg_widget_blocks" + type="hidden" + value="<?php echo esc_attr( $this->value() ); ?>" + <?php $this->link(); ?> + /> + <?php + the_gutenberg_widgets( 'gutenberg_customizer' ); + } +} diff --git a/lib/customizer.php b/lib/customizer.php new file mode 100644 index 00000000000000..cb221987ae97c4 --- /dev/null +++ b/lib/customizer.php @@ -0,0 +1,122 @@ +<?php +/** + * Bootstraping the Gutenberg Customizer widget blocks section. + * + * Widget area edits made in the Customizer are synced to Customizer + * changesets as an object, encoded as a JSON string, where the keys + * are widget area IDs and the values are serialized block content. + * This file takes care of that syncing using the 2-way data binding + * supported by `WP_Customize_Control`s. The process is as follows: + * + * - On load, the client checks if the current changeset has + * widget areas that it can parse and use to hydrate the store. + * It will load all widget areas for the current theme, but if + * the changeset has content for a given area, it will replace + * its actual published content with the changeset's. + * + * - On edit, the client updates the 2-way bound input with a new object that maps + * widget area IDs and the values are serialized block content, encoded + * as a JSON string. + * + * - On publish, a PHP action will parse the JSON string in the + * changeset and update all the widget areas in it, to store the + * new content. + * + * @package gutenberg + */ + +/** + * The sanitization function for incoming values for the `gutenberg_widget_blocks` setting. + * It's a JSON string, so it decodes it and encodes it again to make sure it's valid. + * + * @param string $value The incoming value. + */ +function gutenberg_customize_sanitize( $value ) { + return json_encode( json_decode( $value ) ); +} + +/** + * Gutenberg's Customize Register. + * + * Adds a section to the Customizer for editing widgets with Gutenberg. + * + * @param \WP_Customize_Manager $wp_customize An instance of the class that controls most of the Theme Customization API for WordPress 3.4 and newer. + * @since 6.1.0 + */ +function gutenberg_customize_register( $wp_customize ) { + require dirname( __FILE__ ) . '/class-wp-customize-widget-blocks-control.php'; + $wp_customize->add_setting( + 'gutenberg_widget_blocks', + array( + 'default' => '{}', + 'type' => 'gutenberg_widget_blocks', + 'capability' => 'edit_theme_options', + 'transport' => 'postMessage', + 'sanitize_callback' => 'gutenberg_customize_sanitize', + ) + ); + $wp_customize->add_section( + 'gutenberg_widget_blocks', + array( 'title' => __( 'Widget Blocks (Experimental)', 'gutenberg' ) ) + ); + $wp_customize->add_control( + new WP_Customize_Widget_Blocks_Control( + $wp_customize, + 'gutenberg_widget_blocks', + array( + 'section' => 'gutenberg_widget_blocks', + 'settings' => 'gutenberg_widget_blocks', + ) + ) + ); +} +add_action( 'customize_register', 'gutenberg_customize_register' ); + +/** + * Specifies how to save the `gutenberg_widget_blocks` setting. It parses the JSON string and updates the + * referenced widget areas with the new content. + * + * @param string $value The value that is being published. + * @param \WP_Customize_Setting $setting The setting instance. + */ +function gutenberg_customize_update( $value, $setting ) { + foreach ( json_decode( $value ) as $sidebar_id => $sidebar_content ) { + $id_referenced_in_sidebar = Experimental_WP_Widget_Blocks_Manager::get_post_id_referenced_in_sidebar( $sidebar_id ); + + $post_id = wp_insert_post( + array( + 'ID' => $id_referenced_in_sidebar, + 'post_content' => $sidebar_content, + 'post_type' => 'wp_area', + ) + ); + + if ( 0 === $id_referenced_in_sidebar ) { + Experimental_WP_Widget_Blocks_Manager::reference_post_id_in_sidebar( $sidebar_id, $post_id ); + } + } +} +add_action( 'customize_update_gutenberg_widget_blocks', 'gutenberg_customize_update', 10, 2 ); + +/** + * Filters the Customizer widget settings arguments. + * This is needed because the Customizer registers settings for the raw registered widgets, without going through the `sidebars_widgets` filter. + * The `WP_Customize_Widgets` class expects sidebars to have an array of widgets registered, not a post ID. + * This results in the value passed to `sanitize_js_callback` being `null` and throwing an error. + * + * TODO: Figure out why core is not running the `sidebars_widgets` filter for the relevant part of the code. + * Then, either fix it or change this filter to parse the post IDs and then pass them to the original `sanitize_js_callback`. + * + * @param array $args Array of Customizer setting arguments. + * @param string $id Widget setting ID. + * @return array Maybe modified array of Customizer setting arguments. + */ +function filter_widget_customizer_setting_args( $args, $id = null ) { + // Posts won't have a settings ID like widgets. We can use that to remove the sanitization callback. + if ( ! isset( $id ) ) { + unset( $args['sanitize_js_callback'] ); + } + + return $args; +} +add_filter( 'widget_customizer_setting_args', 'filter_widget_customizer_setting_args' ); diff --git a/lib/load.php b/lib/load.php index d93b9dc1f9f0ea..7a3c97fb65553b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -34,3 +34,4 @@ require dirname( __FILE__ ) . '/demo.php'; require dirname( __FILE__ ) . '/widgets.php'; require dirname( __FILE__ ) . '/widgets-page.php'; +require dirname( __FILE__ ) . '/customizer.php'; diff --git a/lib/widgets-page.php b/lib/widgets-page.php index f790e60d24b7a1..27ebadcb9b9aa0 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -9,10 +9,21 @@ * The main entry point for the Gutenberg widgets page. * * @since 5.2.0 + * + * @param string $page The page name the function is being called for, `'gutenberg_customizer'` for the Customizer. */ -function the_gutenberg_widgets() { +function the_gutenberg_widgets( $page = 'gutenberg_page_gutenberg-widgets' ) { ?> - <div id="widgets-editor" class="blocks-widgets-container"> + <div + id="widgets-editor" + class="blocks-widgets-container + <?php + echo 'gutenberg_customizer' === $page + ? ' is-in-customizer' + : ''; + ?> + " + > </div> <?php } @@ -25,10 +36,14 @@ function the_gutenberg_widgets() { * @param string $hook Page. */ function gutenberg_widgets_init( $hook ) { - if ( 'gutenberg_page_gutenberg-widgets' !== $hook ) { + if ( 'gutenberg_page_gutenberg-widgets' !== $hook && 'gutenberg_customizer' !== $hook ) { return; } + $initializer_name = 'gutenberg_page_gutenberg-widgets' === $hook + ? 'initialize' + : 'customizerInitialize'; + // Media settings. $max_upload_size = wp_max_upload_size(); if ( ! $max_upload_size ) { @@ -58,15 +73,20 @@ function gutenberg_widgets_init( $hook ) { wp_add_inline_script( 'wp-edit-widgets', sprintf( - 'wp.editWidgets.initialize( "widgets-editor", %s );', + 'wp.domReady( function() { + wp.editWidgets.%s( "widgets-editor", %s ); + } );', + $initializer_name, wp_json_encode( $settings ) ) ); + // Preload server-registered block schemas. wp_add_inline_script( 'wp-blocks', 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');' ); + wp_enqueue_script( 'wp-edit-widgets' ); wp_enqueue_script( 'wp-format-library' ); wp_enqueue_style( 'wp-edit-widgets' ); diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js new file mode 100644 index 00000000000000..db31dd0a48d6e3 --- /dev/null +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js @@ -0,0 +1,34 @@ +/** + * WordPress dependencies + */ +import { + SlotFillProvider, + Popover, + navigateRegions, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import WidgetAreas from '../widget-areas'; + +import './sync-customizer'; + +function CustomizerEditWidgetsInitializer( { settings } ) { + return ( + <SlotFillProvider> + <div + className="edit-widgets-customizer-edit-widgets-initializer__content" + role="region" + aria-label={ __( 'Widgets screen content' ) } + tabIndex="-1" + > + <WidgetAreas blockEditorSettings={ settings } /> + </div> + <Popover.Slot /> + </SlotFillProvider> + ); +} + +export default navigateRegions( CustomizerEditWidgetsInitializer ); diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss new file mode 100644 index 00000000000000..f7ac0e338ce99c --- /dev/null +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss @@ -0,0 +1,18 @@ +.edit-widgets-customizer-edit-widgets-initializer__content { + background: #f1f1f1; + margin: 0; + min-height: 100%; + padding: 30px 0; + + .block-editor-block-list__layout { + padding: 0 0 0 18px; + } + + .block-editor-block-list__empty-block-inserter { + left: -18px; + } + + .block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable.wp-block-paragraph { + padding: 0; + } +} diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js new file mode 100644 index 00000000000000..724c3e5ea463d0 --- /dev/null +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js @@ -0,0 +1,126 @@ +/** + * External dependencies + */ +import { throttle } from 'lodash'; + +/** + * WordPress dependencies + */ +import { parse, serialize } from '@wordpress/blocks'; + +/* +Widget area edits made in the Customizer are synced to Customizer +changesets as an object, encoded as a JSON string, where the keys +are widget area IDs and the values are serialized block content. +This file takes care of that syncing using the 2-way data binding +supported by `WP_Customize_Control`s. The process is as follows: + +- On load, the client checks if the current changeset has +widget areas that it can parse and use to hydrate the store. +It will load all widget areas for the current theme, but if +the changeset has content for a given area, it will replace +its actual published content with the changeset's. + +- On edit, the client updates the 2-way bound input with a new object that maps +widget area IDs and the values are serialized block content, encoded +as a JSON string. + +- On publish, a PHP action will parse the JSON string in the +changeset and update all the widget areas in it, to store the +new content. +*/ + +// Get widget areas from the store in an `id => blocks` mapping. +const getWidgetAreasObject = () => { + const { getWidgetAreas, getBlocksFromWidgetArea } = window.wp.data.select( + 'core/edit-widgets' + ); + + return getWidgetAreas().reduce( ( widgetAreasObject, { id } ) => { + widgetAreasObject[ id ] = getBlocksFromWidgetArea( id ); + return widgetAreasObject; + }, {} ); +}; + +// Serialize the provided blocks and render them in the widget area with the provided ID. +const previewBlocksInWidgetArea = throttle( ( id, blocks ) => { + const customizePreviewIframe = document.querySelector( + '#customize-preview > iframe' + ); + if ( ! customizePreviewIframe || ! customizePreviewIframe.contentDocument ) { + return; + } + + const widgetArea = customizePreviewIframe.contentDocument.querySelector( + `[data-customize-partial-placement-context*='"sidebar_id":"${ id }"']` + ); + if ( widgetArea ) { + widgetArea.innerHTML = serialize( blocks ); + widgetArea.parentElement.innerHTML = widgetArea.outerHTML; + } +}, 1000 ); + +// Update the hidden input that has 2-way data binding with Customizer settings. +const updateSettingInputValue = throttle( ( nextWidgetAreas ) => { + const settingInput = document.getElementById( + '_customize-input-gutenberg_widget_blocks' + ); + if ( settingInput ) { + settingInput.value = JSON.stringify( + Object.keys( nextWidgetAreas ).reduce( ( value, id ) => { + value[ id ] = serialize( nextWidgetAreas[ id ] ); + return value; + }, {} ) + ); + settingInput.dispatchEvent( new window.Event( 'change' ) ); + } +}, 1000 ); + +// Check that all the necessary globals are present. +if ( window.wp && window.wp.customize && window.wp.data ) { + // Wait for the Customizer to finish bootstrapping. + window.wp.customize.bind( 'ready', () => + window.wp.customize.previewer.bind( 'ready', () => { + // Try to parse a previous changeset from the hidden input. + let widgetAreas; + try { + widgetAreas = JSON.parse( + document.getElementById( '_customize-input-gutenberg_widget_blocks' ) + .value + ); + widgetAreas = Object.keys( widgetAreas ).reduce( ( value, id ) => { + value[ id ] = parse( widgetAreas[ id ] ); + return value; + }, {} ); + } catch ( err ) { + widgetAreas = {}; + } + + // Wait for setup to finish before overwriting sidebars with changeset data, + // if any, and subscribe to registry changes after that so that we can preview + // changes and update the hidden input's value when any of the widget areas change. + const { setupWidgetAreas, updateBlocksInWidgetArea } = window.wp.data + .dispatch( 'core/edit-widgets' ); + setupWidgetAreas().then( () => { + Object.keys( widgetAreas ).forEach( ( id ) => updateBlocksInWidgetArea( id, widgetAreas[ id ] ) ); + widgetAreas = getWidgetAreasObject(); + window.wp.data.subscribe( () => { + const nextWidgetAreas = getWidgetAreasObject(); + + let didUpdate = false; + for ( const id of Object.keys( nextWidgetAreas ) ) { + if ( widgetAreas[ id ] !== nextWidgetAreas[ id ] ) { + previewBlocksInWidgetArea( id, nextWidgetAreas[ id ] ); + didUpdate = true; + } + } + + if ( didUpdate ) { + updateSettingInputValue( nextWidgetAreas ); + } + widgetAreas = nextWidgetAreas; + } ); + } ); + } ) + ); +} diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 8b4bb8573262de..16035513da954d 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -11,12 +11,13 @@ import { registerCoreBlocks } from '@wordpress/block-library'; import './hooks'; import './store'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; +import CustomizerEditWidgetsInitializer from './components/customizer-edit-widgets-initializer'; /** - * Initilizes the widgets screen + * Initializes the block editor in the widgets screen. * - * @param {string} id Id of the root element to render the screen. - * @param {Object} settings Id of the root element to render the screen. + * @param {string} id ID of the root element to render the screen in. + * @param {Object} settings Block editor settings. */ export function initialize( id, settings ) { registerCoreBlocks(); @@ -27,3 +28,19 @@ export function initialize( id, settings ) { document.getElementById( id ) ); } + +/** + * Initializes the block editor in the widgets Customizer section. + * + * @param {string} id ID of the root element to render the section in. + * @param {Object} settings Block editor settings. + */ +export function customizerInitialize( id, settings ) { + registerCoreBlocks(); + render( + <CustomizerEditWidgetsInitializer + settings={ settings } + />, + document.getElementById( id ) + ); +} diff --git a/packages/edit-widgets/src/style.scss b/packages/edit-widgets/src/style.scss index ab68ef801af1d4..aac1067bcfc15c 100644 --- a/packages/edit-widgets/src/style.scss +++ b/packages/edit-widgets/src/style.scss @@ -1,3 +1,4 @@ +@import "./components/customizer-edit-widgets-initializer/style.scss"; @import "./components/header/style.scss"; @import "./components/layout/style.scss"; @import "./components/notices/style.scss"; @@ -41,6 +42,11 @@ body.gutenberg_page_gutenberg-widgets { > .components-navigate-regions { height: 100%; } + + &.is-in-customizer { + min-height: initial; + position: initial; + } } /** From bb50415ab03839577866a0534fdc45caa52f43f5 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 4 Jul 2019 18:32:15 +0100 Subject: [PATCH 436/664] Fix: Image block does not retain the dimensions when opening the media library (#16125) --- packages/block-library/src/image/edit.js | 20 ++++++-- packages/block-library/src/image/test/edit.js | 50 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 packages/block-library/src/image/test/edit.js diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 270b168abc142b..ea948b51915b17 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -261,7 +261,7 @@ const ImageURLInputUI = ( { ); }; -class ImageEdit extends Component { +export class ImageEdit extends Component { constructor( { attributes } ) { super( ...arguments ); this.updateAlt = this.updateAlt.bind( this ); @@ -357,11 +357,23 @@ class ImageEdit extends Component { isEditing: false, } ); + const { id, url } = this.props.attributes; + let additionalAttributes; + // Reset the dimension attributes if changing to a different image. + if ( ! media.id || media.id !== id ) { + additionalAttributes = { + width: undefined, + height: undefined, + sizeSlug: DEFAULT_SIZE_SLUG, + }; + } else { + // Keep the same url when selecting the same file, so "Image Size" option is not changed. + additionalAttributes = { url }; + } + this.props.setAttributes( { ...pickRelevantMediaFiles( media ), - width: undefined, - height: undefined, - sizeSlug: DEFAULT_SIZE_SLUG, + ...additionalAttributes, } ); } diff --git a/packages/block-library/src/image/test/edit.js b/packages/block-library/src/image/test/edit.js new file mode 100644 index 00000000000000..506a3358a54e42 --- /dev/null +++ b/packages/block-library/src/image/test/edit.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import TestRenderer from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import { ImageEdit } from '../edit'; + +describe( 'core/image/edit', () => { + describe( 'onSelectImage', () => { + test( 'should reset dimensions when changing the image and keep them on selecting the same image', () => { + const attributes = { + id: 1, + url: 'http://www.example.com/myimage.jpeg', + alt: 'alt1', + }; + const setAttributes = jest.fn( () => {} ); + const testRenderer = TestRenderer.create( + <ImageEdit attributes={ attributes } setAttributes={ setAttributes } /> + ); + const instance = testRenderer.getInstance(); + + instance.onSelectImage( { + id: 1, + url: 'http://www.example.com/myimage.jpeg', + alt: 'alt2', + } ); + expect( setAttributes ).toHaveBeenCalledWith( { + id: 1, + url: 'http://www.example.com/myimage.jpeg', + alt: 'alt2', + } ); + instance.onSelectImage( { + id: 2, + url: 'http://www.example.com/myimage.jpeg', + alt: 'alt2', + } ); + expect( setAttributes ).toHaveBeenCalledWith( { + id: 2, + url: 'http://www.example.com/myimage.jpeg', + alt: 'alt2', + sizeSlug: 'large', + width: undefined, + height: undefined, + } ); + } ); + } ); +} ); From 907c96b18c5aa78d7ed32d1f6fd8e65a9528956b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 4 Jul 2019 20:10:37 +0100 Subject: [PATCH 437/664] Fix: Show pre-publish panel items for contributors (#16424) --- .../editor/src/components/post-publish-panel/prepublish.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index ba7967b479e021..9368db03008db2 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -56,11 +56,11 @@ function PostPublishPanelPrepublish( { ] }> <PostSchedule /> </PanelBody> - <MaybePostFormatPanel /> - <MaybeTagsPanel /> - { children } </> ) } + <MaybePostFormatPanel /> + <MaybeTagsPanel /> + { children } </div> ); } From 367480b63846719a4e39901349217fd110c5028e Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Thu, 4 Jul 2019 23:00:36 +0100 Subject: [PATCH 438/664] Add rudimentary support for table block cell scope attributes (#16154) * Add rudimentary support for table block cell scope attributes * Incorporate testing for scope property on table cells into full-content test * Revert "Incorporate testing for scope property on table cells into full-content test" This reverts commit 2805d1eceacd8f8d3453c07d2e77553cf5ca267f. * Use separate fixture for table scope tests * Ensure scope cannot be used on td elements * Attempt to get block transforms tests to pass --- packages/block-library/src/table/block.json | 15 ++ packages/block-library/src/table/edit.js | 3 +- packages/block-library/src/table/save.js | 3 +- .../block-library/src/table/transforms.js | 1 + .../e2e-tests/fixtures/block-transforms.js | 6 + .../blocks/core__table__scope-attribute.html | 3 + .../blocks/core__table__scope-attribute.json | 148 ++++++++++++++++++ .../core__table__scope-attribute.parsed.json | 20 +++ ...re__table__scope-attribute.serialized.html | 3 + 9 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 1e2d1ebc35abcf..a96913bbc12b5b 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -29,6 +29,11 @@ "type": "string", "default": "td", "source": "tag" + }, + "scope": { + "type": "string", + "source": "attribute", + "attribute": "scope" } } } @@ -54,6 +59,11 @@ "type": "string", "default": "td", "source": "tag" + }, + "scope": { + "type": "string", + "source": "attribute", + "attribute": "scope" } } } @@ -79,6 +89,11 @@ "type": "string", "default": "td", "source": "tag" + }, + "scope": { + "type": "string", + "source": "attribute", + "attribute": "scope" } } } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 97d94335afb85a..38a7c02e54a81f 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -363,7 +363,7 @@ export class TableEdit extends Component { <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag: CellTag }, columnIndex ) => { + { cells.map( ( { content, tag: CellTag, scope }, columnIndex ) => { const isSelected = selectedCell && ( type === selectedCell.section && rowIndex === selectedCell.rowIndex && @@ -382,6 +382,7 @@ export class TableEdit extends Component { <CellTag key={ columnIndex } className={ cellClasses } + scope={ CellTag === 'th' ? scope : undefined } > <RichText className="wp-block-table__cell-content" diff --git a/packages/block-library/src/table/save.js b/packages/block-library/src/table/save.js index 110a42724b933e..14ac51ff299b7b 100644 --- a/packages/block-library/src/table/save.js +++ b/packages/block-library/src/table/save.js @@ -40,11 +40,12 @@ export default function save( { attributes } ) { <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag }, cellIndex ) => + { cells.map( ( { content, tag, scope }, cellIndex ) => <RichText.Content tagName={ tag } value={ content } key={ cellIndex } + scope={ tag === 'th' ? scope : undefined } /> ) } </tr> diff --git a/packages/block-library/src/table/transforms.js b/packages/block-library/src/table/transforms.js index 13be7752ac7fd1..e002dec2219150 100644 --- a/packages/block-library/src/table/transforms.js +++ b/packages/block-library/src/table/transforms.js @@ -10,6 +10,7 @@ const tableContentPasteSchema = { th: { allowEmpty: true, children: getPhrasingContentSchema(), + attributes: [ 'scope' ], }, td: { allowEmpty: true, diff --git a/packages/e2e-tests/fixtures/block-transforms.js b/packages/e2e-tests/fixtures/block-transforms.js index 7391c1bc7adb90..3dd876b460a998 100644 --- a/packages/e2e-tests/fixtures/block-transforms.js +++ b/packages/e2e-tests/fixtures/block-transforms.js @@ -416,6 +416,12 @@ export const EXPECTED_TRANSFORMS = { 'Group', ], }, + 'core__table__scope-attribute': { + originalBlock: 'Table', + availableTransforms: [ + 'Group', + ], + }, 'core__tag-cloud': { originalBlock: 'Tag Cloud', availableTransforms: [ diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html new file mode 100644 index 00000000000000..4a5175c4c07d41 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html @@ -0,0 +1,3 @@ +<!-- wp:table --> +<table class="wp-block-table"><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json new file mode 100644 index 00000000000000..96e101aa0264c9 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json @@ -0,0 +1,148 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/table", + "isValid": true, + "attributes": { + "hasFixedLayout": false, + "head": [ + { + "cells": [ + { + "content": "Version", + "tag": "th", + "scope": "col" + }, + { + "content": "Musician", + "tag": "th", + "scope": "col" + }, + { + "content": "Date", + "tag": "th", + "scope": "col" + } + ] + } + ], + "body": [ + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a>", + "tag": "td" + }, + { + "content": "No musician chosen.", + "tag": "td" + }, + { + "content": "May 27, 2003", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a>", + "tag": "td" + }, + { + "content": "Miles Davis", + "tag": "td" + }, + { + "content": "January 3, 2004", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a>", + "tag": "td" + }, + { + "content": "…", + "tag": "td" + }, + { + "content": "…", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a>", + "tag": "td" + }, + { + "content": "Clifford Brown", + "tag": "td" + }, + { + "content": "December 8, 2015", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a>", + "tag": "td" + }, + { + "content": "Coleman Hawkins", + "tag": "td" + }, + { + "content": "April 12, 2016", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a>", + "tag": "td" + }, + { + "content": "Pepper Adams", + "tag": "td" + }, + { + "content": "August 16, 2016", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a>", + "tag": "td" + }, + { + "content": "Sarah Vaughan", + "tag": "td" + }, + { + "content": "December 6, 2016", + "tag": "td" + } + ] + } + ], + "foot": [] + }, + "innerBlocks": [], + "originalContent": "<table class=\"wp-block-table\"><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json new file mode 100644 index 00000000000000..e8410b63327af8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/table", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<table class=\"wp-block-table\"><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n", + "innerContent": [ + "\n<table class=\"wp-block-table\"><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html new file mode 100644 index 00000000000000..4a5175c4c07d41 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:table --> +<table class="wp-block-table"><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<!-- /wp:table --> From 21e2dce2b287347e21c3e0618bc8fa8c0c1ef5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 5 Jul 2019 09:36:48 +0200 Subject: [PATCH 439/664] Merging blocks: clone blocks to safely insert selection tracking character (#16419) * Merging blocks: clone blocks to safely insert selection character * Update tests --- packages/block-editor/src/store/effects.js | 26 ++++++++----- .../block-editor/src/store/test/effects.js | 39 +++++++++++-------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js index 7cd87ee731114c..02d766f50f7a62 100644 --- a/packages/block-editor/src/store/effects.js +++ b/packages/block-editor/src/store/effects.js @@ -12,6 +12,7 @@ import { doBlocksMatchTemplate, switchToBlockType, synchronizeBlocksWithTemplate, + cloneBlock, } from '@wordpress/blocks'; import { _n, sprintf } from '@wordpress/i18n'; import { create, toHTMLString, insert, remove } from '@wordpress/rich-text'; @@ -83,15 +84,23 @@ export default { const blockB = getBlock( state, clientIdB ); const blockBType = getBlockType( blockB.name ); const { clientId, attributeKey, offset } = getSelectionStart( state ); - const hasSelection = clientId === clientIdA || clientId === clientIdB; - const selectedBlock = clientId === clientIdA ? blockA : blockB; - const html = selectedBlock.attributes[ attributeKey ]; + const hasTextSelection = ( + ( clientId === clientIdA || clientId === clientIdB ) && + attributeKey !== undefined && + offset !== undefined + ); // A robust way to retain selection position through various transforms // is to insert a special character at the position and then recover it. const START_OF_SELECTED_AREA = '\u0086'; - if ( hasSelection ) { + // Clone the blocks so we don't insert the character in a "live" block. + const cloneA = cloneBlock( blockA ); + const cloneB = cloneBlock( blockB ); + + if ( hasTextSelection ) { + const selectedBlock = clientId === clientIdA ? cloneA : cloneB; + const html = selectedBlock.attributes[ attributeKey ]; const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; const multilineTag = selectedBlockType.attributes[ attributeKey ].multiline; const value = insert( create( { @@ -108,8 +117,8 @@ export default { // We can only merge blocks with similar types // thus, we transform the block to merge first const blocksWithTheSameType = blockA.name === blockB.name ? - [ blockB ] : - switchToBlockType( blockB, blockA.name ); + [ cloneB ] : + switchToBlockType( cloneB, blockA.name ); // If the block types can not match, do nothing if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { @@ -118,11 +127,11 @@ export default { // Calling the merge to update the attributes and remove the block to be merged const updatedAttributes = blockAType.merge( - blockA.attributes, + cloneA.attributes, blocksWithTheSameType[ 0 ].attributes ); - if ( hasSelection ) { + if ( hasTextSelection ) { const newAttributeKey = findKey( updatedAttributes, ( v ) => typeof v === 'string' && v.indexOf( START_OF_SELECTED_AREA ) !== -1 ); @@ -134,7 +143,6 @@ export default { const newHtml = toHTMLString( { value: newValue, multilineTag } ); updatedAttributes[ newAttributeKey ] = newHtml; - selectedBlock.attributes[ attributeKey ] = html; dispatch( selectionChange( blockA.clientId, diff --git a/packages/block-editor/src/store/test/effects.js b/packages/block-editor/src/store/test/effects.js index b2a0d90dba6d02..827dde0d39b5c1 100644 --- a/packages/block-editor/src/store/test/effects.js +++ b/packages/block-editor/src/store/test/effects.js @@ -2,6 +2,7 @@ * External dependencies */ import { noop } from 'lodash'; +import deepFreeze from 'deep-freeze'; /** * WordPress dependencies @@ -55,14 +56,14 @@ describe( 'effects', () => { it( 'should only focus the blockA if the blockA has no merge function', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); - const blockA = { + const blockA = deepFreeze( { clientId: 'chicken', name: 'core/test-block', - }; - const blockB = { + } ); + const blockB = deepFreeze( { clientId: 'ribs', name: 'core/test-block', - }; + } ); selectors.getBlock = ( state, clientId ) => { return blockA.clientId === clientId ? blockA : blockB; }; @@ -89,16 +90,18 @@ describe( 'effects', () => { category: 'common', title: 'test block', } ); - const blockA = { + const blockA = deepFreeze( { clientId: 'chicken', name: 'core/test-block', attributes: { content: 'chicken' }, - }; - const blockB = { + innerBlocks: [], + } ); + const blockB = deepFreeze( { clientId: 'ribs', name: 'core/test-block', attributes: { content: 'ribs' }, - }; + innerBlocks: [], + } ); selectors.getBlock = ( state, clientId ) => { return blockA.clientId === clientId ? blockA : blockB; }; @@ -151,16 +154,18 @@ describe( 'effects', () => { title: 'test block', } ); registerBlockType( 'core/test-block-2', defaultBlockSettings ); - const blockA = { + const blockA = deepFreeze( { clientId: 'chicken', name: 'core/test-block', attributes: { content: 'chicken' }, - }; - const blockB = { + innerBlocks: [], + } ); + const blockB = deepFreeze( { clientId: 'ribs', name: 'core/test-block-2', attributes: { content: 'ribs' }, - }; + innerBlocks: [], + } ); selectors.getBlock = ( state, clientId ) => { return blockA.clientId === clientId ? blockA : blockB; }; @@ -216,16 +221,18 @@ describe( 'effects', () => { category: 'common', title: 'test block 2', } ); - const blockA = { + const blockA = deepFreeze( { clientId: 'chicken', name: 'core/test-block', attributes: { content: 'chicken' }, - }; - const blockB = { + innerBlocks: [], + } ); + const blockB = deepFreeze( { clientId: 'ribs', name: 'core/test-block-2', attributes: { content2: 'ribs' }, - }; + innerBlocks: [], + } ); selectors.getBlock = ( state, clientId ) => { return blockA.clientId === clientId ? blockA : blockB; }; From ab847cb2a05a1e484f735a92789ef73742b89302 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 5 Jul 2019 17:18:18 +0100 Subject: [PATCH 440/664] Track the parent block to optimize hierarchy selectors (#16392) --- packages/block-editor/src/store/reducer.js | 124 +++++- packages/block-editor/src/store/selectors.js | 45 +-- .../block-editor/src/store/test/reducer.js | 188 ++++++--- .../block-editor/src/store/test/selectors.js | 361 +++++++++++++++++- 4 files changed, 607 insertions(+), 111 deletions(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index d9d0ecbdd28b64..a2df297f3e1365 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -54,6 +54,23 @@ function mapBlockOrder( blocks, rootClientId = '' ) { return result; } +/** + * Given an array of blocks, returns an object where each key contains + * the clientId of the block and the value is the parent of the block. + * + * @param {Array} blocks Blocks to map. + * @param {?string} rootClientId Assumed root client ID. + * + * @return {Object} Block order map object. + */ +function mapBlockParents( blocks, rootClientId = '' ) { + return blocks.reduce( ( result, block ) => Object.assign( + result, + { [ block.clientId ]: rootClientId }, + mapBlockParents( block.innerBlocks, block.clientId ) + ), {} ); +} + /** * Helper method to iterate through all blocks, recursing into inner blocks, * applying a transformation function to each one. @@ -264,16 +281,40 @@ function withIgnoredBlockChange( reducer ) { * @return {Function} Enhanced reducer function. */ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { - if ( state && action.type === 'REMOVE_BLOCKS' ) { - const clientIds = [ ...action.clientIds ]; + const getAllChildren = ( clientIds ) => { + let result = clientIds; + for ( let i = 0; i < result.length; i++ ) { + if ( ! state.order[ result[ i ] ] ) { + continue; + } - // For each removed client ID, include its inner blocks to remove, - // recursing into those so long as inner blocks exist. - for ( let i = 0; i < clientIds.length; i++ ) { - clientIds.push( ...state.order[ clientIds[ i ] ] ); + if ( result === clientIds ) { + result = [ ...result ]; + } + + result.push( ...state.order[ result[ i ] ] ); } - action = { ...action, clientIds }; + return result; + }; + + if ( state ) { + switch ( action.type ) { + case 'REMOVE_BLOCKS': + action = { + ...action, + type: 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN', + removedClientIds: getAllChildren( action.clientIds ), + }; + break; + case 'REPLACE_BLOCKS': + action = { + ...action, + type: 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN', + replacedClientIds: getAllChildren( action.clientIds ), + }; + break; + } } return reducer( state, action ); @@ -306,6 +347,10 @@ const withBlockReset = ( reducer ) => ( state, action ) => { ...omit( state.order, visibleClientIds ), ...mapBlockOrder( action.blocks ), }, + parents: { + ...omit( state.parents, visibleClientIds ), + ...mapBlockParents( action.blocks ), + }, }; } @@ -435,18 +480,18 @@ export const blocks = flow( ...getFlattenedBlocksWithoutAttributes( action.blocks ), }; - case 'REPLACE_BLOCKS': + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': if ( ! action.blocks ) { return state; } return { - ...omit( state, action.clientIds ), + ...omit( state, action.replacedClientIds ), ...getFlattenedBlocksWithoutAttributes( action.blocks ), }; - case 'REMOVE_BLOCKS': - return omit( state, action.clientIds ); + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return omit( state, action.removedClientIds ); } return state; @@ -511,18 +556,18 @@ export const blocks = flow( ...getFlattenedBlockAttributes( action.blocks ), }; - case 'REPLACE_BLOCKS': + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': if ( ! action.blocks ) { return state; } return { - ...omit( state, action.clientIds ), + ...omit( state, action.replacedClientIds ), ...getFlattenedBlockAttributes( action.blocks ), }; - case 'REMOVE_BLOCKS': - return omit( state, action.clientIds ); + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return omit( state, action.removedClientIds ); } return state; @@ -609,7 +654,7 @@ export const blocks = flow( }; } - case 'REPLACE_BLOCKS': { + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': { const { clientIds } = action; if ( ! action.blocks ) { return state; @@ -618,7 +663,7 @@ export const blocks = flow( const mappedBlocks = mapBlockOrder( action.blocks ); return flow( [ - ( nextState ) => omit( nextState, clientIds ), + ( nextState ) => omit( nextState, action.replacedClientIds ), ( nextState ) => ( { ...nextState, ...omit( mappedBlocks, '' ), @@ -642,20 +687,59 @@ export const blocks = flow( ] )( state ); } - case 'REMOVE_BLOCKS': + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': return flow( [ // Remove inner block ordering for removed blocks - ( nextState ) => omit( nextState, action.clientIds ), + ( nextState ) => omit( nextState, action.removedClientIds ), // Remove deleted blocks from other blocks' orderings ( nextState ) => mapValues( nextState, ( subState ) => ( - without( subState, ...action.clientIds ) + without( subState, ...action.removedClientIds ) ) ), ] )( state ); } return state; }, + + // While technically redundant data as the inverse of `order`, it serves as + // an optimization for the selectors which derive the ancestry of a block. + parents( state = {}, action ) { + switch ( action.type ) { + case 'RESET_BLOCKS': + return mapBlockParents( action.blocks ); + + case 'RECEIVE_BLOCKS': + return { + ...state, + ...mapBlockParents( action.blocks ), + }; + + case 'INSERT_BLOCKS': + return { + ...state, + ...mapBlockParents( action.blocks, action.rootClientId || '' ), + }; + + case 'MOVE_BLOCK_TO_POSITION': { + return { + ...state, + [ action.clientId ]: action.toRootClientId || '', + }; + } + + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return { + ...omit( state, action.replacedClientIds ), + ...mapBlockParents( action.blocks, state[ action.clientIds[ 0 ] ] ), + }; + + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + return omit( state, action.removedClientIds ); + } + + return state; + }, } ); /** diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 6633611fbd9b19..21937e3bc3d8ae 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -458,22 +458,11 @@ export function getSelectedBlock( state ) { * * @return {?string} Root client ID, if exists */ -export const getBlockRootClientId = createSelector( - ( state, clientId ) => { - const { order } = state.blocks; - - for ( const rootClientId in order ) { - if ( includes( order[ rootClientId ], clientId ) ) { - return rootClientId; - } - } - - return null; - }, - ( state ) => [ - state.blocks.order, - ] -); +export function getBlockRootClientId( state, clientId ) { + return state.blocks.parents[ clientId ] !== undefined ? + state.blocks.parents[ clientId ] : + null; +} /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. @@ -483,21 +472,15 @@ export const getBlockRootClientId = createSelector( * * @return {string} Root client ID */ -export const getBlockHierarchyRootClientId = createSelector( - ( state, clientId ) => { - let rootClientId = clientId; - let current = clientId; - while ( rootClientId ) { - current = rootClientId; - rootClientId = getBlockRootClientId( state, current ); - } - - return current; - }, - ( state ) => [ - state.blocks.order, - ] -); +export function getBlockHierarchyRootClientId( state, clientId ) { + let current = clientId; + let parent; + do { + parent = current; + current = state.blocks.parents[ current ]; + } while ( current ); + return parent; +} /** * Returns the client ID of the block adjacent one at the given reference diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 7543fdad57dd8b..90116e78052549 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -193,27 +193,31 @@ describe( 'state', () => { it( 'can replace a child block', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, - 'clicken-child': { + 'chicken-child': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, }, attributes: { - clicken: {}, - 'clicken-child': { + chicken: {}, + 'chicken-child': { attr: true, }, }, order: { - '': [ 'clicken' ], - clicken: [ 'clicken-child' ], - 'clicken-child': [], + '': [ 'chicken' ], + chicken: [ 'chicken-child' ], + 'chicken-child': [], + }, + parents: { + chicken: '', + 'chicken-child': 'chicken', }, } ); @@ -226,7 +230,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock ], }; @@ -236,7 +240,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -248,35 +252,42 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId ]: { attr: false, attr2: 'perfect', }, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId ], + '': [ 'chicken' ], + chicken: [ newChildBlockId ], [ newChildBlockId ]: [], }, + parents: { + [ newChildBlockId ]: 'chicken', + chicken: '', + }, } ); } ); it( 'can insert a child block', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, }, attributes: { - clicken: {}, + chicken: {}, }, order: { - '': [ 'clicken' ], - clicken: [], + '': [ 'chicken' ], + chicken: [], + }, + parents: { + chicken: '', }, } ); @@ -289,7 +300,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock ], }; @@ -299,7 +310,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -311,53 +322,62 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId ]: { attr: false, attr2: 'perfect', }, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId ], + '': [ 'chicken' ], + chicken: [ newChildBlockId ], [ newChildBlockId ]: [], }, + parents: { + [ newChildBlockId ]: 'chicken', + chicken: '', + }, } ); } ); it( 'can replace multiple child blocks', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, - 'clicken-child': { + 'chicken-child': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, - 'clicken-child-2': { + 'chicken-child-2': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, }, attributes: { - clicken: {}, - 'clicken-child': { + chicken: {}, + 'chicken-child': { attr: true, }, - 'clicken-child-2': { + 'chicken-child-2': { attr2: 'ok', }, }, order: { - '': [ 'clicken' ], - clicken: [ 'clicken-child', 'clicken-child-2' ], - 'clicken-child': [], - 'clicken-child-2': [], + '': [ 'chicken' ], + chicken: [ 'chicken-child', 'chicken-child-2' ], + 'chicken-child': [], + 'chicken-child-2': [], + }, + parents: { + chicken: '', + 'chicken-child': 'chicken', + 'chicken-child-2': 'chicken', }, } ); @@ -381,7 +401,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock1, newChildBlock2, newChildBlock3 ], }; @@ -391,7 +411,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -413,7 +433,7 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId1 ]: { attr: false, attr2: 'perfect', @@ -427,44 +447,55 @@ describe( 'state', () => { }, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId1, newChildBlockId2, newChildBlockId3 ], + '': [ 'chicken' ], + chicken: [ newChildBlockId1, newChildBlockId2, newChildBlockId3 ], [ newChildBlockId1 ]: [], [ newChildBlockId2 ]: [], [ newChildBlockId3 ]: [], }, + parents: { + chicken: '', + [ newChildBlockId1 ]: 'chicken', + [ newChildBlockId2 ]: 'chicken', + [ newChildBlockId3 ]: 'chicken', + }, } ); } ); it( 'can replace a child block that has other children', () => { const existingState = deepFreeze( { byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, }, - 'clicken-child': { + 'chicken-child': { clientId: 'chicken-child', name: 'core/test-child-block', isValid: true, }, - 'clicken-grand-child': { + 'chicken-grand-child': { clientId: 'chicken-child', name: 'core/test-block', isValid: true, }, }, attributes: { - clicken: {}, - 'clicken-child': {}, - 'clicken-grand-child': {}, + chicken: {}, + 'chicken-child': {}, + 'chicken-grand-child': {}, }, order: { - '': [ 'clicken' ], - clicken: [ 'clicken-child' ], - 'clicken-child': [ 'clicken-grand-child' ], - 'clicken-grand-child': [], + '': [ 'chicken' ], + chicken: [ 'chicken-child' ], + 'chicken-child': [ 'chicken-grand-child' ], + 'chicken-grand-child': [], + }, + parents: { + chicken: '', + 'chicken-child': 'chicken', + 'chicken-grand-child': 'chicken-child', }, } ); @@ -474,7 +505,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_INNER_BLOCKS', - rootClientId: 'clicken', + rootClientId: 'chicken', blocks: [ newChildBlock ], }; @@ -484,7 +515,7 @@ describe( 'state', () => { isPersistentChange: true, isIgnoredChange: false, byClientId: { - clicken: { + chicken: { clientId: 'chicken', name: 'core/test-parent-block', isValid: true, @@ -496,14 +527,18 @@ describe( 'state', () => { }, }, attributes: { - clicken: {}, + chicken: {}, [ newChildBlockId ]: {}, }, order: { - '': [ 'clicken' ], - clicken: [ newChildBlockId ], + '': [ 'chicken' ], + chicken: [ newChildBlockId ], [ newChildBlockId ]: [], }, + parents: { + chicken: '', + [ newChildBlockId ]: 'chicken', + }, } ); } ); } ); @@ -515,6 +550,7 @@ describe( 'state', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, isPersistentChange: true, isIgnoredChange: false, } ); @@ -612,6 +648,45 @@ describe( 'state', () => { '': [ 'wings' ], wings: [], } ); + expect( state.parents ).toEqual( { + wings: '', + } ); + } ); + it( 'should replace the block and remove references to its inner blocks', () => { + const original = blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [ + { + clientId: 'child', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], + } ], + } ); + const state = blocks( original, { + type: 'REPLACE_BLOCKS', + clientIds: [ 'chicken' ], + blocks: [ { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], + } ], + } ); + + expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); + expect( state.order ).toEqual( { + '': [ 'wings' ], + wings: [], + } ); + expect( state.parents ).toEqual( { + wings: '', + } ); } ); it( 'should replace the nested block', () => { @@ -634,6 +709,10 @@ describe( 'state', () => { [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], [ replacementBlock.clientId ]: [], } ); + expect( state.parents ).toEqual( { + [ wrapperBlock.clientId ]: '', + [ replacementBlock.clientId ]: wrapperBlock.clientId, + } ); } ); it( 'should replace the block even if the new block clientId is the same', () => { @@ -1024,6 +1103,9 @@ describe( 'state', () => { expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); expect( state.order ).not.toHaveProperty( 'chicken' ); + expect( state.parents ).toEqual( { + ribs: '', + } ); expect( state.byClientId ).toEqual( { ribs: { clientId: 'ribs', @@ -1063,6 +1145,9 @@ describe( 'state', () => { expect( state.order[ '' ] ).toEqual( [ 'ribs' ] ); expect( state.order ).not.toHaveProperty( 'chicken' ); expect( state.order ).not.toHaveProperty( 'veggies' ); + expect( state.parents ).toEqual( { + ribs: '', + } ); expect( state.byClientId ).toEqual( { ribs: { clientId: 'ribs', @@ -1095,6 +1180,7 @@ describe( 'state', () => { expect( state.order ).toEqual( { '': [], } ); + expect( state.parents ).toEqual( {} ); } ); it( 'should insert at the specified index', () => { diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index c038a40621eff6..bbb61fbfc04ecf 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -156,6 +156,9 @@ describe( 'selectors', () => { '': rootOrder, 123: rootBlockOrder, }, + parents: { + 123: '', + }, }, }; @@ -171,6 +174,9 @@ describe( 'selectors', () => { '': rootOrder, 123: rootBlockOrder, }, + parents: { + 123: '', + }, }, }; @@ -192,6 +198,9 @@ describe( 'selectors', () => { '': rootOrder, 123: [], }, + parents: { + 123: '', + }, }, }; @@ -210,6 +219,10 @@ describe( 'selectors', () => { 123: [ 456 ], 456: [], }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -239,6 +252,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -257,6 +274,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -284,6 +305,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -302,6 +327,10 @@ describe( 'selectors', () => { 123: rootBlockOrder, 456: childBlockOrder, }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -335,6 +364,11 @@ describe( 'selectors', () => { 456: childBlockOrder, 789: grandChildBlockOrder, }, + parents: { + 123: '', + 456: 123, + 789: 456, + }, }, }; @@ -356,6 +390,11 @@ describe( 'selectors', () => { 456: childBlockOrder, 789: grandChildBlockOrder, }, + parents: { + 123: '', + 456: 123, + 789: 456, + }, }, }; @@ -372,6 +411,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, }; @@ -396,6 +436,9 @@ describe( 'selectors', () => { '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], }, + parents: { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': '', + }, }, }; @@ -419,6 +462,9 @@ describe( 'selectors', () => { '': [ 123 ], 123: [], }, + parents: { + 123: '', + }, }, }; @@ -436,6 +482,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, }; @@ -458,6 +505,10 @@ describe( 'selectors', () => { 123: [ 456 ], 456: [], }, + parents: { + 123: '', + 456: 123, + }, }, }; @@ -507,6 +558,9 @@ describe( 'selectors', () => { '': [ 123 ], 123: [], }, + parents: { + 123: '', + }, }, }; @@ -538,6 +592,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 123: '', + 23: '', + }, }, }; @@ -603,6 +661,20 @@ describe( 'selectors', () => { 'uuid-26': [ ], 'uuid-28': [ 'uuid-30' ], }, + parents: { + 'uuid-6': '', + 'uuid-8': '', + 'uuid-10': '', + 'uuid-22': '', + 'uuid-12': 'uuid-10', + 'uuid-14': 'uuid-10', + 'uuid-16': 'uuid-12', + 'uuid-18': 'uuid-14', + 'uuid-24': 'uuid-18', + 'uuid-26': 'uuid-24', + 'uuid-28': 'uuid-24', + 'uuid-30': 'uuid-28', + }, }, }; expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual( [ @@ -673,6 +745,20 @@ describe( 'selectors', () => { 'uuid-26': [ ], 'uuid-28': [ 'uuid-30' ], }, + parents: { + 'uuid-6': '', + 'uuid-8': '', + 'uuid-10': '', + 'uuid-22': '', + 'uuid-12': 'uuid-10', + 'uuid-14': 'uuid-10', + 'uuid-16': 'uuid-12', + 'uuid-18': 'uuid-14', + 'uuid-24': 'uuid-18', + 'uuid-26': 'uuid-24', + 'uuid-28': 'uuid-24', + 'uuid-30': 'uuid-28', + }, }, }; expect( getClientIdsWithDescendants( state ) ).toEqual( [ @@ -730,6 +816,11 @@ describe( 'selectors', () => { '': [ 123 ], 123: [ 456, 789 ], }, + parents: { + 123: '', + 456: 123, + 789: 123, + }, }, }; @@ -788,6 +879,10 @@ describe( 'selectors', () => { order: { '': [ 123, 456 ], }, + parents: { + 123: '', + 456: '', + }, }, }; @@ -805,6 +900,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, }; expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); @@ -862,6 +958,10 @@ describe( 'selectors', () => { 23: [], 123: [], }, + parents: { + 23: '', + 123: '', + }, }, blockSelection: { start: {}, end: {} }, }; @@ -885,6 +985,10 @@ describe( 'selectors', () => { 23: [], 123: [], }, + parents: { + 123: '', + 23: '', + }, }, blockSelection: { start: { clientId: 23 }, end: { clientId: 123 } }, }; @@ -908,6 +1012,10 @@ describe( 'selectors', () => { 23: [], 123: [], }, + parents: { + 123: '', + 23: '', + }, }, blockSelection: { start: { clientId: 23 }, end: { clientId: 23 } }, }; @@ -926,6 +1034,7 @@ describe( 'selectors', () => { const state = { blocks: { order: {}, + parents: {}, }, }; @@ -939,10 +1048,16 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 123: '', + 23: '', + 456: 123, + 56: 123, + }, }, }; - expect( getBlockRootClientId( state, 56 ) ).toBe( '123' ); + expect( getBlockRootClientId( state, 56 ) ).toBe( 123 ); } ); } ); @@ -951,37 +1066,51 @@ describe( 'selectors', () => { const state = { blocks: { order: {}, + parents: {}, }, }; - expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( 56 ); + expect( getBlockHierarchyRootClientId( state, '56' ) ).toBe( '56' ); } ); it( 'should return root ClientId relative the block ClientId', () => { const state = { blocks: { order: { - '': [ 123, 23 ], - 123: [ 456, 56 ], + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', }, }, }; - expect( getBlockHierarchyRootClientId( state, 56 ) ).toBe( '123' ); + expect( getBlockHierarchyRootClientId( state, 'c' ) ).toBe( 'a' ); } ); it( 'should return the top level root ClientId relative the block ClientId', () => { const state = { blocks: { order: { - '': [ '123', '23' ], - 123: [ '456', '56' ], - 56: [ '12' ], + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', }, }, }; - expect( getBlockHierarchyRootClientId( state, '12' ) ).toBe( '123' ); + expect( getBlockHierarchyRootClientId( state, 'e' ) ).toBe( 'a' ); } ); } ); @@ -992,6 +1121,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 123: '', + 23: '', + }, }, blockSelection: { start: {}, end: {} }, }; @@ -1005,6 +1138,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 2 } }, }; @@ -1018,6 +1158,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1032,6 +1179,17 @@ describe( 'selectors', () => { '': [ 5, 4, 3, 2, 1 ], 4: [ 9, 8, 7, 6 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + 6: 4, + 7: 4, + 8: 4, + 9: 4, + }, }, blockSelection: { start: { clientId: 7 }, end: { clientId: 9 } }, }; @@ -1047,6 +1205,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, blockSelection: { start: {}, end: {} }, }; @@ -1060,6 +1222,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1074,6 +1243,17 @@ describe( 'selectors', () => { '': [ 5, 4, 3, 2, 1 ], 4: [ 9, 8, 7, 6 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + 6: 4, + 7: 4, + 8: 4, + 9: 4, + }, }, blockSelection: { start: { clientId: 7 }, end: { clientId: 9 } }, }; @@ -1089,6 +1269,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, blockSelection: { start: {}, end: {} }, }; @@ -1142,6 +1323,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1155,6 +1340,11 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + }, }, }; @@ -1169,6 +1359,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1182,6 +1376,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 56: 123, + 456: 123, + }, }, }; @@ -1196,6 +1396,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1209,6 +1413,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1221,6 +1431,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1234,6 +1448,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1248,6 +1468,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1261,6 +1485,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1273,6 +1503,10 @@ describe( 'selectors', () => { order: { '': [ 123, 23 ], }, + parents: { + 23: '', + 123: '', + }, }, }; @@ -1286,6 +1520,12 @@ describe( 'selectors', () => { '': [ 123, 23 ], 123: [ 456, 56 ], }, + parents: { + 23: '', + 123: '', + 456: 123, + 56: 123, + }, }, }; @@ -1327,6 +1567,11 @@ describe( 'selectors', () => { order: { 4: [ 3, 2, 1 ], }, + parents: { + 1: 4, + 2: 4, + 3: 4, + }, }, }; @@ -1340,6 +1585,11 @@ describe( 'selectors', () => { order: { 4: [ 3, 2, 1 ], }, + parents: { + 1: 4, + 2: 4, + 3: 4, + }, }, }; @@ -1352,6 +1602,13 @@ describe( 'selectors', () => { order: { 6: [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: 6, + 2: 6, + 3: 6, + 4: 6, + 5: 6, + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1365,6 +1622,12 @@ describe( 'selectors', () => { 3: [ 2, 1 ], 6: [ 5, 4 ], }, + parents: { + 1: 3, + 2: 3, + 4: 6, + 5: 6, + }, }, blockSelection: { start: { clientId: 5 }, end: { clientId: 4 } }, }; @@ -1380,6 +1643,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1393,6 +1663,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1406,6 +1683,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1419,6 +1703,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, }; @@ -1467,6 +1758,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1486,6 +1784,13 @@ describe( 'selectors', () => { order: { '': [ 5, 4, 3, 2, 1 ], }, + parents: { + 1: '', + 2: '', + 3: '', + 4: '', + 5: '', + }, }, blockSelection: { start: { clientId: 2 }, end: { clientId: 4 } }, }; @@ -1598,6 +1903,10 @@ describe( 'selectors', () => { clientId1: [ 'clientId2' ], clientId2: [], }, + parents: { + clientId1: '', + clientId2: 'clientId1', + }, }, insertionPoint: { rootClientId: undefined, @@ -1628,6 +1937,9 @@ describe( 'selectors', () => { '': [ 'clientId1' ], clientId1: [], }, + parents: { + clientId1: '', + }, }, insertionPoint: null, }; @@ -1658,6 +1970,10 @@ describe( 'selectors', () => { clientId1: [ 'clientId2' ], clientId2: [], }, + parents: { + clientId1: '', + clientId2: 'clientId1', + }, }, insertionPoint: null, }; @@ -1688,6 +2004,10 @@ describe( 'selectors', () => { clientId1: [], clientId2: [], }, + parents: { + clientId1: '', + clientId2: '', + }, }, insertionPoint: null, }; @@ -1718,6 +2038,10 @@ describe( 'selectors', () => { clientId1: [], clientId2: [], }, + parents: { + clientId1: '', + clientId2: '', + }, }, insertionPoint: null, }; @@ -1921,6 +2245,7 @@ describe( 'selectors', () => { block1: {}, }, order: {}, + parents: {}, }, settings: { __experimentalReusableBlocks: [ @@ -1991,6 +2316,9 @@ describe( 'selectors', () => { order: { '': [ 'block1ref' ], }, + parents: { + block1ref: '', + }, }, settings: { __experimentalReusableBlocks: [ @@ -2051,6 +2379,11 @@ describe( 'selectors', () => { referredBlock2: [ 'childReferredBlock2' ], childReferredBlock2: [ 'grandchildReferredBlock2' ], }, + parents: { + block2ref: '', + childReferredBlock2: 'referredBlock2', + grandchildReferredBlock2: 'childReferredBlock2', + }, }, settings: { @@ -2094,6 +2427,7 @@ describe( 'selectors', () => { block2: {}, }, order: {}, + parents: {}, }, settings: { __experimentalReusableBlocks: [ @@ -2137,6 +2471,10 @@ describe( 'selectors', () => { order: { '': [ 'block3', 'block4' ], }, + parents: { + block3: '', + block4: '', + }, }, settings: { __experimentalReusableBlocks: [ @@ -2214,6 +2552,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, preferences: { insertUsage: {}, @@ -2232,6 +2571,7 @@ describe( 'selectors', () => { byClientId: {}, attributes: {}, order: {}, + parents: {}, }, preferences: { insertUsage: { @@ -2259,6 +2599,9 @@ describe( 'selectors', () => { order: { '': [ 'block1' ], }, + parents: { + block1: '', + }, }, preferences: { insertUsage: {}, From 011fa750e33de1df5ae8b94d59a4ce90638902bc Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 5 Jul 2019 17:57:19 +0100 Subject: [PATCH 441/664] Adds a cache key to the blocks reducer in order to optimize the getBlock selector (#16407) --- .../developers/data/data-core-block-editor.md | 18 - packages/block-editor/src/store/reducer.js | 193 ++++++++++- packages/block-editor/src/store/selectors.js | 30 +- .../block-editor/src/store/test/reducer.js | 88 ++++- .../block-editor/src/store/test/selectors.js | 311 +++--------------- 5 files changed, 313 insertions(+), 327 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 6e496446d6be20..1b42d76df3e3b4 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -80,24 +80,6 @@ _Returns_ - `number`: Number of blocks in the post. -<a name="getBlockDependantsCacheBust" href="#getBlockDependantsCacheBust">#</a> **getBlockDependantsCacheBust** - -Returns a new reference when the inner blocks of a given block client ID -change. This is used exclusively as a memoized selector dependant, relying -on this selector's shared return value and recursively those of its inner -blocks defined as dependencies. This abuses mechanics of the selector -memoization to return from the original selector function only when -dependants change. - -_Parameters_ - -- _state_ `Object`: Editor state. -- _clientId_ `string`: Block client ID. - -_Returns_ - -- `*`: A value whose reference will change only when inner blocks of the given block client ID change. - <a name="getBlockHierarchyRootClientId" href="#getBlockHierarchyRootClientId">#</a> **getBlockHierarchyRootClientId** Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index a2df297f3e1365..b4cd52a08886e3 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -13,6 +13,9 @@ import { isEqual, isEmpty, get, + identity, + difference, + omitBy, } from 'lodash'; /** @@ -81,7 +84,7 @@ function mapBlockParents( blocks, rootClientId = '' ) { * * @return {Object} Flattened object. */ -function flattenBlocks( blocks, transform ) { +function flattenBlocks( blocks, transform = identity ) { const result = {}; const stack = [ ...blocks ]; @@ -192,6 +195,160 @@ export function isUpdatingSameBlockAttribute( action, lastAction ) { ); } +/** + * Higher-order reducer intended to reset the cache key of all blocks + * whenever the post meta values change. + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +const withPostMetaUpdateCacheReset = ( reducer ) => ( state, action ) => { + const newState = reducer( state, action ); + const previousMetaValues = get( state, [ 'settings', '__experimentalMetaSource', 'value' ] ); + const nextMetaValues = get( newState.settings.__experimentalMetaSource, [ 'value' ] ); + // If post meta values change, reset the cache key for all blocks + if ( previousMetaValues !== nextMetaValues ) { + newState.blocks = { + ...newState.blocks, + cache: mapValues( newState.blocks.cache, () => ( {} ) ), + }; + } + + return newState; +}; + +/** + * Utility returning an object with an empty object value for each key. + * + * @param {Array} objectKeys Keys to fill. + * @return {Object} Object filled with empty object as values for each clientId. + */ +const fillKeysWithEmptyObject = ( objectKeys ) => { + return objectKeys.reduce( ( result, key ) => { + result[ key ] = {}; + return result; + }, {} ); +}; + +/** + * Higher-order reducer intended to compute a cache key for each block in the post. + * A new instance of the cache key (empty object) is created each time the block object + * needs to be refreshed (for any change in the block or its children). + * + * @param {Function} reducer Original reducer function. + * + * @return {Function} Enhanced reducer function. + */ +const withBlockCache = ( reducer ) => ( state = {}, action ) => { + const newState = reducer( state, action ); + + if ( newState === state ) { + return state; + } + newState.cache = state.cache ? state.cache : {}; + + const getBlocksWithParentsClientIds = ( clientIds ) => { + return clientIds.reduce( ( result, clientId ) => { + let current = clientId; + do { + result.push( current ); + current = state.parents[ current ]; + } while ( current ); + return result; + }, [] ); + }; + + switch ( action.type ) { + case 'RESET_BLOCKS': + newState.cache = mapValues( flattenBlocks( action.blocks ), () => ( {} ) ); + break; + case 'RECEIVE_BLOCKS': + case 'INSERT_BLOCKS': { + const updatedBlockUids = keys( flattenBlocks( action.blocks ) ); + if ( action.rootClientId ) { + updatedBlockUids.push( action.rootClientId ); + } + newState.cache = { + ...newState.cache, + ...fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( updatedBlockUids ), + ), + }; + break; + } + case 'UPDATE_BLOCK': + case 'UPDATE_BLOCK_ATTRIBUTES': + newState.cache = { + ...newState.cache, + ...fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( [ action.clientId ] ), + ), + }; + break; + case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': + newState.cache = { + ...omit( newState.cache, action.replacedClientIds ), + ...fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( keys( flattenBlocks( action.blocks ) ) ), + ), + }; + break; + case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': + newState.cache = { + ...omit( newState.cache, action.removedClientIds ), + ...fillKeysWithEmptyObject( + difference( getBlocksWithParentsClientIds( action.clientIds ), action.clientIds ), + ), + }; + break; + case 'MOVE_BLOCK_TO_POSITION': { + const updatedBlockUids = [ action.clientId ]; + if ( action.fromRootClientId ) { + updatedBlockUids.push( action.fromRootClientId ); + } + if ( action.toRootClientId ) { + updatedBlockUids.push( action.toRootClientId ); + } + newState.cache = { + ...newState.cache, + ...fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( updatedBlockUids ) + ), + }; + break; + } + case 'MOVE_BLOCKS_UP': + case 'MOVE_BLOCKS_DOWN': { + const updatedBlockUids = []; + if ( action.rootClientId ) { + updatedBlockUids.push( action.rootClientId ); + } + newState.cache = { + ...newState.cache, + ...fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( updatedBlockUids ) + ), + }; + break; + } + case 'SAVE_REUSABLE_BLOCK_SUCCESS': { + const updatedBlockUids = keys( omitBy( newState.attributes, ( attributes, clientId ) => { + return newState.byClientId[ clientId ].name !== 'core/block' || attributes.ref !== action.updatedId; + } ) ); + + newState.cache = { + ...newState.cache, + ...fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( updatedBlockUids ) + ), + }; + } + } + + return newState; +}; + /** * Higher-order reducer intended to augment the blocks reducer, assigning an * `isPersistentChange` property value corresponding to whether a change in @@ -294,7 +451,6 @@ const withInnerBlocksRemoveCascade = ( reducer ) => ( state, action ) => { result.push( ...state.order[ result[ i ] ] ); } - return result; }; @@ -351,6 +507,10 @@ const withBlockReset = ( reducer ) => ( state, action ) => { ...omit( state.parents, visibleClientIds ), ...mapBlockParents( action.blocks ), }, + cache: { + ...omit( state.cache, visibleClientIds ), + ...mapValues( flattenBlocks( action.blocks ), () => ( {} ) ), + }, }; } @@ -436,10 +596,11 @@ const withSaveReusableBlock = ( reducer ) => ( state, action ) => { */ export const blocks = flow( combineReducers, + withSaveReusableBlock, // needs to be before withBlockCache + withBlockCache, // needs to be before withInnerBlocksRemoveCascade withInnerBlocksRemoveCascade, withReplaceInnerBlocks, // needs to be after withInnerBlocksRemoveCascade withBlockReset, - withSaveReusableBlock, withPersistentBlockChange, withIgnoredBlockChange, )( { @@ -1067,15 +1228,17 @@ export const blockListSettings = ( state = {}, action ) => { return state; }; -export default combineReducers( { - blocks, - isTyping, - isCaretWithinFormattedText, - blockSelection, - blocksMode, - blockListSettings, - insertionPoint, - template, - settings, - preferences, -} ); +export default withPostMetaUpdateCacheReset( + combineReducers( { + blocks, + isTyping, + isCaretWithinFormattedText, + blockSelection, + blocksMode, + blockListSettings, + insertionPoint, + template, + settings, + preferences, + } ) +); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 21937e3bc3d8ae..85a7442cd8613d 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -72,28 +72,6 @@ const EMPTY_ARRAY = []; */ const EMPTY_OBJECT = {}; -/** - * Returns a new reference when the inner blocks of a given block client ID - * change. This is used exclusively as a memoized selector dependant, relying - * on this selector's shared return value and recursively those of its inner - * blocks defined as dependencies. This abuses mechanics of the selector - * memoization to return from the original selector function only when - * dependants change. - * - * @param {Object} state Editor state. - * @param {string} clientId Block client ID. - * - * @return {*} A value whose reference will change only when inner blocks of - * the given block client ID change. - */ -export const getBlockDependantsCacheBust = createSelector( - () => [], - ( state, clientId ) => map( - getBlockOrder( state, clientId ), - ( innerBlockClientId ) => getBlock( state, innerBlockClientId ), - ), -); - /** * Returns a block's name given its client ID, or null if no block exists with * the client ID. @@ -192,8 +170,12 @@ export const getBlock = createSelector( }; }, ( state, clientId ) => [ - ...getBlockAttributes.getDependants( state, clientId ), - getBlockDependantsCacheBust( state, clientId ), + // Normally, we'd have both `getBlockAttributes` dependancies and + // `getBlocks` (children) dependancies here but for performance reasons + // we use a denormalized cache key computed in the reducer that takes both + // the attributes and inner blocks into account. The value of the cache key + // is being changed whenever one of these dependencies is out of date. + state.blocks.cache[ clientId ], ] ); diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 90116e78052549..99f0ecb16282a4 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -219,6 +219,10 @@ describe( 'state', () => { chicken: '', 'chicken-child': 'chicken', }, + cache: { + chicken: {}, + 'chicken-child': {}, + }, } ); const newChildBlock = createBlock( 'core/test-child-block', { @@ -267,7 +271,12 @@ describe( 'state', () => { [ newChildBlockId ]: 'chicken', chicken: '', }, + cache: { + chicken: {}, + [ newChildBlockId ]: {}, + }, } ); + expect( state.cache.chicken ).not.toBe( existingState.cache.chicken ); } ); it( 'can insert a child block', () => { @@ -289,6 +298,9 @@ describe( 'state', () => { parents: { chicken: '', }, + cache: { + chicken: {}, + }, } ); const newChildBlock = createBlock( 'core/test-child-block', { @@ -337,7 +349,12 @@ describe( 'state', () => { [ newChildBlockId ]: 'chicken', chicken: '', }, + cache: { + chicken: {}, + [ newChildBlockId ]: {}, + }, } ); + expect( state.cache.chicken ).not.toBe( existingState.cache.chicken ); } ); it( 'can replace multiple child blocks', () => { @@ -379,6 +396,11 @@ describe( 'state', () => { 'chicken-child': 'chicken', 'chicken-child-2': 'chicken', }, + cache: { + chicken: {}, + 'chicken-child': {}, + 'chicken-child-2': {}, + }, } ); const newChildBlock1 = createBlock( 'core/test-child-block', { @@ -459,6 +481,12 @@ describe( 'state', () => { [ newChildBlockId2 ]: 'chicken', [ newChildBlockId3 ]: 'chicken', }, + cache: { + chicken: {}, + [ newChildBlockId1 ]: {}, + [ newChildBlockId2 ]: {}, + [ newChildBlockId3 ]: {}, + }, } ); } ); @@ -497,6 +525,11 @@ describe( 'state', () => { 'chicken-child': 'chicken', 'chicken-grand-child': 'chicken-child', }, + cache: { + chicken: {}, + 'chicken-child': {}, + 'chicken-grand-child': {}, + }, } ); const newChildBlock = createBlock( 'core/test-block' ); @@ -539,7 +572,14 @@ describe( 'state', () => { chicken: '', [ newChildBlockId ]: 'chicken', }, + cache: { + chicken: {}, + [ newChildBlockId ]: {}, + }, } ); + + // the cache key of the parent should be updated + expect( existingState.cache.chicken ).not.toBe( state.cache.chicken ); } ); } ); @@ -553,6 +593,7 @@ describe( 'state', () => { parents: {}, isPersistentChange: true, isIgnoredChange: false, + cache: {}, } ); } ); @@ -572,6 +613,9 @@ describe( 'state', () => { '': [ 'bananas' ], bananas: [], } ); + expect( state.cache ).toEqual( { + bananas: {}, + } ); } ); } ); @@ -591,6 +635,10 @@ describe( 'state', () => { apples: [], bananas: [ 'apples' ], } ); + expect( state.cache ).toEqual( { + bananas: {}, + apples: {}, + } ); } ); it( 'should insert block', () => { @@ -619,6 +667,12 @@ describe( 'state', () => { chicken: [], ribs: [], } ); + expect( state.cache ).toEqual( { + chicken: {}, + ribs: {}, + } ); + // The cache key is the same because the block has not been modified. + expect( original.cache.chicken ).toBe( state.cache.chicken ); } ); it( 'should replace the block', () => { @@ -651,6 +705,9 @@ describe( 'state', () => { expect( state.parents ).toEqual( { wings: '', } ); + expect( state.cache ).toEqual( { + wings: {}, + } ); } ); it( 'should replace the block and remove references to its inner blocks', () => { const original = blocks( undefined, { @@ -687,6 +744,9 @@ describe( 'state', () => { expect( state.parents ).toEqual( { wings: '', } ); + expect( state.cache ).toEqual( { + wings: {}, + } ); } ); it( 'should replace the nested block', () => { @@ -713,6 +773,10 @@ describe( 'state', () => { [ wrapperBlock.clientId ]: '', [ replacementBlock.clientId ]: wrapperBlock.clientId, } ); + expect( state.cache ).toEqual( { + [ wrapperBlock.clientId ]: {}, + [ replacementBlock.clientId ]: {}, + } ); } ); it( 'should replace the block even if the new block clientId is the same', () => { @@ -743,6 +807,10 @@ describe( 'state', () => { '': [ 'chicken' ], chicken: [], } ); + expect( replacedState.cache ).toEqual( { + chicken: {}, + } ); + expect( originalState.cache.chicken ).not.toBe( replacedState.cache.chicken ); const nestedBlock = { clientId: 'chicken', @@ -808,6 +876,11 @@ describe( 'state', () => { expect( state.attributes.chicken ).toEqual( { content: 'ribs', } ); + + expect( state.cache ).toEqual( { + chicken: {}, + } ); + expect( state.cache.chicken ).not.toBe( original.cache.chicken ); } ); it( 'should update the reusable block reference if the temporary id is swapped', () => { @@ -839,6 +912,10 @@ describe( 'state', () => { expect( state.attributes.chicken ).toEqual( { ref: 3, } ); + expect( state.cache ).toEqual( { + chicken: {}, + } ); + expect( state.cache.chicken ).not.toBe( original.cache.chicken ); } ); it( 'should move the block up', () => { @@ -862,6 +939,8 @@ describe( 'state', () => { } ); expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken' ] ); + expect( state.cache.ribs ).toBe( original.cache.ribs ); + expect( state.cache.chicken ).toBe( original.cache.chicken ); } ); it( 'should move the nested block up', () => { @@ -884,6 +963,9 @@ describe( 'state', () => { [ movedBlock.clientId ]: [], [ siblingBlock.clientId ]: [], } ); + expect( state.cache[ wrapperBlock.clientId ] ).not.toBe( original.cache[ wrapperBlock.clientId ] ); + expect( state.cache[ movedBlock.clientId ] ).toBe( original.cache[ movedBlock.clientId ] ); + expect( state.cache[ siblingBlock.clientId ] ).toBe( original.cache[ siblingBlock.clientId ] ); } ); it( 'should move multiple blocks up', () => { @@ -1115,6 +1197,9 @@ describe( 'state', () => { expect( state.attributes ).toEqual( { ribs: {}, } ); + expect( state.cache ).toEqual( { + ribs: {}, + } ); } ); it( 'should remove multiple blocks', () => { @@ -1607,7 +1692,8 @@ describe( 'state', () => { describe( 'isIgnoredChange', () => { it( 'should consider received blocks as ignored change', () => { - const state = blocks( undefined, { + const resetState = blocks( undefined, { type: 'random action' } ); + const state = blocks( resetState, { type: 'RECEIVE_BLOCKS', blocks: [ { clientId: 'kumquat', diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index bbb61fbfc04ecf..7bc0390317ece5 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -19,7 +19,6 @@ import { RawHTML } from '@wordpress/element'; import * as selectors from '../selectors'; const { - getBlockDependantsCacheBust, getBlockName, getBlock, getBlocks, @@ -136,274 +135,6 @@ describe( 'selectors', () => { setFreeformContentHandlerName( undefined ); } ); - describe( 'getBlockDependantsCacheBust', () => { - const rootBlock = { clientId: 123, name: 'core/paragraph' }; - const rootBlockAttributes = {}; - const rootOrder = [ 123 ]; - - it( 'returns an unchanging reference', () => { - const rootBlockOrder = []; - - const state = { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - }, - parents: { - 123: '', - }, - }, - }; - - const nextState = { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - }, - parents: { - 123: '', - }, - }, - }; - - expect( - getBlockDependantsCacheBust( state, 123 ) - ).toBe( getBlockDependantsCacheBust( nextState, 123 ) ); - } ); - - it( 'returns a new reference on added inner block', () => { - const state = { - blocks: { - byClientId: { - 123: rootBlock, - }, - attributes: { - 123: rootBlockAttributes, - }, - order: { - '': rootOrder, - 123: [], - }, - parents: { - 123: '', - }, - }, - }; - - const nextState = { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: {}, - }, - order: { - '': rootOrder, - 123: [ 456 ], - 456: [], - }, - parents: { - 123: '', - 456: 123, - }, - }, - }; - - expect( - getBlockDependantsCacheBust( state, 123 ) - ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); - } ); - - it( 'returns an unchanging reference on unchanging inner block', () => { - const rootBlockOrder = [ 456 ]; - const childBlock = { clientId: 456, name: 'core/paragraph' }; - const childBlockAttributes = {}; - const childBlockOrder = []; - - const state = { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, - parents: { - 123: '', - 456: 123, - }, - }, - }; - - const nextState = { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, - parents: { - 123: '', - 456: 123, - }, - }, - }; - - expect( - getBlockDependantsCacheBust( state, 123 ) - ).toBe( getBlockDependantsCacheBust( nextState, 123 ) ); - } ); - - it( 'returns a new reference on updated inner block', () => { - const rootBlockOrder = [ 456 ]; - const childBlockOrder = []; - - const state = { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: {}, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, - parents: { - 123: '', - 456: 123, - }, - }, - }; - - const nextState = { - blocks: { - byClientId: { - 123: rootBlock, - 456: { clientId: 456, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: { content: [ 'foo' ] }, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - }, - parents: { - 123: '', - 456: 123, - }, - }, - }; - - expect( - getBlockDependantsCacheBust( state, 123 ) - ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); - } ); - - it( 'returns a new reference on updated grandchild inner block', () => { - const rootBlockOrder = [ 456 ]; - const childBlock = { clientId: 456, name: 'core/paragraph' }; - const childBlockAttributes = {}; - const childBlockOrder = [ 789 ]; - const grandChildBlockOrder = []; - - const state = { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - 789: {}, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - 789: grandChildBlockOrder, - }, - parents: { - 123: '', - 456: 123, - 789: 456, - }, - }, - }; - - const nextState = { - blocks: { - byClientId: { - 123: rootBlock, - 456: childBlock, - 789: { clientId: 789, name: 'core/paragraph' }, - }, - attributes: { - 123: rootBlockAttributes, - 456: childBlockAttributes, - 789: { content: [ 'foo' ] }, - }, - order: { - '': rootOrder, - 123: rootBlockOrder, - 456: childBlockOrder, - 789: grandChildBlockOrder, - }, - parents: { - 123: '', - 456: 123, - 789: 456, - }, - }, - }; - - expect( - getBlockDependantsCacheBust( state, 123 ) - ).not.toBe( getBlockDependantsCacheBust( nextState, 123 ) ); - } ); - } ); - describe( 'getBlockName', () => { it( 'returns null if no block by clientId', () => { const state = { @@ -465,6 +196,9 @@ describe( 'selectors', () => { parents: { 123: '', }, + cache: { + 123: {}, + }, }, }; @@ -483,6 +217,7 @@ describe( 'selectors', () => { attributes: {}, order: {}, parents: {}, + cache: {}, }, }; @@ -509,6 +244,10 @@ describe( 'selectors', () => { 123: '', 456: 123, }, + cache: { + 123: {}, + 456: {}, + }, }, }; @@ -561,6 +300,9 @@ describe( 'selectors', () => { parents: { 123: '', }, + cache: { + 123: {}, + }, }, }; @@ -596,6 +338,10 @@ describe( 'selectors', () => { 123: '', 23: '', }, + cache: { + 123: {}, + 23: {}, + }, }, }; @@ -1016,6 +762,9 @@ describe( 'selectors', () => { 123: '', 23: '', }, + cache: { + 23: {}, + }, }, blockSelection: { start: { clientId: 23 }, end: { clientId: 23 } }, }; @@ -2246,6 +1995,7 @@ describe( 'selectors', () => { }, order: {}, parents: {}, + cache: {}, }, settings: { __experimentalReusableBlocks: [ @@ -2319,6 +2069,9 @@ describe( 'selectors', () => { parents: { block1ref: '', }, + cache: { + block1ref: {}, + }, }, settings: { __experimentalReusableBlocks: [ @@ -2384,6 +2137,11 @@ describe( 'selectors', () => { childReferredBlock2: 'referredBlock2', grandchildReferredBlock2: 'childReferredBlock2', }, + cache: { + block2ref: {}, + childReferredBlock2: {}, + grandchildReferredBlock2: {}, + }, }, settings: { @@ -2428,6 +2186,7 @@ describe( 'selectors', () => { }, order: {}, parents: {}, + cache: {}, }, settings: { __experimentalReusableBlocks: [ @@ -2475,6 +2234,12 @@ describe( 'selectors', () => { block3: '', block4: '', }, + cache: { + block1: {}, + block2: {}, + block3: {}, + block4: {}, + }, }, settings: { __experimentalReusableBlocks: [ @@ -2534,6 +2299,9 @@ describe( 'selectors', () => { order: { '': [ 'block1' ], }, + cache: { + block1: {}, + }, }, preferences: { insertUsage: {}, @@ -2553,6 +2321,7 @@ describe( 'selectors', () => { attributes: {}, order: {}, parents: {}, + cache: {}, }, preferences: { insertUsage: {}, @@ -2572,6 +2341,7 @@ describe( 'selectors', () => { attributes: {}, order: {}, parents: {}, + cache: {}, }, preferences: { insertUsage: { @@ -2602,6 +2372,9 @@ describe( 'selectors', () => { parents: { block1: '', }, + cache: { + block1: {}, + }, }, preferences: { insertUsage: {}, From 707336cdbd10013ef341ebaa3d736c95183c08b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?sarah=20=E2=9C=88=20semark?= <sarah@triggersandsparks.com> Date: Fri, 5 Jul 2019 18:08:35 +0100 Subject: [PATCH 442/664] Fix inconsistent references to Settings Sidebar (#16138) * Switch out instances of Inspector and Block Settings with Settings Sidebar; fixes #14988. * Results for npm run docs:build (whitespace) --- docs/contributors/principles/the-block.md | 4 ++-- docs/designers-developers/designers/block-design.md | 12 ++++++------ ...ctor.md => block-controls-toolbar-and-sidebar.md} | 12 ++++++------ docs/manifest-devhub.json | 6 +++--- docs/manifest.json | 6 +++--- docs/toc.json | 2 +- .../src/components/sidebar/settings-header/index.js | 4 ++-- 7 files changed, 23 insertions(+), 23 deletions(-) rename docs/designers-developers/developers/tutorials/block-tutorial/{block-controls-toolbars-and-inspector.md => block-controls-toolbar-and-sidebar.md} (89%) diff --git a/docs/contributors/principles/the-block.md b/docs/contributors/principles/the-block.md index 29e1486f8c1f84..bdb0c920c77c11 100644 --- a/docs/contributors/principles/the-block.md +++ b/docs/contributors/principles/the-block.md @@ -23,5 +23,5 @@ The placeholder content in the content area of the block can be thought of as a ### The block toolbar is the place for critical options that can’t be incorporated into placeholder UI. Basic block settings won’t always make sense in the context of the placeholder / content UI. As a secondary option, options that are critical to the functionality of a block can live in the block toolbar. The block toolbar is one step removed from direct manipulation, but is still highly contextual and visible on all screen sizes, so it is a great secondary option. -### The block sidebar should only be used for advanced, tertiary controls. -The sidebar is not visible by default on a small / mobile screen, and may also be collapsed even in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that only power users may discover. +### The Settings Sidebar should only be used for advanced, tertiary controls. +The Settings Sidebar is not visible by default on a small / mobile screen, and may also be collapsed even in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that only power users may discover. diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index 9bf9c0fefb841e..cc99f4f74bd181 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -11,17 +11,17 @@ Since the block itself represents what will actually appear on the site, interac 1. The placeholder content in the content area of the block can be thought of as a guide or interface for users to follow a set of instructions or “fill in the blanks”. For example, a block that embeds content from a 3rd-party service might contain controls for signing in to that service in the placeholder. 2. After the user has added content, selecting the block can reveal additional controls to adjust or edit that content. For example, a signup block might reveal a control for hiding/showing subscriber count. However, this should be done in minimal ways, so as to avoid dramatically changing the size and display of a block when a user selects it (this could be disorienting or annoying). -### The block toolbar is a secondary place for required options & controls +### The Block Toolbar is a secondary place for required options & controls -Basic block settings won’t always make sense in the context of the placeholder/content UI. As a secondary option, options that are critical to the functionality of a block can live in the block toolbar. The block toolbar is still highly contextual and visible on all screen sizes. One notable constraint with the block toolbar is that it is icon-based UI, so any controls that live in the block toolbar need to be ones that can effectively be communicated via an icon or icon group. +Basic block settings won’t always make sense in the context of the placeholder/content UI. As a secondary option, options that are critical to the functionality of a block can live in the block toolbar. The Block Toolbar is still highly contextual and visible on all screen sizes. One notable constraint with the Block Toolbar is that it is icon-based UI, so any controls that live in the Block Toolbar need to be ones that can effectively be communicated via an icon or icon group. -### The block sidebar should only be used for advanced, tertiary controls +### The Settings Sidebar should only be used for advanced, tertiary controls -The sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the sidebar as something that most users should not need to open. +The Settings Sidebar is not visible by default on a small / mobile screen, and may also be collapsed in a desktop view. Therefore, it should not be relied on for anything that is necessary for the basic operation of the block. Pick good defaults, make important actions available in the block toolbar, and think of the Settings Sidebar as something that most users should not need to open. -In addition, use sections and headers in the block sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. +In addition, use sections and headers in the Settings Sidebar if there are more than a handful of options, in order to allow users to easily scan and understand the options available. -Each block sidebar comes with an "Advanced" section by default. This area houses an "Additional CSS Class" field, and should be used to house other power user controls. +Each Settings Sidebar comes with an "Advanced" section by default. This area houses an "Additional CSS Class" field, and should be used to house other power user controls. ## Setup state vs. live preview state diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md similarity index 89% rename from docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md rename to docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md index bfabb7379b40a6..028b0e8e56a0f8 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md @@ -1,8 +1,8 @@ -# Block Controls: Toolbars and Inspector +# Block Controls: Block Toolbar and Settings Sidebar To simplify block customization and ensure a consistent experience for users, there are a number of built-in UI patterns to help generate the editor preview. Like with the `RichText` component covered in the previous chapter, the `wp.editor` global includes a few other common components to render editing interfaces. In this chapter, we'll explore toolbars and the block inspector. -## Toolbar +## Block Toolbar ![Screenshot of the rich text toolbar applied to a paragraph block inside the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-text.png) @@ -169,10 +169,10 @@ Note that `BlockControls` is only visible when the block is currently selected a ![Screenshot of the inspector panel focused on the settings for a paragraph block](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/inspector.png) -The inspector is used to display less-often-used settings or settings that require more screen space. The inspector should be used for **block-level settings only**. +The Settings Sidebar is used to display less-often-used settings or settings that require more screen space. The Settings Sidebar should be used for **block-level settings only**. -If you have settings that affects only selected content inside a block (example: the "bold" setting for selected text inside a paragraph): **do not place it inside the inspector**. The inspector is displayed even when editing a block in HTML mode, so it should only contain block-level settings. +If you have settings that affects only selected content inside a block (example: the "bold" setting for selected text inside a paragraph): **do not place it inside the Settings Sidebar**. The Settings Sidebar is displayed even when editing a block in HTML mode, so it should only contain block-level settings. -The inspector region is shown in place of the post settings sidebar when a block is selected. +The Block Tab is shown in place of the Document Tab when a block is selected. -Similar to rendering a toolbar, if you include an `InspectorControls` element in the return value of your block type's `edit` function, those controls will be shown in the inspector region. +Similar to rendering a toolbar, if you include an `InspectorControls` element in the return value of your block type's `edit` function, those controls will be shown in the Settings Sidebar region. diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 94f21727035536..559d963aa2a494 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -426,9 +426,9 @@ "parent": "block-tutorial" }, { - "title": "Block Controls: Toolbars and Inspector", - "slug": "block-controls-toolbars-and-inspector", - "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", + "title": "Block Controls: Block Toolbar and Settings Sidebar", + "slug": "block-controls-toolbar-and-sidebar", + "markdown_source": "../docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md", "parent": "block-tutorial" }, { diff --git a/docs/manifest.json b/docs/manifest.json index 6cee0109f47498..03f85343b014a3 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1056,9 +1056,9 @@ "parent": "block-tutorial" }, { - "title": "Block Controls: Toolbars and Inspector", - "slug": "block-controls-toolbars-and-inspector", - "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md", + "title": "Block Controls: Block Toolbar and Settings Sidebar", + "slug": "block-controls-toolbar-and-sidebar", + "markdown_source": "https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md", "parent": "block-tutorial" }, { diff --git a/docs/toc.json b/docs/toc.json index 4d0c1b38905e93..c26aec92dd5f82 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -82,7 +82,7 @@ { "docs/designers-developers/developers/tutorials/block-tutorial/writing-your-first-block-type.md": [] }, { "docs/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets.md": [] }, { "docs/designers-developers/developers/tutorials/block-tutorial/introducing-attributes-and-editable-fields.md": [] }, - { "docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbars-and-inspector.md": [] }, + { "docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md": [] }, { "docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md": [] }, { "docs/designers-developers/developers/tutorials/block-tutorial/generate-blocks-with-wp-cli.md": [] } ] }, diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index eeb95a872166f5..04132d6f2664ea 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -18,9 +18,9 @@ const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName [ __( 'Document' ), '' ]; const [ blockAriaLabel, blockActiveClass ] = sidebarName === 'edit-post/block' ? - // translators: ARIA label for the Block sidebar tab, selected. + // translators: ARIA label for the Settings Sidebar tab, selected. [ __( 'Block (selected)' ), 'is-active' ] : - // translators: ARIA label for the Block sidebar tab, not selected. + // translators: ARIA label for the Settings Sidebar tab, not selected. [ __( 'Block' ), '' ]; return ( From baa249842cda1f6cb4827c379bf8b77f42b13394 Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Sat, 6 Jul 2019 09:32:15 -0400 Subject: [PATCH 443/664] Export cloneBlock method to the mobile (#16441) --- packages/blocks/src/api/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js index b99fccc531c217..3b3be8f28c3a46 100644 --- a/packages/blocks/src/api/index.native.js +++ b/packages/blocks/src/api/index.native.js @@ -1,4 +1,5 @@ export { + cloneBlock, createBlock, switchToBlockType, } from './factory'; From a047c54063ddd87c3ec9804c4ba22850b55e4d2a Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Mon, 8 Jul 2019 09:21:25 +1000 Subject: [PATCH 444/664] Move post permalink to beneath title on mobile. (#16277) * Move post permalink to beneath title on mobile. * Stop button text overflowing on IE. * Adjust margins and borders. * Increase spacing between permalink elements. * Adjust margin and padding. --- .../src/components/post-permalink/style.scss | 15 +++++++++++++-- .../src/components/post-title/style.scss | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/components/post-permalink/style.scss b/packages/editor/src/components/post-permalink/style.scss index 5b13e758439949..0162eb164cccc5 100644 --- a/packages/editor/src/components/post-permalink/style.scss +++ b/packages/editor/src/components/post-permalink/style.scss @@ -1,8 +1,9 @@ .editor-post-permalink { display: inline-flex; align-items: center; + flex-wrap: wrap; background: $white; - padding: 5px; + padding: $grid-size $grid-size 0; font-family: $default-font; font-size: $default-font-size; height: 40px; @@ -25,11 +26,21 @@ // Put toolbar snugly to edge on mobile. margin-left: -$block-padding - $border-width; // This hides the border off the edge of the screen. margin-right: -$block-padding - $border-width; + @include break-mobile() { + padding: $grid-size-small; + } @include break-small() { margin-left: -$border-width; margin-right: -$border-width; } + // Increase specificity to override margins set on label element. + &.editor-post-permalink > * { + margin-bottom: $grid-size; + @include break-mobile() { + margin-bottom: 0; + } + } button { // Prevent button shrinking in IE11 when other items have a 100% flex basis. // This should be safe to apply in all browsers because we don't want these @@ -56,7 +67,7 @@ color: $dark-gray-200; text-decoration: underline; margin-right: 10px; - width: 100%; + flex-grow: 1; overflow: hidden; position: relative; white-space: nowrap; diff --git a/packages/editor/src/components/post-title/style.scss b/packages/editor/src/components/post-title/style.scss index 8f758e9947ae76..3bf8c5ad9f321d 100644 --- a/packages/editor/src/components/post-title/style.scss +++ b/packages/editor/src/components/post-title/style.scss @@ -106,11 +106,19 @@ .editor-post-title .editor-post-permalink { font-size: $default-font-size; color: $dark-gray-900; - position: absolute; - top: -$block-toolbar-height + $border-width + $border-width + 1px; // Shift this element upward the same height as the block toolbar, minus the border size - left: 0; - right: 0; - + height: auto; + position: relative; + left: $block-left-border-width; + top: -2px; + width: calc(100% - #{$block-left-border-width}); + + @include break-mobile() { + position: absolute; + top: -$block-toolbar-height + $border-width + $border-width + 1px; // Shift this element upward the same height as the block toolbar, minus the border size + right: 0; + flex-wrap: nowrap; + width: auto; + } @include break-small() { left: $block-side-ui-clearance; right: $block-side-ui-clearance; From d6cd349164509c4f5ab9b55f7313443679616e45 Mon Sep 17 00:00:00 2001 From: Mel Choyce <melchoyce@users.noreply.github.com> Date: Sun, 7 Jul 2019 20:58:02 -0400 Subject: [PATCH 445/664] Update HTML anchor explaination text (#16142) Update the HTML anchor text to be more user friendly to anyone who doesn't already know what HTML anchors are. --- packages/block-editor/src/hooks/anchor.js | 13 +++++++++++-- packages/block-editor/src/hooks/anchor.scss | 4 ++++ packages/block-editor/src/style.scss | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 packages/block-editor/src/hooks/anchor.scss diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 3f21ed3ef3b0a2..3651346a96caf1 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -7,7 +7,7 @@ import { assign, has } from 'lodash'; * WordPress dependencies */ import { addFilter } from '@wordpress/hooks'; -import { TextControl } from '@wordpress/components'; +import { TextControl, ExternalLink } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { hasBlockSupport } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; @@ -70,8 +70,17 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => <BlockEdit { ...props } /> <InspectorAdvancedControls> <TextControl + className="html-anchor-control" label={ __( 'HTML Anchor' ) } - help={ __( 'Anchors lets you link directly to a section on a page.' ) } + help={ ( + <> + { __( 'Enter a word or two — without spaces — to make a unique web address just for this heading, called an “anchor.” Then, you’ll be able to link directly to this section of your page.' ) } + + <ExternalLink href={ 'https://wordpress.org/support/article/page-jumps/' }> + { __( 'Learn more about anchors' ) } + </ExternalLink> + </> + ) } value={ props.attributes.anchor || '' } onChange={ ( nextValue ) => { nextValue = nextValue.replace( ANCHOR_REGEX, '-' ); diff --git a/packages/block-editor/src/hooks/anchor.scss b/packages/block-editor/src/hooks/anchor.scss new file mode 100644 index 00000000000000..5987b154f41a43 --- /dev/null +++ b/packages/block-editor/src/hooks/anchor.scss @@ -0,0 +1,4 @@ +.html-anchor-control .components-external-link { + display: block; + margin-top: $grid-size; +} diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 23d70816397dae..8083cf0cbc6bf5 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -32,3 +32,4 @@ @import "./components/url-popover/style.scss"; @import "./components/warning/style.scss"; @import "./components/writing-flow/style.scss"; +@import "./hooks/anchor.scss"; From 0d9efe27209c396ec6209622d58d706f2e8167c5 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Mon, 8 Jul 2019 11:06:13 +1000 Subject: [PATCH 446/664] Bump plugin version to 6.1.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 977083ad622016..9ae6f7d37b412c 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.0.0 + * Version: 6.1.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 54148d3de97777..39f81f74f0aa5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.0.0", + "version": "6.1.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1087de7094828a..6f3c286a1ef630 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.0.0", + "version": "6.1.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From e7807e38dd9ebe3c9ea7f95024a11dc75c660021 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 8 Jul 2019 18:09:41 +0100 Subject: [PATCH 447/664] Fix: Make post settings publish control use the same logic that the visibility control. --- .../components/sidebar/post-schedule/index.js | 20 ++++--------------- .../sidebar/post-schedule/style.scss | 4 ---- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/packages/edit-post/src/components/sidebar/post-schedule/index.js b/packages/edit-post/src/components/sidebar/post-schedule/index.js index 27dfcc64ba0ed3..eca127989f2e64 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/index.js +++ b/packages/edit-post/src/components/sidebar/post-schedule/index.js @@ -3,37 +3,25 @@ */ import { __ } from '@wordpress/i18n'; import { PanelRow, Dropdown, Button } from '@wordpress/components'; -import { withInstanceId } from '@wordpress/compose'; import { PostSchedule as PostScheduleForm, PostScheduleLabel, PostScheduleCheck } from '@wordpress/editor'; -export function PostSchedule( { instanceId } ) { +export function PostSchedule() { return ( <PostScheduleCheck> <PanelRow className="edit-post-post-schedule"> - <label - htmlFor={ `edit-post-post-schedule__toggle-${ instanceId }` } - id={ `edit-post-post-schedule__heading-${ instanceId }` } - > + <span> { __( 'Publish' ) } - </label> + </span> <Dropdown position="bottom left" contentClassName="edit-post-post-schedule__dialog" renderToggle={ ( { onToggle, isOpen } ) => ( <> - <label - className="edit-post-post-schedule__label" - htmlFor={ `edit-post-post-schedule__toggle-${ instanceId }` } - > - <PostScheduleLabel /> { __( 'Click to change' ) } - </label> <Button - id={ `edit-post-post-schedule__toggle-${ instanceId }` } type="button" className="edit-post-post-schedule__toggle" onClick={ onToggle } aria-expanded={ isOpen } - aria-live="polite" isLink > <PostScheduleLabel /> @@ -47,4 +35,4 @@ export function PostSchedule( { instanceId } ) { ); } -export default withInstanceId( PostSchedule ); +export default PostSchedule; diff --git a/packages/edit-post/src/components/sidebar/post-schedule/style.scss b/packages/edit-post/src/components/sidebar/post-schedule/style.scss index 417591bb68c327..800fe7c6e6d24c 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/style.scss +++ b/packages/edit-post/src/components/sidebar/post-schedule/style.scss @@ -3,10 +3,6 @@ position: relative; } -.edit-post-post-schedule__label { - display: none; -} - .components-button.edit-post-post-schedule__toggle { text-align: right; } From 1ba5cb588d9f9a407ef9f77800010f286925bb0b Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Mon, 8 Jul 2019 13:51:01 -0400 Subject: [PATCH 448/664] Create block list tappable footer which creates empty paragraph block (#16439) --- .../src/components/block-list/index.native.js | 15 ++++++++++++++- .../src/components/block-list/style.native.scss | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 1642b5bf7d0fa8..713fe94d7ea209 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { identity } from 'lodash'; -import { Text, View, Keyboard, SafeAreaView, Platform } from 'react-native'; +import { Text, View, Keyboard, SafeAreaView, Platform, TouchableWithoutFeedback } from 'react-native'; import { subscribeMediaAppend } from 'react-native-gutenberg-bridge'; /** @@ -33,6 +33,7 @@ export class BlockList extends Component { this.renderItem = this.renderItem.bind( this ); this.renderAddBlockSeparator = this.renderAddBlockSeparator.bind( this ); + this.renderBlockListFooter = this.renderBlockListFooter.bind( this ); this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); this.onBlockTypeSelected = this.onBlockTypeSelected.bind( this ); @@ -171,6 +172,7 @@ export class BlockList extends Component { title={ this.props.title } ListHeaderComponent={ this.props.header } ListEmptyComponent={ this.renderDefaultBlockAppender } + ListFooterComponent={ this.renderBlockListFooter } /> <SafeAreaView> <View style={ { height: toolbarHeight } } /> @@ -242,6 +244,17 @@ export class BlockList extends Component { ); } + renderBlockListFooter() { + const paragraphBlock = createBlock( 'core/paragraph' ); + return ( + <TouchableWithoutFeedback onPress={ () => { + this.finishBlockAppendingOrReplacing( paragraphBlock ); + } } > + <View style={ styles.blockListFooter } /> + </TouchableWithoutFeedback> + ); + } + renderHTML() { return ( <HTMLTextInput { ...this.props } parentHeight={ this.props.rootViewHeight } /> diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index f856893d5160ad..884c0612f05867 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -73,3 +73,7 @@ .blockHolderFocused { border-color: $gray-lighten-30; } + +.blockListFooter { + height: 80px; +} From 7f4c1c3456affc031157a88f61342f75b558100a Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 8 Jul 2019 18:17:12 -0400 Subject: [PATCH 449/664] Build Tooling: Wait for MySQL availability before install (#16461) --- bin/install-wp-tests.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh index 26b5cc89cae3ea..17dc681629034a 100755 --- a/bin/install-wp-tests.sh +++ b/bin/install-wp-tests.sh @@ -117,6 +117,13 @@ install_db() { mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute "CREATE DATABASE IF NOT EXISTS $DB_NAME;" } +# Wait for MySQL availability before proceeding with installation +echo -en $(status_message "Attempting to connect to MySQL...") +while ! mysqladmin ping -h"$DB_HOST" --silent; do + echo -n '.' + sleep 2 +done + install_wp install_test_suite install_db From 5471bad754cdb6b5fd688f2e9a6ec03fd4506645 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Mon, 8 Jul 2019 15:38:42 -0700 Subject: [PATCH 450/664] Update documentation for consistent Settings Sidebar terminology (#16438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update glossary deprecating Inspector for Settings Sidebar - Updates Inspector definition stating it is deprecated and directs user to Settings Sidebar - Expand Settings Sidebar definition * Rename insepector reference to settings sidebar * Update docs/designers-developers/developers/block-api/block-registration.md Co-Authored-By: sarah ✈ semark <sarah@triggersandsparks.com> * Update docs/designers-developers/glossary.md Co-Authored-By: sarah ✈ semark <sarah@triggersandsparks.com> * Add anchor link --- .../developers/block-api/block-registration.md | 2 +- docs/designers-developers/glossary.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index 9d78c4247e4ec4..a5e1b982797a0b 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -42,7 +42,7 @@ title: __( 'Book' ) * **Type:** `String` -This is a short description for your block, which can be translated with our translation functions. This will be shown in the block inspector. +This is a short description for your block, which can be translated with our translation functions. This will be shown in the Block Tab in the Settings Sidebar. ```js description: __( 'Block showing a Book card.' ) diff --git a/docs/designers-developers/glossary.md b/docs/designers-developers/glossary.md index 69f9e497a0bc44..a1d2a21fdb4b5e 100644 --- a/docs/designers-developers/glossary.md +++ b/docs/designers-developers/glossary.md @@ -29,7 +29,7 @@ <dd>A type of block where the content of which may change and cannot be determined at the time of saving a post, instead calculated any time the post is shown on the front of a site. These blocks may save fallback content or no content at all in their JavaScript implementation, instead deferring to a PHP block implementation for runtime rendering.</dd> <dt>Inspector</dt> -<dd>A block settings region shown in place of the post settings when a block is selected. Fields may be shown here to allow the user to customize the selected block.</dd> +<dd>Deprecated term. See <a href="#settings-sidebar">Settings Sidebar.</a></dd> <dt>Post settings</dt> <dd>A sidebar region containing metadata fields for the post, including scheduling, visibility, terms, and featured image.</dd> @@ -40,8 +40,8 @@ <dt>Reusable block</dt> <dd>A block that is saved and then can be shared as a reusable, repeatable piece of content.</dd> -<dt>Sidebar</dt> -<dd>The panel on the right which contains the document and block settings. The sidebar is toggled using the Settings gear icon.</dd> +<dt id="settings-sidebar">Settings Sidebar</dt> +<dd>The panel on the right that contains the document and block settings. The sidebar is toggled using the Settings gear icon. Block settings are shown when a block is selected, otherwise document settings are shown.</dd> <dt>Serialization</dt> <dd>The process of converting a block's attributes object into HTML markup, which occurs each time a block is edited.</dd> From 7de6bf3f6a142095d4584ea934d5fe249c783c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?sarah=20=E2=9C=88=20semark?= <sarah@triggersandsparks.com> Date: Mon, 8 Jul 2019 23:43:38 +0100 Subject: [PATCH 451/664] Documentation: Clarify block conventions (#16458) * Establish explicit naming convention for blocks in UI & docs. See #16118. * Elaborate on sentence format for block descriptions. Vaguely related: #16002. * Update docs/designers-developers/designers/block-design.md Co-Authored-By: Marcus Kazmierczak <marcus@mkaz.com> * Minor copyedit to Block Description section. --- .../designers/block-design.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index cc99f4f74bd181..1014ec0bdcd146 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -52,10 +52,16 @@ In most cases, a block’s setup state is only shown once and then further custo ## Do's and Don'ts -### Blocks +### Block Identification A block should have a straightforward, short name so users can easily find it in the Block Library. A block named "YouTube" is easy to find and understand. The same block, named "Embedded Video (YouTube)", would be less clear and harder to find in the Block Library. +When referring to a block in documentation or UI, use title case for the block title, and lowercase for the "block" descriptor. For example: + +- Paragraph block +- Latest Posts block +- Media & Text block + Blocks should have an identifying icon, ideally using a single color. Try to avoid using the same icon used by an existing block. The core block icons are based on [Material Design Icons](https://material.io/tools/icons/). Look to that icon set, or to [Dashicons](https://developer.wordpress.org/resource/dashicons/) for style inspiration. ![A screenshot of the Block Library with concise block names](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/assets/blocks-do.png) @@ -68,7 +74,15 @@ Avoid long, multi-line block names. ### Block Description -Every block should include a description in the “Block” tab of the Settings sidebar. This description should explain your block's function clearly. Keep it to a single sentence. +Every block should include a description that clearly explains the block's function. The description will display in the Settings Sidebar. + +You can add a description by using the description attribute in the [registerBlockType function](/docs/designers-developers/developers/block-api/block-registration/). + +Stick to a single imperative sentence with an action + subject format. Examples: + +- Start with the building block of all narrative. +- Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content. +- Create a bulleted or numbered list. ![A screenshot of a short block description](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/assets/block-descriptions-do.png) **Do:** From 65ba28b87abcd4182270c332ce3cd88798e9a41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 9 Jul 2019 00:59:46 +0200 Subject: [PATCH 452/664] Docs: Fix typo in sanitize (#16455) --- packages/editor/src/utils/url.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/utils/url.js b/packages/editor/src/utils/url.js index 8eb941d30b39d1..1a0730fa82a5aa 100644 --- a/packages/editor/src/utils/url.js +++ b/packages/editor/src/utils/url.js @@ -25,7 +25,7 @@ export function getWPAdminURL( page, query ) { /** * Performs some basic cleanup of a string for use as a post slug * - * This replicates some of what santize_title() does in WordPress core, but + * This replicates some of what sanitize_title() does in WordPress core, but * is only designed to approximate what the slug will be. * * Converts whitespace, periods, forward slashes and underscores to hyphens. From af888f4f60ccada88a86c780bebbcb21b37bb7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 9 Jul 2019 01:04:27 +0200 Subject: [PATCH 453/664] Docs: Fix typos (#16468) * Fix typos * Wheter -> Whether --- docs/designers-developers/developers/data/data-core-blocks.md | 2 +- docs/designers-developers/developers/slotfills/README.md | 2 +- packages/blocks/src/store/selectors.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-blocks.md b/docs/designers-developers/developers/data/data-core-blocks.md index b1d377ed11a6fe..5c6806fa70703e 100644 --- a/docs/designers-developers/developers/data/data-core-blocks.md +++ b/docs/designers-developers/developers/data/data-core-blocks.md @@ -186,7 +186,7 @@ _Parameters_ _Returns_ -- `Array<Object>`: Wheter block type matches search term. +- `Array<Object>`: Whether block type matches search term. <!-- END TOKEN(Autogenerated selectors) --> diff --git a/docs/designers-developers/developers/slotfills/README.md b/docs/designers-developers/developers/slotfills/README.md index 50c23ac616c1c8..61e642658a0350 100644 --- a/docs/designers-developers/developers/slotfills/README.md +++ b/docs/designers-developers/developers/slotfills/README.md @@ -68,7 +68,7 @@ export default PluginPostStatusInfo; This new Slot is then exposed in the editor. The example below is from core and represents the Status & Visibility panel. As we can see, the `<PluginPostStatusInfo.Slot>` is wrapping all of the items that will appear in the panel. -Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed betwee the `<PostAuthor/>` and `<PostTrash/>` components. +Any items that have been added via the SlotFill ( see the example above ), will be included in the `fills` parameter and be displayed between the `<PostAuthor/>` and `<PostTrash/>` components. See [core code](https://github.com/WordPress/gutenberg/tree/master/packages/edit-post/src/components/sidebar/post-status/index.js#L26). diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 25fe40ca9f6111..cb9333e3f6ff12 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -177,7 +177,7 @@ export function hasBlockSupport( state, nameOrType, feature, defaultSupports ) { * @param {(string|Object)} nameOrType Block name or type object. * @param {string} searchTerm Search term by which to filter. * - * @return {Object[]} Wheter block type matches search term. + * @return {Object[]} Whether block type matches search term. */ export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { const blockType = getNormalizedBlockType( state, nameOrType ); From 779a91631f9aca69646f124d7244b5ac2be0fa4f Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Tue, 9 Jul 2019 01:09:57 +0200 Subject: [PATCH 454/664] Update composer lock file (#16459) Brings `composer.lock` up to date with the latest changes in composer.json --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index e6bdc5bf4f1473..95af33e6daad86 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "78aec8b4f7bcfa500026de9ddb1abace", + "content-hash": "14e6f0898af5e07bd8ff8b2bc6172712", "packages": [ { "name": "composer/installers", From 105cb4717db1f11d439fd70fe0f45a17813cdee6 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Tue, 9 Jul 2019 10:50:37 +1000 Subject: [PATCH 455/664] Make tables accessible at high zoom levels (#16324) * Make top bar and tables accessible when zoomed in * Change word-break to IE-compatible value. * Fix failing tests * Fix collapsing floated tables. * Add deprecated. * Fix overflow issue * Undo header changes that moved to another PR. * Word wrapping on fixed width and aligned tables. * Add fixture for table block deprecation. * Fix striped table styles. * Change wrapper to figure. * Address PR comments. * Fix failing tests. --- .../block-library/src/table/deprecated.js | 81 ++++++++++ packages/block-library/src/table/edit.js | 14 +- packages/block-library/src/table/editor.scss | 11 +- packages/block-library/src/table/index.js | 2 + packages/block-library/src/table/save.js | 12 +- packages/block-library/src/table/style.scss | 28 +++- packages/block-library/src/table/theme.scss | 4 +- .../fixtures/blocks/core__table.html | 2 +- .../fixtures/blocks/core__table.json | 2 +- .../fixtures/blocks/core__table.parsed.json | 4 +- .../blocks/core__table.serialized.html | 2 +- .../blocks/core__table__deprecated-1.html | 3 + .../blocks/core__table__deprecated-1.json | 145 ++++++++++++++++++ .../core__table__deprecated-1.parsed.json | 20 +++ .../core__table__deprecated-1.serialized.html | 3 + .../blocks/core__table__scope-attribute.html | 2 +- .../blocks/core__table__scope-attribute.json | 2 +- .../core__table__scope-attribute.parsed.json | 4 +- ...re__table__scope-attribute.serialized.html | 2 +- .../blocks/__snapshots__/table.test.js.snap | 12 +- .../src/components/header/style.scss | 1 + test/integration/fixtures/apple-out.html | 4 +- test/integration/fixtures/evernote-out.html | 4 +- .../integration/fixtures/google-docs-out.html | 2 +- .../fixtures/google-docs-table-out.html | 2 +- .../google-docs-table-with-comments-out.html | 2 +- .../google-docs-with-comments-out.html | 2 +- test/integration/fixtures/markdown-out.html | 4 +- .../fixtures/ms-word-online-out.html | 2 +- test/integration/fixtures/ms-word-out.html | 4 +- 30 files changed, 332 insertions(+), 50 deletions(-) create mode 100644 packages/block-library/src/table/deprecated.js create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html diff --git a/packages/block-library/src/table/deprecated.js b/packages/block-library/src/table/deprecated.js new file mode 100644 index 00000000000000..79fc5726ea23fb --- /dev/null +++ b/packages/block-library/src/table/deprecated.js @@ -0,0 +1,81 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { RichText, getColorClassName } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; + +const supports = { + align: true, +}; + +const deprecated = [ + { + attributes: metadata.attributes, + supports, + save( { attributes } ) { + const { + hasFixedLayout, + head, + body, + foot, + backgroundColor, + } = attributes; + const isEmpty = ! head.length && ! body.length && ! foot.length; + + if ( isEmpty ) { + return null; + } + + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + + const classes = classnames( backgroundClass, { + 'has-fixed-layout': hasFixedLayout, + 'has-background': !! backgroundClass, + } ); + + const Section = ( { type, rows } ) => { + if ( ! rows.length ) { + return null; + } + + const Tag = `t${ type }`; + + return ( + <Tag> + { rows.map( ( { cells }, rowIndex ) => ( + <tr key={ rowIndex }> + { cells.map( ( { content, tag, scope }, cellIndex ) => + <RichText.Content + tagName={ tag } + value={ content } + key={ cellIndex } + scope={ tag === 'th' ? scope : undefined } + /> + ) } + </tr> + ) ) } + </Tag> + ); + }; + + return ( + <table className={ classes }> + <Section type="head" rows={ head } /> + <Section type="body" rows={ body } /> + <Section type="foot" rows={ foot } /> + </table> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 38a7c02e54a81f..90a4682e2223ad 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -451,7 +451,7 @@ export class TableEdit extends Component { ); } - const classes = classnames( className, backgroundColor.class, { + const tableClasses = classnames( backgroundColor.class, { 'has-fixed-layout': hasFixedLayout, 'has-background': !! backgroundColor.color, } ); @@ -499,11 +499,13 @@ export class TableEdit extends Component { ] } /> </InspectorControls> - <table className={ classes }> - <Section type="head" rows={ head } /> - <Section type="body" rows={ body } /> - <Section type="foot" rows={ foot } /> - </table> + <figure className={ className }> + <table className={ tableClasses }> + <Section type="head" rows={ head } /> + <Section type="body" rows={ body } /> + <Section type="foot" rows={ foot } /> + </table> + </figure> </> ); } diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index 46ae863c31a0d9..73df244f815ff8 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -2,10 +2,17 @@ &[data-align="left"], &[data-align="right"], &[data-align="center"] { + // Stop table block from collapsing when tables are floated. + height: auto; + table { // Ensure the table element is not full-width when aligned. width: auto; } + td, + th { + word-break: break-all; + } } &[data-align="center"] { @@ -19,9 +26,11 @@ .wp-block-table { + // Remove default <figure> style. + margin: 0; + table { border-collapse: collapse; - width: 100%; } td, diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index b5172841a876da..b8bd0893e87ec8 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -6,6 +6,7 @@ import { __, _x } from '@wordpress/i18n'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; @@ -30,4 +31,5 @@ export const settings = { transforms, edit, save, + deprecated, }; diff --git a/packages/block-library/src/table/save.js b/packages/block-library/src/table/save.js index 14ac51ff299b7b..086a0221c045b5 100644 --- a/packages/block-library/src/table/save.js +++ b/packages/block-library/src/table/save.js @@ -55,10 +55,12 @@ export default function save( { attributes } ) { }; return ( - <table className={ classes }> - <Section type="head" rows={ head } /> - <Section type="body" rows={ body } /> - <Section type="foot" rows={ foot } /> - </table> + <figure> + <table className={ classes }> + <Section type="head" rows={ head } /> + <Section type="body" rows={ body } /> + <Section type="foot" rows={ foot } /> + </table> + </figure> ); } diff --git a/packages/block-library/src/table/style.scss b/packages/block-library/src/table/style.scss index b79d044fac2b35..13e9f048059890 100644 --- a/packages/block-library/src/table/style.scss +++ b/packages/block-library/src/table/style.scss @@ -4,10 +4,21 @@ $subtle-pale-blue: #e7f5fe; $subtle-pale-pink: #fcf0ef; + overflow-x: auto; + + table { + width: 100%; + } + // Fixed layout toggle - &.has-fixed-layout { + .has-fixed-layout { table-layout: fixed; width: 100%; + + td, + th { + word-break: break-all; + } } &.alignleft, @@ -17,25 +28,30 @@ // The table element needs to be kept as display table // for table features to work reliably. display: table; - // Table cannot be 100% width if it is aligned, so set // width as auto. width: auto; + + td, + th { + // Aligned tables shouldn't scroll horizontally so we need their contents to wrap. + word-break: break-all; + } } - &.has-subtle-light-gray-background-color { + .has-subtle-light-gray-background-color { background-color: $subtle-light-gray; } - &.has-subtle-pale-green-background-color { + .has-subtle-pale-green-background-color { background-color: $subtle-pale-green; } - &.has-subtle-pale-blue-background-color { + .has-subtle-pale-blue-background-color { background-color: $subtle-pale-blue; } - &.has-subtle-pale-pink-background-color { + .has-subtle-pale-pink-background-color { background-color: $subtle-pale-pink; } diff --git a/packages/block-library/src/table/theme.scss b/packages/block-library/src/table/theme.scss index 7f604ba661f29c..664a4f57cf53dd 100644 --- a/packages/block-library/src/table/theme.scss +++ b/packages/block-library/src/table/theme.scss @@ -1,12 +1,10 @@ .wp-block-table { - width: 100%; - min-width: $break-mobile / 2; border-collapse: collapse; td, th { padding: 0.5em; border: 1px solid; - word-break: break-all; + word-break: normal; } } diff --git a/packages/e2e-tests/fixtures/blocks/core__table.html b/packages/e2e-tests/fixtures/blocks/core__table.html index b9b41659e835aa..7cc12a4efb3b5b 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table.html +++ b/packages/e2e-tests/fixtures/blocks/core__table.html @@ -1,3 +1,3 @@ <!-- wp:core/table --> -<table class="wp-block-table"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<figure class="wp-block-table"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:core/table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table.json b/packages/e2e-tests/fixtures/blocks/core__table.json index 7d71d09c53be82..3318420a516b30 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table.json +++ b/packages/e2e-tests/fixtures/blocks/core__table.json @@ -140,6 +140,6 @@ "foot": [] }, "innerBlocks": [], - "originalContent": "<table class=\"wp-block-table\"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>" + "originalContent": "<figure class=\"wp-block-table\"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__table.parsed.json b/packages/e2e-tests/fixtures/blocks/core__table.parsed.json index 5462dae3908a8c..591b028b25b567 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__table.parsed.json @@ -3,9 +3,9 @@ "blockName": "core/table", "attrs": {}, "innerBlocks": [], - "innerHTML": "\n<table class=\"wp-block-table\"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n", + "innerHTML": "\n<figure class=\"wp-block-table\"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure>\n", "innerContent": [ - "\n<table class=\"wp-block-table\"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n" + "\n<figure class=\"wp-block-table\"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__table.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table.serialized.html index a791eb149f6273..346cd673c17514 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__table.serialized.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<table class="wp-block-table"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.html b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.html new file mode 100644 index 00000000000000..b9b41659e835aa --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.html @@ -0,0 +1,3 @@ +<!-- wp:core/table --> +<table class="wp-block-table"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<!-- /wp:core/table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.json b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.json new file mode 100644 index 00000000000000..7d71d09c53be82 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.json @@ -0,0 +1,145 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/table", + "isValid": true, + "attributes": { + "hasFixedLayout": false, + "head": [ + { + "cells": [ + { + "content": "Version", + "tag": "th" + }, + { + "content": "Musician", + "tag": "th" + }, + { + "content": "Date", + "tag": "th" + } + ] + } + ], + "body": [ + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a>", + "tag": "td" + }, + { + "content": "No musician chosen.", + "tag": "td" + }, + { + "content": "May 27, 2003", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a>", + "tag": "td" + }, + { + "content": "Miles Davis", + "tag": "td" + }, + { + "content": "January 3, 2004", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a>", + "tag": "td" + }, + { + "content": "…", + "tag": "td" + }, + { + "content": "…", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a>", + "tag": "td" + }, + { + "content": "Clifford Brown", + "tag": "td" + }, + { + "content": "December 8, 2015", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a>", + "tag": "td" + }, + { + "content": "Coleman Hawkins", + "tag": "td" + }, + { + "content": "April 12, 2016", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a>", + "tag": "td" + }, + { + "content": "Pepper Adams", + "tag": "td" + }, + { + "content": "August 16, 2016", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a>", + "tag": "td" + }, + { + "content": "Sarah Vaughan", + "tag": "td" + }, + { + "content": "December 6, 2016", + "tag": "td" + } + ] + } + ], + "foot": [] + }, + "innerBlocks": [], + "originalContent": "<table class=\"wp-block-table\"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.parsed.json b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.parsed.json new file mode 100644 index 00000000000000..5462dae3908a8c --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/table", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<table class=\"wp-block-table\"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n", + "innerContent": [ + "\n<table class=\"wp-block-table\"><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html new file mode 100644 index 00000000000000..346cd673c17514 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:table --> +<figure class="wp-block-table"><table class=""><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> +<!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html index 4a5175c4c07d41..dfe5cdd5c26168 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<table class="wp-block-table"><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<figure class="wp-block-table"><table><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json index 96e101aa0264c9..23bb732aebff2f 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json @@ -143,6 +143,6 @@ "foot": [] }, "innerBlocks": [], - "originalContent": "<table class=\"wp-block-table\"><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>" + "originalContent": "<figure class=\"wp-block-table\"><table><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json index e8410b63327af8..052a8a0a3d049c 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.parsed.json @@ -3,9 +3,9 @@ "blockName": "core/table", "attrs": {}, "innerBlocks": [], - "innerHTML": "\n<table class=\"wp-block-table\"><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n", + "innerHTML": "\n<figure class=\"wp-block-table\"><table><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure>\n", "innerContent": [ - "\n<table class=\"wp-block-table\"><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table>\n" + "\n<figure class=\"wp-block-table\"><table><thead><tr><th scope=\"col\">Version</th><th scope=\"col\">Musician</th><th scope=\"col\">Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html index 4a5175c4c07d41..88122c62ed9683 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<table class="wp-block-table"><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap index abafd184d19109..f2c4ee232d0785 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -2,36 +2,36 @@ exports[`Table allows adding and deleting columns across the table header, body and footer 1`] = ` "<!-- wp:table --> -<table class=\\"wp-block-table\\"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table></figure> <!-- /wp:table -->" `; exports[`Table allows adding and deleting columns across the table header, body and footer 2`] = ` "<!-- wp:table --> -<table class=\\"wp-block-table\\"><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr><tr><td></td></tr></tbody><tfoot><tr><td></td></tr></tfoot></table> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr><tr><td></td></tr></tbody><tfoot><tr><td></td></tr></tfoot></table></figure> <!-- /wp:table -->" `; exports[`Table allows header and footer rows to be switched on and off 1`] = ` "<!-- wp:table --> -<table class=\\"wp-block-table\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table></figure> <!-- /wp:table -->" `; exports[`Table allows header and footer rows to be switched on and off 2`] = ` "<!-- wp:table --> -<table class=\\"wp-block-table\\"><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody></table> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody></table></figure> <!-- /wp:table -->" `; exports[`Table allows text to by typed into cells 1`] = ` "<!-- wp:table --> -<table class=\\"wp-block-table\\"><tbody><tr><td>This</td><td>is</td></tr><tr><td>table</td><td>block</td></tr></tbody></table> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>This</td><td>is</td></tr><tr><td>table</td><td>block</td></tr></tbody></table></figure> <!-- /wp:table -->" `; exports[`Table displays a form for choosing the row and column count of the table 1`] = ` "<!-- wp:table --> -<table class=\\"wp-block-table\\"><tbody><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table></figure> <!-- /wp:table -->" `; diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index a94783dab8f727..05ea111d6ab82c 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -1,4 +1,5 @@ .edit-post-header { + height: $header-height; padding: $grid-size-small 2px; border-bottom: $border-width solid $light-gray-500; background: $white; diff --git a/test/integration/fixtures/apple-out.html b/test/integration/fixtures/apple-out.html index 530bf8d34140c2..40d4f1a8d133ed 100644 --- a/test/integration/fixtures/apple-out.html +++ b/test/integration/fixtures/apple-out.html @@ -19,7 +19,7 @@ <!-- /wp:list --> <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td> +<figure class="wp-block-table"><table class=""><tbody><tr><td> One </td><td> Two @@ -37,7 +37,7 @@ II </td><td> III -</td></tr></tbody></table> +</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:paragraph --> diff --git a/test/integration/fixtures/evernote-out.html b/test/integration/fixtures/evernote-out.html index 4d8c6c43b9c99e..c984df312f0bef 100644 --- a/test/integration/fixtures/evernote-out.html +++ b/test/integration/fixtures/evernote-out.html @@ -17,13 +17,13 @@ <!-- /wp:separator --> <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td>One +<figure class="wp-block-table"><table class=""><tbody><tr><td>One </td><td>Two </td><td>Three </td></tr><tr><td>Four </td><td>Five </td><td>Six -</td></tr></tbody></table> +</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:image --> diff --git a/test/integration/fixtures/google-docs-out.html b/test/integration/fixtures/google-docs-out.html index 7733ca660bdd02..576b2cd7fda73f 100644 --- a/test/integration/fixtures/google-docs-out.html +++ b/test/integration/fixtures/google-docs-out.html @@ -19,7 +19,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:list --> <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:separator --> diff --git a/test/integration/fixtures/google-docs-table-out.html b/test/integration/fixtures/google-docs-table-out.html index 697c2d41ea5cd9..fbca760d25258b 100644 --- a/test/integration/fixtures/google-docs-table-out.html +++ b/test/integration/fixtures/google-docs-table-out.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/test/integration/fixtures/google-docs-table-with-comments-out.html b/test/integration/fixtures/google-docs-table-with-comments-out.html index 697c2d41ea5cd9..fbca760d25258b 100644 --- a/test/integration/fixtures/google-docs-table-with-comments-out.html +++ b/test/integration/fixtures/google-docs-table-with-comments-out.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/test/integration/fixtures/google-docs-with-comments-out.html b/test/integration/fixtures/google-docs-with-comments-out.html index 7733ca660bdd02..576b2cd7fda73f 100644 --- a/test/integration/fixtures/google-docs-with-comments-out.html +++ b/test/integration/fixtures/google-docs-with-comments-out.html @@ -19,7 +19,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:list --> <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:separator --> diff --git a/test/integration/fixtures/markdown-out.html b/test/integration/fixtures/markdown-out.html index ba9a33b7172cc2..85a105b640923b 100644 --- a/test/integration/fixtures/markdown-out.html +++ b/test/integration/fixtures/markdown-out.html @@ -28,11 +28,11 @@ <h2>Table</h2> <!-- /wp:heading --> <!-- wp:table --> -<table class="wp-block-table"><thead><tr><th>First Header</th><th>Second Header</th></tr></thead><tbody><tr><td>Content from cell 1</td><td>Content from cell 2</td></tr><tr><td>Content in the first column</td><td>Content in the second column</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><thead><tr><th>First Header</th><th>Second Header</th></tr></thead><tbody><tr><td>Content from cell 1</td><td>Content from cell 2</td></tr><tr><td>Content in the first column</td><td>Content in the second column</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:table --> -<table class="wp-block-table"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td>Table with empty cells.</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td>Table with empty cells.</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:heading --> diff --git a/test/integration/fixtures/ms-word-online-out.html b/test/integration/fixtures/ms-word-online-out.html index 3088b7480877f7..798881e8c0151b 100644 --- a/test/integration/fixtures/ms-word-online-out.html +++ b/test/integration/fixtures/ms-word-online-out.html @@ -15,7 +15,7 @@ <!-- /wp:list --> <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td>One&nbsp;</td><td>Two&nbsp;</td><td>Three&nbsp;</td></tr><tr><td>1&nbsp;</td><td>2&nbsp;</td><td>3&nbsp;</td></tr><tr><td>I&nbsp;</td><td>II&nbsp;</td><td>III&nbsp;</td></tr></tbody></table> +<figure class="wp-block-table"><table class=""><tbody><tr><td>One&nbsp;</td><td>Two&nbsp;</td><td>Three&nbsp;</td></tr><tr><td>1&nbsp;</td><td>2&nbsp;</td><td>3&nbsp;</td></tr><tr><td>I&nbsp;</td><td>II&nbsp;</td><td>III&nbsp;</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:paragraph --> diff --git a/test/integration/fixtures/ms-word-out.html b/test/integration/fixtures/ms-word-out.html index c53c5aaeba2e97..427e12686ad625 100644 --- a/test/integration/fixtures/ms-word-out.html +++ b/test/integration/fixtures/ms-word-out.html @@ -29,7 +29,7 @@ <h2>This is a heading level 2</h2> <!-- /wp:list --> <!-- wp:table --> -<table class="wp-block-table"><tbody><tr><td> +<figure class="wp-block-table"><table class=""><tbody><tr><td> One </td><td> Two @@ -47,7 +47,7 @@ <h2>This is a heading level 2</h2> II </td><td> III - </td></tr></tbody></table> + </td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:paragraph --> From 49124712753d8fbb424afc8fc184f013a12d67f1 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Tue, 9 Jul 2019 11:00:11 +1000 Subject: [PATCH 456/664] Change font-size-picker markup to use select (#16148) * Change font-size-picker markup to use select * Improve naming. * Fix e2e tests. * Fix more tests. * Address PR feedback. * Address PR feedback. --- .../components/font-sizes/with-font-sizes.js | 2 +- packages/components/src/base-control/index.js | 7 +- .../components/src/font-size-picker/index.js | 105 ++++++++---------- .../src/font-size-picker/style.scss | 50 ++------- .../components/src/select-control/index.js | 3 +- packages/e2e-tests/specs/editor-modes.test.js | 6 +- .../e2e-tests/specs/font-size-picker.test.js | 17 +-- 7 files changed, 69 insertions(+), 121 deletions(-) diff --git a/packages/block-editor/src/components/font-sizes/with-font-sizes.js b/packages/block-editor/src/components/font-sizes/with-font-sizes.js index 23a8c76a11d25b..147d66df20c388 100644 --- a/packages/block-editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/block-editor/src/components/font-sizes/with-font-sizes.js @@ -64,7 +64,7 @@ export default ( ...fontSizeNames ) => { createSetFontSize( fontSizeAttributeName, customFontSizeAttributeName ) { return ( fontSizeValue ) => { - const fontSizeObject = find( this.props.fontSizes, { size: fontSizeValue } ); + const fontSizeObject = find( this.props.fontSizes, { size: Number( fontSizeValue ) } ); this.props.setAttributes( { [ fontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug ? fontSizeObject.slug : undefined, [ customFontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug ? undefined : fontSizeValue, diff --git a/packages/components/src/base-control/index.js b/packages/components/src/base-control/index.js index 37a5939e10775c..3abe25ee062823 100644 --- a/packages/components/src/base-control/index.js +++ b/packages/components/src/base-control/index.js @@ -3,11 +3,14 @@ */ import classnames from 'classnames'; -function BaseControl( { id, label, help, className, children } ) { +function BaseControl( { id, label, hideLabelFromVision, help, className, children } ) { return ( <div className={ classnames( 'components-base-control', className ) }> <div className="components-base-control__field"> - { label && id && <label className="components-base-control__label" htmlFor={ id }>{ label }</label> } + { label && id && <label + className={ classnames( 'components-base-control__label', { 'screen-reader-text': hideLabelFromVision } ) } + htmlFor={ id }>{ label } + </label> } { label && ! id && <BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel> } { children } </div> diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index 6f6c22e12ff017..0143c04fa098ca 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -1,22 +1,31 @@ -/** - * External dependencies - */ -import { map } from 'lodash'; /** * WordPress dependencies */ -import { __, _x, sprintf } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import Dashicon from '../dashicon'; -import BaseControl from '../base-control'; import Button from '../button'; -import Dropdown from '../dropdown'; import RangeControl from '../range-control'; -import { NavigableMenu } from '../navigable-container'; +import SelectControl from '../select-control'; + +function getSelectValueFromFontSize( fontSizes, value ) { + if ( value ) { + const fontSizeValue = fontSizes.find( ( font ) => font.size === value ); + return fontSizeValue ? fontSizeValue.slug : 'custom'; + } + return 'normal'; +} + +function getSelectOptions( optionsArray ) { + return [ + ...optionsArray.map( ( option ) => ( { value: option.slug, label: option.name } ) ), + { value: 'custom', label: __( 'Custom' ) }, + ]; +} function FontSizePicker( { fallbackFontSize, @@ -26,11 +35,16 @@ function FontSizePicker( { value, withSlider = false, } ) { + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const [ currentSelectValue, setCurrentSelectValue ] = useState( getSelectValueFromFontSize( fontSizes, value ) ); + if ( disableCustomFontSizes && ! fontSizes.length ) { return null; } + const onChangeValue = ( event ) => { const newValue = event.target.value; + setCurrentSelectValue( getSelectValueFromFontSize( fontSizes, Number( newValue ) ) ); if ( newValue === '' ) { onChange( undefined ); return; @@ -38,56 +52,28 @@ function FontSizePicker( { onChange( Number( newValue ) ); }; - const currentFont = fontSizes.find( ( font ) => font.size === value ); - const currentFontSizeName = ( currentFont && currentFont.name ) || ( ! value && _x( 'Normal', 'font size name' ) ) || _x( 'Custom', 'font size name' ); + const onSelectChangeValue = ( eventValue ) => { + setCurrentSelectValue( eventValue ); + const selectedFont = fontSizes.find( ( font ) => font.slug === eventValue ); + if ( selectedFont ) { + onChange( selectedFont.size ); + } + }; return ( - <BaseControl> - <BaseControl.VisualLabel> + <fieldset> + <legend> { __( 'Font Size' ) } - </BaseControl.VisualLabel> - <div className="components-font-size-picker__buttons"> + </legend> + <div className="components-font-size-picker__controls"> { ( fontSizes.length > 0 ) && - <Dropdown - className="components-font-size-picker__dropdown" - contentClassName="components-font-size-picker__dropdown-content" - position="bottom" - renderToggle={ ( { isOpen, onToggle } ) => ( - <Button - className="components-font-size-picker__selector" - isLarge - onClick={ onToggle } - aria-expanded={ isOpen } - aria-label={ sprintf( - /* translators: %s: font size name */ - __( 'Font size: %s' ), currentFontSizeName - ) } - > - { currentFontSizeName } - </Button> - ) } - renderContent={ () => ( - <NavigableMenu> - { map( fontSizes, ( { name, size, slug } ) => { - const isSelected = ( value === size || ( ! value && slug === 'normal' ) ); - - return ( - <Button - key={ slug } - onClick={ () => onChange( slug === 'normal' ? undefined : size ) } - className={ `is-font-${ slug }` } - role="menuitemradio" - aria-checked={ isSelected } - > - { isSelected && <Dashicon icon="saved" /> } - <span className="components-font-size-picker__dropdown-text-size" style={ { fontSize: size } }> - { name } - </span> - </Button> - ); - } ) } - </NavigableMenu> - ) } + <SelectControl + className={ 'components-font-size-picker__select' } + label={ 'Choose preset' } + hideLabelFromVision={ true } + value={ currentSelectValue } + onChange={ onSelectChangeValue } + options={ getSelectOptions( fontSizes ) } /> } { ( ! withSlider && ! disableCustomFontSizes ) && @@ -95,7 +81,7 @@ function FontSizePicker( { className="components-range-control__number" type="number" onChange={ onChangeValue } - aria-label={ __( 'Custom font size' ) } + aria-label={ __( 'Custom' ) } value={ value || '' } /> } @@ -103,7 +89,10 @@ function FontSizePicker( { className="components-color-palette__clear" type="button" disabled={ value === undefined } - onClick={ () => onChange( undefined ) } + onClick={ () => { + onChange( undefined ); + setCurrentSelectValue( getSelectValueFromFontSize( fontSizes, undefined ) ); + } } isSmall isDefault > @@ -123,7 +112,7 @@ function FontSizePicker( { afterIcon="editor-textcolor" /> } - </BaseControl> + </fieldset> ); } diff --git a/packages/components/src/font-size-picker/style.scss b/packages/components/src/font-size-picker/style.scss index 1841375ccf397d..00c140099f7f98 100644 --- a/packages/components/src/font-size-picker/style.scss +++ b/packages/components/src/font-size-picker/style.scss @@ -1,8 +1,9 @@ -.components-font-size-picker__buttons { +.components-font-size-picker__controls { max-width: $sidebar-width - ( 2 * $panel-padding ); display: flex; justify-content: space-between; align-items: center; + margin-bottom: $grid-size * 3; // Apply the same height as the isSmall Reset button. .components-range-control__number { @@ -18,6 +19,12 @@ } } +// needed to override CSS set in https://github.com/WordPress/gutenberg/blob/9c438f93a7215d50d1efc0492c308e4cbaa59c52/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss#L6 +.components-font-size-picker__select.components-font-size-picker__select.components-font-size-picker__select.components-font-size-picker__select, +.components-font-size-picker__select .components-base-control__field { + margin-bottom: 0; +} + .components-font-size-picker__custom-input { .components-range-control__slider + .dashicon { width: 30px; @@ -25,44 +32,3 @@ } } -.components-font-size-picker__dropdown-content .components-button { - display: block; - position: relative; - padding: 10px 20px 10px 40px; - width: 100%; - text-align: left; - - .dashicon { - position: absolute; - top: calc(50% - 10px); - left: 10px; - } - - &:hover { - @include menu-style__hover; - } - - &:focus { - @include menu-style__focus; - } -} - -.components-font-size-picker__buttons .components-font-size-picker__selector { - border: 1px solid; - background: none; - position: relative; - width: 110px; - - @include input-style__neutral(); - - &:focus { - @include input-style__focus(); - } - - &::after { - @include dropdown-arrow(); - right: 8px; - top: 12px; - position: absolute; - } -} diff --git a/packages/components/src/select-control/index.js b/packages/components/src/select-control/index.js index 3d1755060e1130..abc72be25be091 100644 --- a/packages/components/src/select-control/index.js +++ b/packages/components/src/select-control/index.js @@ -21,6 +21,7 @@ function SelectControl( { onChange, options = [], className, + hideLabelFromVision, ...props } ) { const id = `inspector-select-control-${ instanceId }`; @@ -38,7 +39,7 @@ function SelectControl( { /* eslint-disable jsx-a11y/no-onchange */ return ! isEmpty( options ) && ( - <BaseControl label={ label } id={ id } help={ help } className={ className }> + <BaseControl label={ label } hideLabelFromVision={ hideLabelFromVision } id={ id } help={ help } className={ className }> <select id={ id } className="components-select-control__input" diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js index eda64312514c98..4096338ce9f64b 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -56,7 +56,7 @@ describe( 'Editing modes (visual/HTML)', () => { // The font size picker for the paragraph block should appear, even in // HTML editing mode. - const fontSizePicker = await page.$$( '.edit-post-sidebar .components-font-size-picker__buttons' ); + const fontSizePicker = await page.$$( '.edit-post-sidebar .components-font-size-picker__controls' ); expect( fontSizePicker ).toHaveLength( 1 ); } ); @@ -74,9 +74,7 @@ describe( 'Editing modes (visual/HTML)', () => { expect( htmlBlockContent ).toEqual( '<p>Hello world!</p>' ); // Change the font size using the sidebar. - await page.click( '.components-font-size-picker__selector' ); - const changeSizeButton = await page.waitForSelector( '.components-button.is-font-large' ); - await changeSizeButton.click(); + await page.select( '.components-font-size-picker__select .components-select-control__input', 'large' ); // Make sure the HTML content updated. htmlBlockContent = await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', ( node ) => node.textContent ); diff --git a/packages/e2e-tests/specs/font-size-picker.test.js b/packages/e2e-tests/specs/font-size-picker.test.js index 35c41042073817..a844556bbc456b 100644 --- a/packages/e2e-tests/specs/font-size-picker.test.js +++ b/packages/e2e-tests/specs/font-size-picker.test.js @@ -17,9 +17,7 @@ describe( 'Font Size Picker', () => { // Create a paragraph block with some content. await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "large"' ); - await page.click( '.components-font-size-picker__selector' ); - const changeSizeButton = await page.waitForSelector( '.components-button.is-font-large' ); - await changeSizeButton.click(); + await page.select( '.components-font-size-picker__select .components-select-control__input', 'large' ); // Ensure content matches snapshot. const content = await getEditedPostContent(); @@ -58,14 +56,9 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph with font size reset using button' ); - await page.click( '.blocks-font-size .components-range-control__number' ); - // This should be the default font-size of the current theme. - await page.keyboard.type( '22' ); - - // Blur the range control - await page.click( '.components-base-control__label' ); + await page.select( '.components-font-size-picker__select .components-select-control__input', 'normal' ); - const resetButton = ( await page.$x( '//*[contains(concat(" ", @class, " "), " components-font-size-picker__buttons ")]//*[text()=\'Reset\']' ) )[ 0 ]; + const resetButton = ( await page.$x( '//*[contains(concat(" ", @class, " "), " components-font-size-picker__controls ")]//*[text()=\'Reset\']' ) )[ 0 ]; await resetButton.click(); // Ensure content matches snapshot. @@ -78,9 +71,7 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph with font size reset using input field' ); - await page.click( '.components-font-size-picker__selector' ); - const changeSizeButton = await page.waitForSelector( '.components-button.is-font-large' ); - await changeSizeButton.click(); + await page.select( '.components-font-size-picker__select .components-select-control__input', 'large' ); // Clear the custom font size input. await page.click( '.blocks-font-size .components-range-control__number' ); From 7a03158d398a8d2eccdffe5c063dba917c9836c5 Mon Sep 17 00:00:00 2001 From: Pinar Olguc <pinarolguc@gmail.com> Date: Tue, 9 Jul 2019 08:42:37 +0300 Subject: [PATCH 457/664] [Mobile]Fix video upload (#16331) * Remove attributes.url usage * Do not update src with local url * Remove unnecessary state item and fix icon color * Fix unit-tests * Temp fix for the crashing side effect * Revert changes * Revert "Temp fix for the crashing side effect" This reverts commit 1acb43727871572526cb88ef968d164dcea0f12f. --- .../block-library/src/video/edit.native.js | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 1b7fc518313440..54b6a7c88d5818 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -49,7 +49,6 @@ class VideoEdit extends React.Component { this.state = { showSettings: false, - isMediaRequested: false, videoContainerHeight: 0, }; @@ -64,7 +63,7 @@ class VideoEdit extends React.Component { componentDidMount() { const { attributes } = this.props; - if ( attributes.id && attributes.url && ! isURL( attributes.src ) ) { + if ( attributes.id && ! isURL( attributes.src ) ) { mediaUploadSync(); } } @@ -91,7 +90,6 @@ class VideoEdit extends React.Component { if ( payload.mediaUrl ) { setAttributes( { url: payload.mediaUrl } ); } - if ( ! this.state.isUploadInProgress ) { this.setState( { isUploadInProgress: true } ); } @@ -100,25 +98,24 @@ class VideoEdit extends React.Component { finishMediaUploadWithSuccess( payload ) { const { setAttributes } = this.props; setAttributes( { src: payload.mediaUrl, id: payload.mediaServerId } ); - this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + this.setState( { isUploadInProgress: false } ); } finishMediaUploadWithFailure( payload ) { const { setAttributes } = this.props; setAttributes( { id: payload.mediaId } ); - this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + this.setState( { isUploadInProgress: false } ); } mediaUploadStateReset() { const { setAttributes } = this.props; setAttributes( { id: null, src: null } ); - this.setState( { isMediaRequested: false, isUploadInProgress: false } ); + this.setState( { isUploadInProgress: false } ); } onSelectMediaUploadOption( mediaId, mediaUrl ) { const { setAttributes } = this.props; setAttributes( { id: mediaId, src: mediaUrl } ); - this.setState( { isMediaRequested: true } ); } onVideoContanerLayout( event ) { @@ -129,18 +126,18 @@ class VideoEdit extends React.Component { } } - getIcon( isRetryIcon, isUploadInProgress ) { + getIcon( isRetryIcon, isMediaPlaceholder ) { if ( isRetryIcon ) { return <Icon icon={ SvgIconRetry } { ...style.icon } />; } - return <Icon icon={ SvgIcon } { ...( isUploadInProgress ? style.iconUploading : style.icon ) } />; + return <Icon icon={ SvgIcon } { ...( ! isMediaPlaceholder ? style.iconUploading : style.icon ) } />; } render() { const { attributes, isSelected, setAttributes } = this.props; const { caption, id, src } = attributes; - const { isMediaRequested, videoContainerHeight } = this.state; + const { videoContainerHeight } = this.state; const toolbarEditButton = ( <MediaUpload mediaType={ MEDIA_TYPE_VIDEO } @@ -160,13 +157,13 @@ class VideoEdit extends React.Component { </MediaUpload> ); - if ( ! isMediaRequested && ! src ) { + if ( ! id ) { return ( <View style={ { flex: 1 } } > <MediaPlaceholder mediaType={ MEDIA_TYPE_VIDEO } onSelectURL={ this.onSelectMediaUploadOption } - icon={ this.getIcon( false ) } + icon={ this.getIcon( false, true ) } onFocus={ this.props.onFocus } /> </View> @@ -193,8 +190,8 @@ class VideoEdit extends React.Component { onUpdateMediaProgress={ this.updateMediaProgress } onMediaUploadStateReset={ this.mediaUploadStateReset } renderContent={ ( { isUploadInProgress, isUploadFailed, retryMessage } ) => { - const showVideo = src && ! isUploadInProgress && ! isUploadFailed; - const icon = this.getIcon( isUploadFailed, isUploadInProgress ); + const showVideo = isURL( src ) && ! isUploadInProgress && ! isUploadFailed; + const icon = this.getIcon( isUploadFailed, false ); const styleIconContainer = isUploadFailed ? style.modalIconRetry : style.modalIcon; const iconContainer = ( @@ -212,7 +209,7 @@ class VideoEdit extends React.Component { return ( <View onLayout={ this.onVideoContanerLayout } style={ containerStyle }> - { showVideo && isURL( src ) && + { showVideo && <View style={ style.videoContainer }> <Video isSelected={ isSelected } From 04af1c1e2bd0717fd559647c9061dc7a2c427719 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli <ercoli@gmail.com> Date: Tue, 9 Jul 2019 07:56:14 +0200 Subject: [PATCH 458/664] [RN Mobile] Track unsupported blocks list (#16434) * Send the list of unsupported blocks up to the Native bridge * Update test * Update test again * Rename variable * Remove unused var --- packages/edit-post/src/editor.native.js | 7 ++----- packages/edit-post/src/test/editor.native.js | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index 714b9665ef590f..71f8bbd9cb7746 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -9,7 +9,6 @@ import RNReactNativeGutenbergBridge, { subscribeSetTitle, sendNativeEditorDidLayout, } from 'react-native-gutenberg-bridge'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -135,10 +134,8 @@ class Editor extends Component { if ( ! prevProps.isReady && this.props.isReady ) { const blocks = this.props.getEditorBlocks(); const isUnsupportedBlock = ( { name } ) => name === getUnregisteredTypeHandlerName(); - const unsupportedBlocks = blocks.filter( isUnsupportedBlock ); - const hasUnsupportedBlocks = ! isEmpty( unsupportedBlocks ); - - RNReactNativeGutenbergBridge.editorDidMount( hasUnsupportedBlocks ); + const unsupportedBlockNames = blocks.filter( isUnsupportedBlock ).map( ( block ) => block.attributes.originalName ); + RNReactNativeGutenbergBridge.editorDidMount( unsupportedBlockNames ); } } diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index 44592b0fb4204f..fce8b23299b563 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -31,7 +31,7 @@ describe( 'Editor', () => { appContainer.unmount(); expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledTimes( 1 ); - expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledWith( true ); + expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledWith( [ 'core/notablock' ] ); } ); } ); From 76aa7154913d54985ffbb57b695c9d7e86492124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Tue, 9 Jul 2019 10:31:24 +0200 Subject: [PATCH 459/664] Docs: Add Playground URL from GitHub Pages (#16456) --- docs/contributors/getting-started.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/contributors/getting-started.md b/docs/contributors/getting-started.md index ed84cb6e81376b..9fbe2b10c182ee 100644 --- a/docs/contributors/getting-started.md +++ b/docs/contributors/getting-started.md @@ -88,3 +88,4 @@ The Gutenberg repository also includes a static Gutenberg playground that allows You can launch the playground by running `npm run playground:start` locally. The playground should be available on [http://localhost:1234](http://localhost:1234). +You can also test the playground version of the current master branch on GitHub Pages: [https://wordpress.github.io/gutenberg/](https://wordpress.github.io/gutenberg/) From bd23075db1a4370dad614003524f774731324c28 Mon Sep 17 00:00:00 2001 From: Alex Lende <ajlende@gmail.com> Date: Tue, 9 Jul 2019 07:14:23 -0500 Subject: [PATCH 460/664] Remove ./node_modules/ from require in example (#16470) Node will automatically look in the node_modules directory, so listing it isn't needed --- packages/scripts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scripts/README.md b/packages/scripts/README.md index c1c3f6ae107703..671ad6cbd2d780 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -312,7 +312,7 @@ To extend the provided webpack config, or replace subsections within the provide In the example below, a `webpack.config.js` file is added to the root folder extending the provided webpack config to include [`@svgr/webpack`](https://www.npmjs.com/package/@svgr/webpack) and [`url-loader`](https://github.com/webpack-contrib/url-loader): ```javascript -const defaultConfig = require("./node_modules/@wordpress/scripts/config/webpack.config"); +const defaultConfig = require("@wordpress/scripts/config/webpack.config"); module.exports = { ...defaultConfig, From 403fa51163f2122017625ca94f852fb4c863d145 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Tue, 9 Jul 2019 15:50:06 +0100 Subject: [PATCH 461/664] Fix using the classic block in nested contexts (#16477) --- packages/blocks/README.md | 1 + packages/blocks/src/api/serializer.js | 39 ++++++++++++------- packages/blocks/src/api/test/serializer.js | 22 +++++++++++ .../src/block-content-provider/index.js | 2 +- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index cf83bc0e3b0870..e456eb546ffbb8 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -639,6 +639,7 @@ Takes a block or set of blocks and returns the serialized post content. _Parameters_ - _blocks_ `Array`: Block(s) to serialize. +- _options_ `WPBlockSerializationOptions`: Serialization options. _Returns_ diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 74219369a29171..1776a32e362999 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -21,6 +21,12 @@ import { import { normalizeBlockType } from './utils'; import BlockContentProvider from '../block-content-provider'; +/** + * @typedef {Object} WPBlockSerializationOptions Serialization Options. + * + * @property {boolean} isInnerBlocks Whether we are serializing inner blocks. + */ + /** * Returns the block's default classname from its name. * @@ -255,34 +261,37 @@ export function getCommentDelimitedContent( rawBlockName, attributes, content ) * Returns the content of a block, including comment delimiters, determining * serialized attributes and content form from the current state of the block. * - * @param {Object} block Block instance. + * @param {Object} block Block instance. + * @param {WPBlockSerializationOptions} options Serialization options. * * @return {string} Serialized block. */ -export function serializeBlock( block ) { +export function serializeBlock( block, { isInnerBlocks = false } = {} ) { const blockName = block.name; const saveContent = getBlockContent( block ); - switch ( blockName ) { - case getFreeformContentHandlerName(): - case getUnregisteredTypeHandlerName(): - return saveContent; - - default: { - const blockType = getBlockType( blockName ); - const saveAttributes = getCommentAttributes( blockType, block.attributes ); - return getCommentDelimitedContent( blockName, saveAttributes, saveContent ); - } + if ( + ( blockName === getUnregisteredTypeHandlerName() ) || + ( ! isInnerBlocks && blockName === getFreeformContentHandlerName() ) + ) { + return saveContent; } + + const blockType = getBlockType( blockName ); + const saveAttributes = getCommentAttributes( blockType, block.attributes ); + return getCommentDelimitedContent( blockName, saveAttributes, saveContent ); } /** * Takes a block or set of blocks and returns the serialized post content. * - * @param {Array} blocks Block(s) to serialize. + * @param {Array} blocks Block(s) to serialize. + * @param {WPBlockSerializationOptions} options Serialization options. * * @return {string} The post content. */ -export default function serialize( blocks ) { - return castArray( blocks ).map( serializeBlock ).join( '\n\n' ); +export default function serialize( blocks, options ) { + return castArray( blocks ) + .map( ( block ) => serializeBlock( block, options ) ) + .join( '\n\n' ); } diff --git a/packages/blocks/src/api/test/serializer.js b/packages/blocks/src/api/test/serializer.js index a0a5eb2461d3bb..28281b42ea8c6d 100644 --- a/packages/blocks/src/api/test/serializer.js +++ b/packages/blocks/src/api/test/serializer.js @@ -243,6 +243,28 @@ describe( 'block serializer', () => { expect( content ).toBe( 'Bananas' ); } ); + it( 'serializes the freeform content fallback block with comment delimiters in nested context', () => { + registerBlockType( 'core/freeform-block', { + category: 'common', + title: 'freeform block', + attributes: { + fruit: { + type: 'string', + }, + }, + save: ( { attributes } ) => attributes.fruit, + } ); + setFreeformContentHandlerName( 'core/freeform-block' ); + const block = createBlock( 'core/freeform-block', { fruit: 'Bananas' } ); + + const content = serializeBlock( block, { isInnerBlocks: true } ); + + expect( content ).toBe( + '<!-- wp:freeform-block {\"fruit\":\"Bananas\"} -->\n' + + 'Bananas\n' + + '<!-- /wp:freeform-block -->' + ); + } ); it( 'serializes the unregistered fallback block without comment delimiters', () => { registerBlockType( 'core/unregistered-block', { category: 'common', diff --git a/packages/blocks/src/block-content-provider/index.js b/packages/blocks/src/block-content-provider/index.js index f7c15d27e1e8ad..352550a87be80f 100644 --- a/packages/blocks/src/block-content-provider/index.js +++ b/packages/blocks/src/block-content-provider/index.js @@ -31,7 +31,7 @@ const { Consumer, Provider } = createContext( () => {} ); const BlockContentProvider = ( { children, innerBlocks } ) => { const BlockContent = () => { // Value is an array of blocks, so defer to block serializer - const html = serialize( innerBlocks ); + const html = serialize( innerBlocks, { isInnerBlocks: true } ); // Use special-cased raw HTML tag to avoid default escaping return <RawHTML>{ html }</RawHTML>; From a7cc76ccaf004f57fe55d7e72a771b5bd1a6dfe2 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <miguelcsf@gmail.com> Date: Tue, 9 Jul 2019 16:25:39 +0100 Subject: [PATCH 462/664] Block API: Support nesting (InnerBlocks) in unknown block types (#14443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Block API: Support nesting in unknown block types When the block parser finds a block of unknown type, it wraps it in a special block (per `getUnregisteredTypeHandlerName`) — by default, this will be a block of type `core/missing`. This new outer block can then offer the user the possibility to extract the wrapped content. This commit updates the parsing logic so that the fallback block is fed the entire tree of content of the unknown block — if it has child blocks — and not just its own surface-level content. For instance, when parsing the following unknown block: ```html <!-- wp:my/unknown --> <p>Begin block that contains a list.</p> <!-- wp:list --> <ul><li>1</li><li>2</li></ul> <!-- /wp:list --> <p>End a block that contains a list.</p> <!-- /wp:my/unknown --> ``` Before, the rescued `originalContent` would have been: ```html <!-- wp:my/unknown --> <p>Begin block that contains a list.</p> <p>End block that contains a list.</p> <!-- /wp:my/unknown --> ``` Now, it would be the entire markup for `my/unknown`. * Add group block in the test template to make sure it works recursively Props Tug --- packages/blocks/src/api/parser.js | 75 +++++++++++++++-- packages/blocks/src/api/test/parser.js | 106 +++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 7 deletions(-) diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index b40532957c5f05..cb3a35d5fffe5b 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -383,6 +383,7 @@ export function createBlockWithFallback( blockNode ) { innerBlocks = [], innerHTML, } = blockNode; + const { innerContent } = blockNode; const freeformContentFallbackBlock = getFreeformContentHandlerName(); const unregisteredFallbackBlock = getUnregisteredTypeHandlerName() || freeformContentFallbackBlock; @@ -416,17 +417,39 @@ export function createBlockWithFallback( blockNode ) { let blockType = getBlockType( name ); if ( ! blockType ) { - // Preserve undelimited content for use by the unregistered type handler. - const originalUndelimitedContent = innerHTML; + // Since the constituents of the block node are extracted at the start + // of the present function, construct a new object rather than reuse + // `blockNode`. + const reconstitutedBlockNode = { + attrs: attributes, + blockName: originalName, + innerBlocks, + innerContent, + }; + + // Preserve undelimited content for use by the unregistered type + // handler. A block node's `innerHTML` isn't enough, as that field only + // carries the block's own HTML and not its nested blocks'. + const originalUndelimitedContent = serializeBlockNode( + reconstitutedBlockNode, + { isCommentDelimited: false } + ); + + // Preserve full block content for use by the unregistered type + // handler, block boundaries included. + const originalContent = serializeBlockNode( + reconstitutedBlockNode, + { isCommentDelimited: true } + ); // If detected as a block which is not registered, preserve comment // delimiters in content of unregistered type handler. if ( name ) { - innerHTML = getCommentDelimitedContent( name, attributes, innerHTML ); + innerHTML = originalContent; } name = unregisteredFallbackBlock; - attributes = { originalName, originalUndelimitedContent }; + attributes = { originalName, originalContent, originalUndelimitedContent }; blockType = getBlockType( name ); } @@ -457,15 +480,53 @@ export function createBlockWithFallback( blockNode ) { block.isValid = isValidBlockContent( blockType, block.attributes, innerHTML ); } - // Preserve original content for future use in case the block is parsed as - // invalid, or future serialization attempt results in an error. - block.originalContent = innerHTML; + // Preserve original content for future use in case the block is parsed + // as invalid, or future serialization attempt results in an error. + block.originalContent = block.originalContent || innerHTML; block = getMigratedBlock( block, attributes ); return block; } +/** + * Serializes a block node into the native HTML-comment-powered block format. + * CAVEAT: This function is intended for reserializing blocks as parsed by + * valid parsers and skips any validation steps. This is NOT a generic + * serialization function for in-memory blocks. For most purposes, see the + * following functions available in the `@wordpress/blocks` package: + * + * @see serializeBlock + * @see serialize + * + * For more on the format of block nodes as returned by valid parsers: + * + * @see `@wordpress/block-serialization-default-parser` package + * @see `@wordpress/block-serialization-spec-parser` package + * + * @param {Object} blockNode A block node as returned by a valid parser. + * @param {?Object} options Serialization options. + * @param {?boolean} options.isCommentDelimited Whether to output HTML comments around blocks. + * + * @return {string} An HTML string representing a block. + */ +export function serializeBlockNode( blockNode, options = {} ) { + const { isCommentDelimited = true } = options; + const { blockName, attrs = {}, innerBlocks = [], innerContent = [] } = blockNode; + + let childIndex = 0; + const content = innerContent.map( ( item ) => + // `null` denotes a nested block, otherwise we have an HTML fragment + item !== null ? + item : + serializeBlockNode( innerBlocks[ childIndex++ ], options ) + ).join( '\n' ).replace( /\n+/g, '\n' ).trim(); + + return isCommentDelimited ? + getCommentDelimitedContent( blockName, attrs, content ) : + content; +} + /** * Creates a parse implementation for the post content which returns a list of blocks. * diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js index 8c0e50feee3eec..f10001cb0c0133 100644 --- a/packages/blocks/src/api/test/parser.js +++ b/packages/blocks/src/api/test/parser.js @@ -19,6 +19,7 @@ import { isOfTypes, isValidByType, isValidByEnum, + serializeBlockNode, } from '../parser'; import { registerBlockType, @@ -757,6 +758,111 @@ describe( 'block parser', () => { } ); } ); + describe( 'serializeBlockNode', () => { + it( 'reserializes block nodes', () => { + const expected = `<!-- wp:columns --> + <div class="wp-block-columns has-2-columns"> + <!-- wp:column --> + <div class="wp-block-column"> + <!-- wp:paragraph --> + <p>A</p> + <!-- /wp:paragraph --> + </div> + <!-- /wp:column --> + <!-- wp:column --> + <div class="wp-block-column"> + <!-- wp:group --> + <div class="wp-block-group"><div class="wp-block-group__inner-container"> + <!-- wp:list --> + <ul><li>B</li><li>C</li></ul> + <!-- /wp:list --> + <!-- wp:paragraph --> + <p>D</p> + <!-- /wp:paragraph --> + </div></div> + <!-- /wp:group --> + </div> + <!-- /wp:column --> + </div> + <!-- /wp:columns -->`.replace( /\t/g, '' ); + const input = { + blockName: 'core/columns', + attrs: {}, + innerBlocks: [ + { + blockName: 'core/column', + attrs: {}, + innerBlocks: [ + { + blockName: 'core/paragraph', + attrs: {}, + innerBlocks: [], + innerHTML: '<p>A</p>', + innerContent: [ '<p>A</p>' ], + }, + ], + innerHTML: '<div class="wp-block-column"></div>', + innerContent: [ + '<div class="wp-block-column">', + null, + '</div>', + ], + }, + { + blockName: 'core/column', + attrs: {}, + innerBlocks: [ + { + blockName: 'core/group', + attrs: {}, + innerBlocks: [ + { + blockName: 'core/list', + attrs: {}, + innerBlocks: [], + innerHTML: '<ul><li>B</li><li>C</li></ul>', + innerContent: [ '<ul><li>B</li><li>C</li></ul>' ], + }, + { + blockName: 'core/paragraph', + attrs: {}, + innerBlocks: [], + innerHTML: '<p>D</p>', + innerContent: [ '<p>D</p>' ], + }, + ], + innerHTML: '<div class="wp-block-group"><div class="wp-block-group__inner-container"></div></div>', + innerContent: [ + '<div class="wp-block-group"><div class="wp-block-group__inner-container">', + null, + '', + null, + '</div></div>' ], + }, + ], + innerHTML: '<div class="wp-block-column"></div>', + innerContent: [ + '<div class="wp-block-column">', + null, + '</div>', + ], + }, + ], + innerHTML: '<div class="wp-block-columns has-2-columns"></div>', + innerContent: [ + '<div class="wp-block-columns has-2-columns">', + null, + '', + null, + '</div>', + ], + }; + const actual = serializeBlockNode( input ); + + expect( actual ).toEqual( expected ); + } ); + } ); + describe( 'parse() of @wordpress/block-serialization-spec-parser', () => { // run the test cases using the PegJS defined parser testCases( parsePegjs ); From ceacc108bc35ce8d471c67c5b6c6c62f7bd3c0a6 Mon Sep 17 00:00:00 2001 From: Matt Chowning <mchowning@gmail.com> Date: Tue, 9 Jul 2019 12:20:11 -0400 Subject: [PATCH 463/664] [Mobile] Insert new block below post title if post title is selected (#16440) * Insert new block below post title if post title is selected Previously, the new block would have been inserted at the end of the post, which is almost always going to be off-screen on mobile. * Fix blockCount reference Co-Authored-By: etoledom <etoledom@icloud.com> --- .../src/components/block-list/index.native.js | 17 +++++++++--- .../components/visual-editor/index.native.js | 27 +++++++++++++++++++ .../src/components/post-title/index.native.js | 27 ++++--------------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 713fe94d7ea209..117e3e5fcd4ee4 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -41,6 +41,7 @@ export class BlockList extends Component { this.keyboardDidHide = this.keyboardDidHide.bind( this ); this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( this ); this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this ); + this.getNewBlockInsertionIndex = this.getNewBlockInsertionIndex.bind( this ); this.state = { blockTypePickerVisible: false, @@ -70,12 +71,22 @@ export class BlockList extends Component { // do replace here this.props.replaceBlock( this.props.selectedBlockClientId, newBlock ); } else { - const indexAfterSelected = this.props.selectedBlockOrder + 1; - const insertionIndex = indexAfterSelected || this.props.blockCount; - this.props.insertBlock( newBlock, insertionIndex ); + this.props.insertBlock( newBlock, this.getNewBlockInsertionIndex() ); } } + getNewBlockInsertionIndex() { + if ( this.props.isPostTitleSelected ) { + // if post title selected, insert at top of post + return 0; + } else if ( this.props.selectedBlockOrder === -1 ) { + // if no block selected, insert at end of post + return this.props.blockCount; + } + // insert after selected block + return this.props.selectedBlockOrder + 1; + } + blockHolderBorderStyle() { return this.state.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; } diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js index ddc0995123ff9f..8a69b8af798831 100644 --- a/packages/edit-post/src/components/visual-editor/index.native.js +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -15,6 +15,26 @@ import { ReadableContentView } from '@wordpress/components'; import styles from './style.scss'; class VisualEditor extends Component { + constructor() { + super( ...arguments ); + + this.onPostTitleSelect = this.onPostTitleSelect.bind( this ); + this.onPostTitleUnselect = this.onPostTitleUnselect.bind( this ); + + this.state = { + isPostTitleSelected: false, + }; + } + + onPostTitleSelect() { + this.setState( { isPostTitleSelected: true } ); + this.props.clearSelectedBlock(); + } + + onPostTitleUnselect() { + this.setState( { isPostTitleSelected: false } ); + } + renderHeader() { const { editTitle, @@ -28,6 +48,9 @@ class VisualEditor extends Component { innerRef={ setTitleRef } title={ title } onUpdate={ editTitle } + onSelect={ this.onPostTitleSelect } + onUnselect={ this.onPostTitleUnselect } + isSelected={ this.state.isPostTitleSelected } placeholder={ __( 'Add title' ) } borderStyle={ this.props.isFullyBordered ? @@ -63,6 +86,7 @@ class VisualEditor extends Component { isFullyBordered={ isFullyBordered } rootViewHeight={ rootViewHeight } safeAreaBottomInset={ safeAreaBottomInset } + isPostTitleSelected={ this.state.isPostTitleSelected } /> </BlockEditorProvider> ); @@ -87,7 +111,10 @@ export default compose( [ resetEditorBlocks, } = dispatch( 'core/editor' ); + const { clearSelectedBlock } = dispatch( 'core/block-editor' ); + return { + clearSelectedBlock, editTitle( title ) { editPost( { title } ); }, diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index d325e81fc725d2..67d58ff14e10cb 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -25,13 +25,7 @@ class PostTitle extends Component { constructor() { super( ...arguments ); - this.onSelect = this.onSelect.bind( this ); - this.onUnselect = this.onUnselect.bind( this ); this.titleViewRef = null; - - this.state = { - isSelected: false, - }; } componentDidMount() { @@ -41,25 +35,16 @@ class PostTitle extends Component { } handleFocusOutside() { - this.onUnselect(); + this.props.onUnselect(); } focus() { if ( this.titleViewRef ) { this.titleViewRef.focus(); - this.setState( { isSelected: true } ); + this.props.onSelect(); } } - onSelect() { - this.setState( { isSelected: true } ); - this.props.clearSelectedBlock(); - } - - onUnselect() { - this.setState( { isSelected: false } ); - } - render() { const { placeholder, @@ -70,12 +55,12 @@ class PostTitle extends Component { } = this.props; const decodedPlaceholder = decodeEntities( placeholder ); - const borderColor = this.state.isSelected ? focusedBorderColor : 'transparent'; + const borderColor = this.props.isSelected ? focusedBorderColor : 'transparent'; return ( <View style={ [ styles.titleContainer, borderStyle, { borderColor } ] } - accessible={ ! this.state.isSelected } + accessible={ ! this.props.isSelected } accessibilityLabel={ isEmpty( title ) ? /* translators: accessibility text. empty post title. */ @@ -90,7 +75,7 @@ class PostTitle extends Component { <RichText tagName={ 'p' } rootTagsToEliminate={ [ 'strong' ] } - unstableOnFocus={ this.onSelect } + unstableOnFocus={ this.props.onSelect } onBlur={ this.props.onBlur } // always assign onBlur as a props multiline={ false } style={ style } @@ -125,7 +110,6 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { const { insertDefaultBlock, - clearSelectedBlock, } = dispatch( 'core/block-editor' ); return { @@ -134,7 +118,6 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { }, onUndo: undo, onRedo: redo, - clearSelectedBlock, }; } ); From 23aef27c49eeedd811c9b4a1734d534bf27e4411 Mon Sep 17 00:00:00 2001 From: Marcus Kazmierczak <marcus@mkaz.com> Date: Tue, 9 Jul 2019 13:46:50 -0700 Subject: [PATCH 464/664] Make capitalization of block names consistent per #16118 (#16469) --- docs/designers-developers/designers/block-design.md | 10 +++++----- .../developers/block-api/block-annotations.md | 2 +- .../developers/block-api/block-deprecation.md | 2 +- .../developers/block-api/block-registration.md | 8 ++++---- .../block-controls-toolbar-and-sidebar.md | 4 ++-- .../tutorials/format-api/2-toolbar-button.md | 2 +- .../tutorials/metabox/meta-block-4-use-data.md | 2 +- docs/designers-developers/key-concepts.md | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/designers-developers/designers/block-design.md b/docs/designers-developers/designers/block-design.md index 1014ec0bdcd146..7ee481637bb225 100644 --- a/docs/designers-developers/designers/block-design.md +++ b/docs/designers-developers/designers/block-design.md @@ -25,7 +25,7 @@ Each Settings Sidebar comes with an "Advanced" section by default. This area hou ## Setup state vs. live preview state -Setup states, sometimes referred to as "placeholders", can be used to walk users through an initial process before showing the live preview state of the block. The setup process gathers information from the user that is needed to render the block. A block’s setup state is indicated with a grey background to provide clear differentiation for the user. Not all blocks have setup states — for example, the paragraph block. +Setup states, sometimes referred to as "placeholders", can be used to walk users through an initial process before showing the live preview state of the block. The setup process gathers information from the user that is needed to render the block. A block’s setup state is indicated with a grey background to provide clear differentiation for the user. Not all blocks have setup states — for example, the Paragraph block. ![An example of a gallery block’s setup state on a grey background](https://make.wordpress.org/design/files/2018/12/gallery-setup.png) @@ -122,7 +122,7 @@ Do not put controls that are essential to the block in the sidebar, or the block The “Block” tab of the Settings Sidebar can contain additional block options and configuration. Keep in mind that a user can dismiss the sidebar and never use it. You should not put critical options in the Sidebar. -![A screenshot of the paragraph block's advanced settings in the sidebar](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/assets/advanced-settings-do.png) +![A screenshot of the Paragraph block's advanced settings in the sidebar](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/designers/assets/advanced-settings-do.png) **Do:** Because the Drop Cap feature is not necessary for the basic operation of the block, you can put it to the Block tab as optional configuration. @@ -140,9 +140,9 @@ To demonstrate some of these practices, here are a few annotated examples of def ### Paragraph -The most basic unit of the editor. The paragraph block is a simple input field. +The most basic unit of the editor. The Paragraph block is a simple input field. -![Paragraph Block](https://cldup.com/HVJe5bGZ8H-3000x3000.png) +![Paragraph block](https://cldup.com/HVJe5bGZ8H-3000x3000.png) ### Placeholder: @@ -158,7 +158,7 @@ The most basic unit of the editor. The paragraph block is a simple input field. Basic image block. -![Image Block Placeholder](https://cldup.com/w6FNywNsj1-3000x3000.png) +![Image block placeholder](https://cldup.com/w6FNywNsj1-3000x3000.png) ### Placeholder: diff --git a/docs/designers-developers/developers/block-api/block-annotations.md b/docs/designers-developers/developers/block-api/block-annotations.md index 962571b60b8f5a..d74a300c54f0b7 100644 --- a/docs/designers-developers/developers/block-api/block-annotations.md +++ b/docs/designers-developers/developers/block-api/block-annotations.md @@ -32,7 +32,7 @@ All available properties can be found in the API documentation of the `addAnnota The property `richTextIdentifier` is the identifier of the RichText instance the annotation applies to. This is necessary because blocks may have multiple rich text instances that are used to manage data for different attributes, so you need to pass this in order to highlight text within the correct one. -For example the paragraph block only has a single RichText instance, with the identifer `content`. The quote block type has 2 RichText instances, so if you wish to highlight text in the citation, you need to pass `citation` as the `richTextIdentifier` when adding an annotation. To target the quote content, you need to use the identifier `value`. Refer to the source code of the block type to find the correct identifier. +For example the Paragraph block only has a single RichText instance, with the identifer `content`. The quote block type has 2 RichText instances, so if you wish to highlight text in the citation, you need to pass `citation` as the `richTextIdentifier` when adding an annotation. To target the quote content, you need to use the identifier `value`. Refer to the source code of the block type to find the correct identifier. ## Block annotation diff --git a/docs/designers-developers/developers/block-api/block-deprecation.md b/docs/designers-developers/developers/block-api/block-deprecation.md index 0e5be3c05f27a8..4645b3b5207d6b 100644 --- a/docs/designers-developers/developers/block-api/block-deprecation.md +++ b/docs/designers-developers/developers/block-api/block-deprecation.md @@ -275,6 +275,6 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', { ``` {% end %} -In the example above we updated the block to use an inner paragraph block with a title instead of a title attribute. +In the example above we updated the block to use an inner Paragraph block with a title instead of a title attribute. *Above are example cases of block deprecation. For more, real-world examples, check for deprecations in the [core block library](/packages/block-library/src/README.md). Core blocks have been updated across releases and contain simple and complex deprecations.* diff --git a/docs/designers-developers/developers/block-api/block-registration.md b/docs/designers-developers/developers/block-api/block-registration.md index a5e1b982797a0b..984ff96ae64320 100644 --- a/docs/designers-developers/developers/block-api/block-registration.md +++ b/docs/designers-developers/developers/block-api/block-registration.md @@ -174,7 +174,7 @@ attributes: { Transforms provide rules for what a block can be transformed from and what it can be transformed to. A block can be transformed from another block, a shortcode, a regular expression, a file or a raw DOM node. -For example, a paragraph block can be transformed into a heading block. This uses the `createBlock` function from the [`wp-blocks` package](/packages/blocks/README.md#createBlock) +For example, a Paragraph block can be transformed into a Heading block. This uses the `createBlock` function from the [`wp-blocks` package](/packages/blocks/README.md#createBlock) {% codetabs %} {% ES5 %} @@ -274,7 +274,7 @@ transforms: { ``` {% end %} -A block can also be transformed into another block type. For example, a heading block can be transformed into a paragraph block. +A block can also be transformed into another block type. For example, a Heading block can be transformed into a Paragraph block. {% codetabs %} {% ES5 %} @@ -345,7 +345,7 @@ transforms: { {% end %} -A block with innerBlocks can also be transformed from and to another block with innerBlocks. +A block with InnerBlocks can also be transformed from and to another block with InnerBlocks. {% codetabs %} {% ES5 %} @@ -478,7 +478,7 @@ transforms: { ``` {% end %} -A prefix transform is a transform that will be applied if the user prefixes some text in e.g. the paragraph block with a given pattern and a trailing space. +A prefix transform is a transform that will be applied if the user prefixes some text in e.g. the Paragraph block with a given pattern and a trailing space. {% codetabs %} {% ES5 %} diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md index 028b0e8e56a0f8..5e120d684f0356 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/block-controls-toolbar-and-sidebar.md @@ -4,7 +4,7 @@ To simplify block customization and ensure a consistent experience for users, th ## Block Toolbar -![Screenshot of the rich text toolbar applied to a paragraph block inside the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-text.png) +![Screenshot of the rich text toolbar applied to a Paragraph block inside the block editor](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/toolbar-text.png) When the user selects a block, a number of control buttons may be shown in a toolbar above the selected block. Some of these block-level controls are included automatically if the editor is able to transform the block to another type, or if the focused element is a RichText component. @@ -167,7 +167,7 @@ Note that `BlockControls` is only visible when the block is currently selected a ## Inspector -![Screenshot of the inspector panel focused on the settings for a paragraph block](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/inspector.png) +![Screenshot of the inspector panel focused on the settings for a Paragraph block](https://raw.githubusercontent.com/WordPress/gutenberg/master/docs/designers-developers/assets/inspector.png) The Settings Sidebar is used to display less-often-used settings or settings that require more screen space. The Settings Sidebar should be used for **block-level settings only**. diff --git a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md index 53b776b562b410..52b07b6002581c 100644 --- a/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md +++ b/docs/designers-developers/developers/tutorials/format-api/2-toolbar-button.md @@ -40,7 +40,7 @@ You may also want to check that upon clicking the button the `toggle format` mes By default, the button is rendered on every rich text toolbar (image captions, buttons, paragraphs, etc). It is possible to render the button only on blocks of a certain type by using `wp.data.withSelect` together with `wp.compose.ifCondition`. -The following sample code renders the previously shown button only on paragraph blocks: +The following sample code renders the previously shown button only on Paragraph blocks: ```js ( function( wp ) { diff --git a/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md b/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md index d0ee92d00720ce..6e7d5b92d4d405 100644 --- a/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md +++ b/docs/designers-developers/developers/tutorials/metabox/meta-block-4-use-data.md @@ -20,7 +20,7 @@ add_filter( 'the_content', 'myguten_content_filter' ); ## Use Post Meta in Block -You can also use the post meta data in other blocks. For this example the data is loaded at the end of every paragraph block when it is rendered, ie. shown to the user. You can replace this for any core or custom block types as needed. +You can also use the post meta data in other blocks. For this example the data is loaded at the end of every Paragraph block when it is rendered, ie. shown to the user. You can replace this for any core or custom block types as needed. In PHP, use the [register_block_type](https://developer.wordpress.org/reference/functions/register_block_type/) function to set a callback when the block is rendered to include the meta value. diff --git a/docs/designers-developers/key-concepts.md b/docs/designers-developers/key-concepts.md index 7a3f18526b03ba..d7f16578f90f87 100644 --- a/docs/designers-developers/key-concepts.md +++ b/docs/designers-developers/key-concepts.md @@ -18,7 +18,7 @@ Blocks can be static or dynamic. Static blocks contain rendered content and an o Each block contains Attributes or configuration settings, which can be sourced from raw HTML in the content, via meta or other customizable origins. -The Paragraph is the default Block. Instead of a new line upon typing return on a keyboard, try to think of it as an empty paragraph block (type / to trigger an autocompleting Slash Inserter -- /image will pull up Images as well as Instagram embeds). +The Paragraph is the default block. Instead of a new line upon typing return on a keyboard, try to think of it as an empty Paragraph block (type / to trigger an autocompleting Slash Inserter -- /image will pull up Images as well as Instagram embeds). Users insert new blocks by clicking the plus button for the Block Inserter, typing / for the Slash Inserter or typing return for a blank Paragraph block. From 5cb8b8f3a025e4ecf24b9b3ec31988b2c7b02d5c Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Tue, 9 Jul 2019 22:03:13 +0100 Subject: [PATCH 465/664] Update to allow alternative Blocks to handle Grouping interactions (#16278) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updates factory util to reference registered Grouping Block Previously the method that determined whether a given Block was the block to be used for “Grouping” interactions was hardcoded as “core/group” within `isContainerBlock`. Updated to utilise the registered Grouping Block in order to allow alternative Blocks to be utilised for Grouping. * Attempt e2e test to validate usage of alternate Block for grouping Currently this doesn’t work because window doesn’t have NODE defined. * Adds e2e test to confirm ability to register alternative Block for Grouping Now using custom Gutenberg Plugin. Currently failing as the test Block appears to be unavailable within the Editor and doesn’t pass the `canInsertBlockType` test. * Utilise withSelect to get the current Grouping Block name * Removes unrequired test suite * Fix bug with misnamed variable * Remove hardcoded group block ref Addresses https://github.com/WordPress/gutenberg/pull/16278#pullrequestreview-258540930 * Remove / update refs to core/group within comments * Block API: Stub default attributes, keywords values for block type registration * Revert "Block API: Stub default attributes, keywords values for block type registration" This reverts commit be6e7679d0d30be5945da13700f573b6ecece0eb. --- .../src/components/block-actions/index.js | 8 +++- packages/blocks/src/api/factory.js | 10 ++--- packages/blocks/src/api/test/factory.js | 31 ++++++++++++-- .../plugins/custom-grouping-block.php | 27 ++++++++++++ .../plugins/custom-grouping-block/index.js | 28 +++++++++++++ .../__snapshots__/block-grouping.test.js.snap | 16 ++++++++ .../e2e-tests/specs/block-grouping.test.js | 41 +++++++++++++++++-- .../convert-button.js | 25 +++++++---- 8 files changed, 164 insertions(+), 22 deletions(-) create mode 100644 packages/e2e-tests/plugins/custom-grouping-block.php create mode 100644 packages/e2e-tests/plugins/custom-grouping-block/index.js diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index c10d03a5c060f0..ffb14ffca8c5eb 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -117,8 +117,14 @@ export default compose( [ return; } + const { + getGroupingBlockName, + } = select( 'core/blocks' ); + + const groupingBlockName = getGroupingBlockName(); + // Activate the `transform` on `core/group` which does the conversion - const newBlocks = switchToBlockType( blocks, 'core/group' ); + const newBlocks = switchToBlockType( blocks, groupingBlockName ); if ( ! newBlocks ) { return; diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 92074c35a6bafd..8d659b1c878864 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -25,7 +25,7 @@ import { createHooks, applyFilters } from '@wordpress/hooks'; /** * Internal dependencies */ -import { getBlockType, getBlockTypes } from './registration'; +import { getBlockType, getBlockTypes, getGroupingBlockName } from './registration'; import { normalizeBlockType } from './utils'; /** @@ -147,8 +147,8 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { return false; } - // Don't allow single 'core/group' blocks to be transformed into - // a 'core/group' block. + // Don't allow single Grouping blocks to be transformed into + // a Grouping block. if ( ! isMultiBlock && isContainerGroupBlock( sourceBlock.name ) && isContainerGroupBlock( transform.blockName ) ) { return false; } @@ -252,7 +252,7 @@ export const isWildcardBlockTransform = ( t ) => t && t.type === 'block' && Arra * * @return {boolean} whether or not the Block is the container Block type */ -export const isContainerGroupBlock = ( name ) => name === 'core/group'; +export const isContainerGroupBlock = ( name ) => name === getGroupingBlockName(); /** * Determines whether the provided Blocks are of the same type @@ -374,7 +374,7 @@ export function switchToBlockType( blocks, name ) { const firstBlock = blocksArray[ 0 ]; const sourceName = firstBlock.name; - // Unless it's a `core/group` Block then for multi block selections + // Unless it's a Grouping Block then for multi block selections // check that all Blocks are of the same type otherwise // we can't run a conversion if ( ! isContainerGroupBlock( name ) && isMultiBlock && ! isBlockSelectionOfSameType( blocksArray ) ) { diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index 973964bfe05bae..bb34cb60cf2cd9 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -23,6 +23,7 @@ import { getBlockTypes, registerBlockType, unregisterBlockType, + setGroupingBlockName, } from '../registration'; describe( 'block factory', () => { @@ -1554,12 +1555,34 @@ describe( 'block factory', () => { } ); describe( 'isContainerGroupBlock', () => { - it( 'should return true when passed block name matches "core/group"', () => { - expect( isContainerGroupBlock( 'core/group' ) ).toBe( true ); + beforeEach( () => { + registerBlockType( 'core/registered-grouping-block', { + attributes: { + value: { + type: 'string', + }, + }, + transforms: { + from: [ { + type: 'block', + blocks: [ '*' ], + transform: noop, + } ], + }, + save: noop, + category: 'common', + title: 'A Block with InnerBlocks that supports grouping', + } ); + } ); + + it( 'should return true when passed block name that matches the registered "Grouping" Block', () => { + setGroupingBlockName( 'registered-grouping-block' ); + expect( isContainerGroupBlock( 'registered-grouping-block' ) ).toBe( true ); } ); - it( 'should return false when passed block name does not match "core/group"', () => { - expect( isContainerGroupBlock( 'core/some-test-name' ) ).toBe( false ); + it( 'should return false when passed block name does not match the registered "Grouping" Block', () => { + setGroupingBlockName( 'registered-grouping-block' ); + expect( isContainerGroupBlock( 'core/group' ) ).toBe( false ); } ); } ); diff --git a/packages/e2e-tests/plugins/custom-grouping-block.php b/packages/e2e-tests/plugins/custom-grouping-block.php new file mode 100644 index 00000000000000..da29a788ff7bca --- /dev/null +++ b/packages/e2e-tests/plugins/custom-grouping-block.php @@ -0,0 +1,27 @@ +<?php +/** + * Plugin Name: Gutenberg Test Custom Grouping Block + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-custom-grouping-block + */ + +/** + * Registers a custom script for the plugin. + */ +function enqueue_custom_grouping_block_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-custom-grouping-block', + plugins_url( 'custom-grouping-block/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-block-editor', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'custom-grouping-block/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_custom_grouping_block_plugin_script' ); diff --git a/packages/e2e-tests/plugins/custom-grouping-block/index.js b/packages/e2e-tests/plugins/custom-grouping-block/index.js new file mode 100644 index 00000000000000..591554ac1367ba --- /dev/null +++ b/packages/e2e-tests/plugins/custom-grouping-block/index.js @@ -0,0 +1,28 @@ +( function() { + wp.blocks.registerBlockType( 'test/alternative-group-block', { + title: 'Alternative Group Block', + category: 'layout', + icon: 'yes', + edit() { + return wp.element.createElement( wp.blockEditor.InnerBlocks ); + }, + + save() { + return wp.element.createElement( wp.blockEditor.InnerBlocks.Content ); + }, + transforms: { + from: [ { + type: 'block', + blocks: [ '*' ], + isMultiBlock: true, + __experimentalConvert( blocks ) { + const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { + return wp.blocks.createBlock( name, attributes, innerBlocks ); + } ); + + return wp.blocks.createBlock( 'test/alternative-group-block', {}, groupInnerBlocks ); + }, + } ], + }, + } ); +}() ); diff --git a/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap index e41b27cdf3aa1a..4b786e99fdb23d 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap @@ -97,3 +97,19 @@ exports[`Block Grouping Preserving selected blocks attributes preserves width al <!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; + +exports[`Block Grouping Registering alternative Blocks to handle Grouping interactions should use registered grouping block for grouping interactions 1`] = ` +"<!-- wp:test/alternative-group-block --> +<!-- wp:paragraph --> +<p>First Paragraph</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Second Paragraph</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>Third Paragraph</p> +<!-- /wp:paragraph --> +<!-- /wp:test/alternative-group-block -->" +`; diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/block-grouping.test.js index a1b25f055edde9..3a42f3f6e43f6a 100644 --- a/packages/e2e-tests/specs/block-grouping.test.js +++ b/packages/e2e-tests/specs/block-grouping.test.js @@ -10,6 +10,8 @@ import { transformBlockTo, getAllBlocks, getAvailableBlockTransforms, + activatePlugin, + deactivatePlugin, } from '@wordpress/e2e-test-utils'; async function insertBlocksOfSameType() { @@ -111,7 +113,7 @@ describe( 'Block Grouping', () => { } ); } ); - describe( 'Container Block availability', () => { + describe( 'Grouping Block availability', () => { beforeEach( async () => { // Disable the Group block await page.evaluate( () => { @@ -133,7 +135,7 @@ describe( 'Block Grouping', () => { } ); } ); - it( 'does not show group transform if container block is disabled', async () => { + it( 'does not show group transform if Grouping block is disabled', async () => { const availableTransforms = await getAvailableBlockTransforms(); expect( @@ -141,7 +143,7 @@ describe( 'Block Grouping', () => { ).not.toContain( 'Group' ); } ); - it( 'does not show group option in the options toolbar if container block is disabled ', async () => { + it( 'does not show group option in the options toolbar if Grouping block is disabled ', async () => { await clickBlockToolbarButton( 'More options' ); const blockOptionsDropdownHTML = await page.evaluate( () => document.querySelector( '.block-editor-block-settings-menu__content' ).innerHTML ); @@ -180,4 +182,37 @@ describe( 'Block Grouping', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); + + describe( 'Registering alternative Blocks to handle Grouping interactions', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-custom-grouping-block' ); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-custom-grouping-block' ); + } ); + + it( 'should use registered grouping block for grouping interactions', async () => { + // Set custom Block as the Block to use for Grouping + await page.evaluate( () => { + window.wp.blocks.setGroupingBlockName( 'test/alternative-group-block' ); + } ); + + // Creating test blocks + await insertBlocksOfSameType(); + + // Multiselect via keyboard. + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + // Group - this will use whichever Block is registered as the Grouping Block + // as opposed to "transformTo()" which uses whatever is passed to it. To + // ensure this test is meaningful we must rely on what is registered. + await clickBlockToolbarButton( 'More options' ); + const groupButton = await page.waitForXPath( '//button[text()="Group"]' ); + await groupButton.click(); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + } ); } ); diff --git a/packages/editor/src/components/convert-to-group-buttons/convert-button.js b/packages/editor/src/components/convert-to-group-buttons/convert-button.js index 2aa2fc9dcd6b19..8b024d325ae35b 100644 --- a/packages/editor/src/components/convert-to-group-buttons/convert-button.js +++ b/packages/editor/src/components/convert-to-group-buttons/convert-button.js @@ -55,33 +55,40 @@ export default compose( [ canInsertBlockType, } = select( 'core/block-editor' ); - const containerBlockAvailable = canInsertBlockType( 'core/group' ); + const { + getGroupingBlockName, + } = select( 'core/blocks' ); + + const groupingBlockName = getGroupingBlockName(); + + const groupingBlockAvailable = canInsertBlockType( groupingBlockName ); const blocksSelection = getBlocksByClientId( clientIds ); - const isSingleContainerBlock = blocksSelection.length === 1 && blocksSelection[ 0 ] && blocksSelection[ 0 ].name === 'core/group'; + const isSingleGroupingBlock = blocksSelection.length === 1 && blocksSelection[ 0 ] && blocksSelection[ 0 ].name === groupingBlockName; // Do we have - // 1. Container block available to be inserted? + // 1. Grouping block available to be inserted? // 2. One or more blocks selected // (we allow single Blocks to become groups unless // they are a soltiary group block themselves) const isGroupable = ( - containerBlockAvailable && + groupingBlockAvailable && blocksSelection.length && - ! isSingleContainerBlock + ! isSingleGroupingBlock ); // Do we have a single Group Block selected and does that group have inner blocks? - const isUngroupable = isSingleContainerBlock && !! blocksSelection[ 0 ].innerBlocks.length; + const isUngroupable = isSingleGroupingBlock && !! blocksSelection[ 0 ].innerBlocks.length; return { isGroupable, isUngroupable, blocksSelection, + groupingBlockName, }; } ), - withDispatch( ( dispatch, { clientIds, onToggle = noop, blocksSelection = [] } ) => { + withDispatch( ( dispatch, { clientIds, onToggle = noop, blocksSelection = [], groupingBlockName } ) => { const { replaceBlocks, } = dispatch( 'core/block-editor' ); @@ -92,8 +99,8 @@ export default compose( [ return; } - // Activate the `transform` on `core/group` which does the conversion - const newBlocks = switchToBlockType( blocksSelection, 'core/group' ); + // Activate the `transform` on the Grouping Block which does the conversion + const newBlocks = switchToBlockType( blocksSelection, groupingBlockName ); if ( newBlocks ) { replaceBlocks( From 4737315925f66dd5438dd17a43aa76ec5519f5f8 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Wed, 10 Jul 2019 13:11:28 +1000 Subject: [PATCH 466/664] Bump plugin version to 6.1.0 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 9ae6f7d37b412c..86c57cc651359e 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.1.0-rc.1 + * Version: 6.1.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 39f81f74f0aa5f..288e97c136bd41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.1.0-rc.1", + "version": "6.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6f3c286a1ef630..dff6817d315a51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.1.0-rc.1", + "version": "6.1.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From a32d813a6c4243dbf03725da1c7d961c62409f44 Mon Sep 17 00:00:00 2001 From: Nahid Ferdous Mohit <nfmohit49@gmail.com> Date: Wed, 10 Jul 2019 11:24:47 +0600 Subject: [PATCH 467/664] [Enhancement] Introduce Link Target in Button Block (#10128) * Introduce link target in Button block * Updated popover style * Moved 'Open in New Tab' toggle to InspectorControls * Implemented inline link replacing URLPopover * Updated button styles * Make the BaseContrrol available in the DOM always, but hide it with CSS until the block is selected * Limit width of the text field if empty * Fix width and positioning issues of the URLInput and Autocomplete suggestions list * Prevent autocomplete suggestions list from showing up when block is deselected * Fixed the rel attribute not working issue * Fixed validation errors * Reverted changes from the package-lock.json file --- .../src/components/url-input/index.js | 26 ++- .../src/components/url-input/style.scss | 17 ++ packages/block-library/src/button/block.json | 12 ++ .../block-library/src/button/deprecated.js | 67 +++++- packages/block-library/src/button/edit.js | 192 +++++++++++------- packages/block-library/src/button/editor.scss | 62 +++--- packages/block-library/src/button/save.js | 4 + 7 files changed, 269 insertions(+), 111 deletions(-) diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 88562b719802b0..39035bd1e9d4c8 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -223,8 +223,14 @@ class URLInput extends Component { this.inputRef.current.focus(); } + static getDerivedStateFromProps( { showSuggestionsOverride }, { showSuggestions } ) { + return { + showSuggestions: showSuggestionsOverride !== undefined ? showSuggestionsOverride : showSuggestions, + }; + } + render() { - const { value = '', autoFocus = true, instanceId, className } = this.props; + const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder } = this.props; const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state; const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`; @@ -232,8 +238,12 @@ class URLInput extends Component { /* eslint-disable jsx-a11y/no-autofocus */ return ( - <div className={ classnames( 'editor-url-input block-editor-url-input', className ) }> + <div className={ classnames( 'editor-url-input block-editor-url-input', className, { + 'is-full-width': isFullWidth, + 'has-border': hasBorder, + } ) }> <input + id={ id } autoFocus={ autoFocus } type="text" aria-label={ __( 'URL' ) } @@ -254,9 +264,17 @@ class URLInput extends Component { { ( loading ) && <Spinner /> } { showSuggestions && !! suggestions.length && - <Popover position="bottom" noArrow focusOnMount={ false }> + <Popover + position="bottom" + noArrow + focusOnMount={ false } + > <div - className="editor-url-input__suggestions block-editor-url-input__suggestions" + className={ classnames( + 'editor-url-input__suggestions', + 'block-editor-url-input__suggestions', + `${ className }__suggestions` + ) } id={ suggestionsListboxId } ref={ this.autocompleteRef } role="listbox" diff --git a/packages/block-editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss index 54c1128281b72b..5e6f34762a4605 100644 --- a/packages/block-editor/src/components/url-input/style.scss +++ b/packages/block-editor/src/components/url-input/style.scss @@ -31,6 +31,23 @@ $input-size: 300px; } } + &.has-border input[type="text"] { + border: 1px solid $dark-gray-500; + border-radius: 4px; + } + + &.is-full-width { + width: 100%; + + input[type="text"] { + width: 100%; + } + + &__suggestions { + width: 100%; + } + } + .components-spinner { position: absolute; right: $input-padding; diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 61c040d8f4508d..32fda95348d8a1 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -30,6 +30,18 @@ }, "customTextColor": { "type": "string" + }, + "linkTarget": { + "type": "string", + "source": "attribute", + "selector": "a", + "attribute": "target" + }, + "rel": { + "type": "string", + "source": "attribute", + "selector": "a", + "attribute": "rel" } } } diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index 5e9a29777b99aa..14f65ff5c98104 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -2,11 +2,15 @@ * External dependencies */ import { omit } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies */ -import { RichText } from '@wordpress/block-editor'; +import { + RichText, + getColorClassName, +} from '@wordpress/block-editor'; const colorsMigration = ( attributes ) => { return omit( { @@ -37,6 +41,67 @@ const blockAttributes = { }; const deprecated = [ + { + attributes: { + ...blockAttributes, + align: { + type: 'string', + default: 'none', + }, + backgroundColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + customBackgroundColor: { + type: 'string', + }, + customTextColor: { + type: 'string', + }, + }, + save( { attributes } ) { + const { + url, + text, + title, + backgroundColor, + textColor, + customBackgroundColor, + customTextColor, + } = attributes; + + const textClass = getColorClassName( 'color', textColor ); + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + + const buttonClasses = classnames( 'wp-block-button__link', { + 'has-text-color': textColor || customTextColor, + [ textClass ]: textClass, + 'has-background': backgroundColor || customBackgroundColor, + [ backgroundClass ]: backgroundClass, + } ); + + const buttonStyle = { + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + color: textClass ? undefined : customTextColor, + }; + + return ( + <div> + <RichText.Content + tagName="a" + className={ buttonClasses } + href={ url } + title={ title } + style={ buttonStyle } + value={ text } + /> + </div> + ); + }, + migrate: colorsMigration, + }, { attributes: { ...blockAttributes, diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 142f9b0a06835f..f4d38282c196ce 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -7,12 +7,19 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; import { - Dashicon, - IconButton, + Component, +} from '@wordpress/element'; +import { + compose, + withInstanceId, +} from '@wordpress/compose'; +import { withFallbackStyles, + PanelBody, + TextControl, + ToggleControl, + BaseControl, } from '@wordpress/components'; import { URLInput, @@ -37,11 +44,15 @@ const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { }; } ); +const NEW_TAB_REL = 'noreferrer noopener'; + class ButtonEdit extends Component { constructor() { super( ...arguments ); this.nodeRef = null; this.bindRef = this.bindRef.bind( this ); + this.onSetLinkRel = this.onSetLinkRel.bind( this ); + this.onToggleOpenInNewTab = this.onToggleOpenInNewTab.bind( this ); } bindRef( node ) { @@ -51,6 +62,27 @@ class ButtonEdit extends Component { this.nodeRef = node; } + onSetLinkRel( value ) { + this.props.setAttributes( { rel: value } ); + } + + onToggleOpenInNewTab( value ) { + const { rel } = this.props.attributes; + const linkTarget = value ? '_blank' : undefined; + + let updatedRel = rel; + if ( linkTarget && ! rel ) { + updatedRel = NEW_TAB_REL; + } else if ( ! linkTarget && rel === NEW_TAB_REL ) { + updatedRel = undefined; + } + + this.props.setAttributes( { + linkTarget, + rel: updatedRel, + } ); + } + render() { const { attributes, @@ -61,90 +93,108 @@ class ButtonEdit extends Component { fallbackBackgroundColor, fallbackTextColor, setAttributes, - isSelected, className, + instanceId, + isSelected, } = this.props; const { text, url, title, + linkTarget, + rel, } = attributes; + const linkId = `wp-block-button__inline-link-${ instanceId }`; + return ( - <> - <div className={ className } title={ title } ref={ this.bindRef }> - <RichText - placeholder={ __( 'Add text…' ) } - value={ text } - onChange={ ( value ) => setAttributes( { text: value } ) } - formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } - className={ classnames( - 'wp-block-button__link', { - 'has-background': backgroundColor.color, - [ backgroundColor.class ]: backgroundColor.class, - 'has-text-color': textColor.color, - [ textColor.class ]: textColor.class, - } - ) } - style={ { - backgroundColor: backgroundColor.color, - color: textColor.color, - } } - keepPlaceholderOnFocus + <div className={ className } title={ title } ref={ this.bindRef }> + <RichText + placeholder={ __( 'Add text…' ) } + value={ text } + onChange={ ( value ) => setAttributes( { text: value } ) } + formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } + className={ classnames( + 'wp-block-button__link', { + 'has-background': backgroundColor.color, + [ backgroundColor.class ]: backgroundColor.class, + 'has-text-color': textColor.color, + [ textColor.class ]: textColor.class, + } + ) } + style={ { + backgroundColor: backgroundColor.color, + color: textColor.color, + } } + keepPlaceholderOnFocus + /> + <BaseControl + label={ __( 'Link' ) } + className="wp-block-button__inline-link" + id={ linkId }> + <URLInput + className="wp-block-button__inline-link-input" + value={ url } + /* eslint-disable jsx-a11y/no-autofocus */ + // Disable Reason: The rule is meant to prevent enabling auto-focus, not disabling it. + autoFocus={ false } + /* eslint-enable jsx-a11y/no-autofocus */ + onChange={ ( value ) => setAttributes( { url: value } ) } + showSuggestionsOverride={ ! isSelected ? false : undefined } + id={ linkId } + isFullWidth + hasBorder /> - <InspectorControls> - <PanelColorSettings - title={ __( 'Color Settings' ) } - colorSettings={ [ - { - value: backgroundColor.color, - onChange: setBackgroundColor, - label: __( 'Background Color' ), - }, - { - value: textColor.color, - onChange: setTextColor, - label: __( 'Text Color' ), - }, - ] } - > - <ContrastChecker - { ...{ - // Text is considered large if font size is greater or equal to 18pt or 24px, - // currently that's not the case for button. - isLargeText: false, - textColor: textColor.color, - backgroundColor: backgroundColor.color, - fallbackBackgroundColor, - fallbackTextColor, - } } - /> - </PanelColorSettings> - </InspectorControls> - </div> - { isSelected && ( - <form - className="block-library-button__inline-link" - onSubmit={ ( event ) => event.preventDefault() }> - <Dashicon icon="admin-links" /> - <URLInput - value={ url } - /* eslint-disable jsx-a11y/no-autofocus */ - // Disable Reason: The rule is meant to prevent enabling auto-focus, not disabling it. - autoFocus={ false } - /* eslint-enable jsx-a11y/no-autofocus */ - onChange={ ( value ) => setAttributes( { url: value } ) } + </BaseControl> + <InspectorControls> + <PanelColorSettings + title={ __( 'Color Settings' ) } + colorSettings={ [ + { + value: backgroundColor.color, + onChange: setBackgroundColor, + label: __( 'Background Color' ), + }, + { + value: textColor.color, + onChange: setTextColor, + label: __( 'Text Color' ), + }, + ] } + > + <ContrastChecker + { ...{ + // Text is considered large if font size is greater or equal to 18pt or 24px, + // currently that's not the case for button. + isLargeText: false, + textColor: textColor.color, + backgroundColor: backgroundColor.color, + fallbackBackgroundColor, + fallbackTextColor, + } } + /> + </PanelColorSettings> + <PanelBody title={ __( 'Link Settings' ) }> + <ToggleControl + label={ __( 'Open in New Tab' ) } + onChange={ this.onToggleOpenInNewTab } + checked={ linkTarget === '_blank' } + /> + <TextControl + label={ __( 'Link Rel' ) } + value={ rel || '' } + onChange={ this.onSetLinkRel } /> - <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> - </form> - ) } - </> + </PanelBody> + </InspectorControls> + </div> ); } } export default compose( [ + withInstanceId, withColors( 'backgroundColor', { textColor: 'color' } ), applyFallbackStyles, ] )( ButtonEdit ); diff --git a/packages/block-library/src/button/editor.scss b/packages/block-library/src/button/editor.scss index 6cfca2f62f0802..4b0cc2381fb7d3 100644 --- a/packages/block-library/src/button/editor.scss +++ b/packages/block-library/src/button/editor.scss @@ -70,52 +70,44 @@ text-overflow: ellipsis; } } -} - -.block-library-button__inline-link { - background: $white; - display: flex; - flex-wrap: wrap; - align-items: center; - font-family: $default-font; - font-size: $default-font-size; - line-height: $default-line-height; - // The width of input box plus padding plus two icon buttons. - $blocks-button__link-input-width: 300px + 2px + 2 * $icon-button-size; - width: $blocks-button__link-input-width; + // Limit width of the text field if empty + .wp-block-button__link[data-is-placeholder-visible="true"] { + max-width: 150px; + } +} - // Move it down by 2px so that it doesn't overlap the button focus outline. - margin-top: 2px; +.wp-block-button__inline-link { + color: $dark-gray-500; + height: 0; + overflow: hidden; + max-width: 290px; - .block-editor-url-input { - width: auto; + &-input__suggestions { + max-width: 290px; } - .block-editor-url-input__suggestions { - width: $blocks-button__link-input-width - $icon-button-size - $icon-button-size; - z-index: z-index(".block-library-button__inline-link .block-editor-url-input__suggestions"); - } + @media (min-width: #{ ($break-medium) }) { + max-width: 260px; - > .dashicon { - width: $icon-button-size; - } + &-input__suggestions { + max-width: 260px; + } - .dashicon { - color: $dark-gray-100; } + @media (min-width: #{ ($break-large) }) { + max-width: 290px; - .block-editor-url-input input[type="text"]::placeholder { - color: $dark-gray-100; - } + &-input__suggestions { + max-width: 290px; + } - [data-align="center"] & { - margin-left: auto; - margin-right: auto; } - [data-align="right"] & { - margin-left: auto; - margin-right: 0; + .is-selected &, + .is-typing & { + height: auto; + overflow: visible; + margin-top: $grid-size-large; } } diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index 67ce1225d26562..9b05e19b7b62fa 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -20,6 +20,8 @@ export default function save( { attributes } ) { textColor, customBackgroundColor, customTextColor, + linkTarget, + rel, } = attributes; const textClass = getColorClassName( 'color', textColor ); @@ -46,6 +48,8 @@ export default function save( { attributes } ) { title={ title } style={ buttonStyle } value={ text } + target={ linkTarget } + rel={ rel } /> </div> ); From 03121c5e59c63d4c79a293f71bbb7b9613fd311c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Wed, 10 Jul 2019 07:48:12 +0200 Subject: [PATCH 468/664] Framework: split block logic out of RichText (part 2) (#16309) * wip * Move enter, delete, paste * Clean up isEmpty * Clean up inputRule * Update package.json * Forgot bind * Fix mistakes * Simplify * href => url * Fix package.json --- package-lock.json | 4 +- .../src/components/rich-text/index.js | 429 ++++++++++++++---- .../src/components/rich-text/patterns.js | 98 ---- packages/format-library/package.json | 1 + packages/format-library/src/code/index.js | 32 +- packages/format-library/src/link/index.js | 24 + packages/rich-text/package.json | 3 - packages/rich-text/src/component/index.js | 269 +++-------- 8 files changed, 444 insertions(+), 416 deletions(-) delete mode 100644 packages/block-editor/src/components/rich-text/patterns.js diff --git a/package-lock.json b/package-lock.json index 288e97c136bd41..03cdd6b0786b90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3624,6 +3624,7 @@ "@wordpress/components": "file:packages/components", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", + "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/rich-text": "file:packages/rich-text", @@ -3788,7 +3789,6 @@ "version": "file:packages/rich-text", "requires": { "@babel/runtime": "^7.4.4", - "@wordpress/blob": "file:packages/blob", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", @@ -3796,10 +3796,8 @@ "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", "@wordpress/hooks": "file:packages/hooks", - "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", - "@wordpress/url": "file:packages/url", "classnames": "^2.2.5", "lodash": "^4.17.11", "memize": "^1.0.5", diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 8f96935d549895..24ebe88d188a9b 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -7,12 +7,26 @@ import { omit } from 'lodash'; /** * WordPress dependencies */ -import { RawHTML } from '@wordpress/element'; +import { RawHTML, Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; -import { pasteHandler, children } from '@wordpress/blocks'; +import { pasteHandler, children, getBlockTransforms, findTransform } from '@wordpress/blocks'; import { withInstanceId, compose } from '@wordpress/compose'; -import { RichText, __unstableCreateElement } from '@wordpress/rich-text'; +import { + RichText, + __unstableCreateElement, + isEmpty, + __unstableIsEmptyLine as isEmptyLine, + insert, + __unstableInsertLineSeparator as insertLineSeparator, + create, + replace, + split, + LINE_SEPARATOR, + toHTMLString, + slice, +} from '@wordpress/rich-text'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; +import { createBlobURL } from '@wordpress/blob'; /** * Internal dependencies @@ -20,7 +34,6 @@ import { withFilters, IsolatedEventContainer } from '@wordpress/components'; import Autocomplete from '../autocomplete'; import BlockFormatControls from '../block-format-controls'; import FormatToolbar from './format-toolbar'; -import { getPatterns, getEnterPatterns } from './patterns'; import { withBlockEditContext } from '../block-edit/context'; import { ListEdit } from './list-edit'; import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; @@ -28,102 +41,324 @@ import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; const wrapperClasses = 'editor-rich-text block-editor-rich-text'; const classes = 'editor-rich-text__editable block-editor-rich-text__editable'; -function RichTextWraper( { - tagName, - value: originalValue, - onChange: originalOnChange, - selectionStart, - selectionEnd, - onSelectionChange, - multiline, - onTagNameChange, - inlineToolbar, - wrapperClassName, - className, - autocompleters, - onReplace, - onRemove, - onMerge, - onSplit, - isCaretWithinFormattedText, - onEnterFormattedText, - onExitFormattedText, - canUserUseUnfilteredHTML, - isSelected: originalIsSelected, - onCreateUndoLevel, - placeholder, - keepPlaceholderOnFocus, - // From experimental filter. - ...experimentalProps -} ) { - let adjustedValue = originalValue; - let adjustedOnChange = originalOnChange; - - // Handle deprecated format. - if ( Array.isArray( originalValue ) ) { - adjustedValue = children.toHTML( originalValue ); - adjustedOnChange = ( newValue ) => originalOnChange( children.fromDOM( - __unstableCreateElement( document, newValue ).childNodes - ) ); +class RichTextWraper extends Component { + constructor() { + super( ...arguments ); + this.onEnter = this.onEnter.bind( this ); + this.onSplit = this.onSplit.bind( this ); + this.onPaste = this.onPaste.bind( this ); + this.onDelete = this.onDelete.bind( this ); + this.inputRule = this.inputRule.bind( this ); } - return ( - <RichText - { ...experimentalProps } - value={ adjustedValue } - onChange={ adjustedOnChange } - selectionStart={ selectionStart } - selectionEnd={ selectionEnd } - onSelectionChange={ onSelectionChange } - tagName={ tagName } - wrapperClassName={ classnames( wrapperClasses, wrapperClassName ) } - className={ classnames( classes, className ) } - placeholder={ placeholder } - keepPlaceholderOnFocus={ keepPlaceholderOnFocus } - __unstableIsSelected={ originalIsSelected } - __unstablePatterns={ getPatterns() } - __unstableEnterPatterns={ getEnterPatterns() } - __unstablePasteHandler={ pasteHandler } - __unstableAutocomplete={ Autocomplete } - __unstableAutocompleters={ autocompleters } - __unstableOnReplace={ onReplace } - __unstableOnRemove={ onRemove } - __unstableOnMerge={ onMerge } - __unstableOnSplit={ onSplit } - __unstableMultiline={ multiline } - __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText } - __unstableOnEnterFormattedText={ onEnterFormattedText } - __unstableOnExitFormattedText={ onExitFormattedText } - __unstableCanUserUseUnfilteredHTML={ canUserUseUnfilteredHTML } - __unstableOnCreateUndoLevel={ onCreateUndoLevel } - > - { ( { isSelected, value, onChange } ) => - <> - { isSelected && multiline === 'li' && ( - <ListEdit - onTagNameChange={ onTagNameChange } - tagName={ tagName } - value={ value } - onChange={ onChange } - /> - ) } - { isSelected && ! inlineToolbar && ( - <BlockFormatControls> - <FormatToolbar /> - </BlockFormatControls> - ) } - { isSelected && inlineToolbar && ( - <IsolatedEventContainer - className="editor-rich-text__inline-toolbar block-editor-rich-text__inline-toolbar" - > - <FormatToolbar /> - </IsolatedEventContainer> - ) } - { isSelected && <RemoveBrowserShortcuts /> } - </> + onEnter( { value, onChange, shiftKey } ) { + const { onReplace, onSplit, multiline } = this.props; + const canSplit = onReplace && onSplit; + + if ( onReplace ) { + const transforms = getBlockTransforms( 'from' ) + .filter( ( { type } ) => type === 'enter' ); + const transformation = findTransform( transforms, ( item ) => { + return item.regExp.test( value.text ); + } ); + + if ( transformation ) { + onReplace( [ + transformation.transform( { content: value.text } ), + ] ); + } + } + + if ( multiline ) { + if ( shiftKey ) { + onChange( insert( value, '\n' ) ); + } else if ( canSplit && isEmptyLine( value ) ) { + this.onSplit( value ); + } else { + onChange( insertLineSeparator( value ) ); } - </RichText> - ); + } else if ( shiftKey || ! canSplit ) { + onChange( insert( value, '\n' ) ); + } else { + this.onSplit( value ); + } + } + + onDelete( { value, isReverse } ) { + const { onMerge, onRemove } = this.props; + + if ( onMerge ) { + onMerge( ! isReverse ); + } + + // Only handle remove on Backspace. This serves dual-purpose of being + // an intentional user interaction distinguishing between Backspace and + // Delete to remove the empty field, but also to avoid merge & remove + // causing destruction of two fields (merge, then removed merged). + if ( onRemove && isEmpty( value ) && isReverse ) { + onRemove( ! isReverse ); + } + } + + onPaste( { value, onChange, html, plainText, image } ) { + const { onReplace, onSplit, tagName, canUserUseUnfilteredHTML } = this.props; + + if ( image && ! html ) { + const file = image.getAsFile ? image.getAsFile() : image; + const content = pasteHandler( { + HTML: `<img src="${ createBlobURL( file ) }">`, + mode: 'BLOCKS', + tagName, + } ); + const shouldReplace = onReplace && isEmpty( value ); + + // Allows us to ask for this information when we get a report. + window.console.log( 'Received item:\n\n', file ); + + if ( shouldReplace ) { + onReplace( content ); + } else { + this.onSplit( value, content ); + } + + return; + } + + const canReplace = onReplace && isEmpty( value ); + const canSplit = onReplace && onSplit; + + let mode = 'INLINE'; + + if ( canReplace ) { + mode = 'BLOCKS'; + } else if ( canSplit ) { + mode = 'AUTO'; + } + + const content = pasteHandler( { + HTML: html, + plainText, + mode, + tagName, + canUserUseUnfilteredHTML, + } ); + + if ( typeof content === 'string' ) { + let valueToInsert = create( { html: content } ); + + // If the content should be multiline, we should process text + // separated by a line break as separate lines. + if ( this.multilineTag ) { + valueToInsert = replace( valueToInsert, /\n+/g, LINE_SEPARATOR ); + } + + onChange( insert( value, valueToInsert ) ); + } else if ( content.length > 0 ) { + if ( canReplace ) { + onReplace( content ); + } else { + this.onSplit( value, content ); + } + } + } + + /** + * Signals to the RichText owner that the block can be replaced with two + * blocks as a result of splitting the block by pressing enter, or with + * blocks as a result of splitting the block by pasting block content in the + * instance. + * + * @param {Object} record The rich text value to split. + * @param {Array} pastedBlocks The pasted blocks to insert, if any. + */ + onSplit( record, pastedBlocks = [] ) { + const { + onReplace, + onSplit, + __unstableOnSplitMiddle: onSplitMiddle, + multiline, + } = this.props; + + if ( ! onReplace || ! onSplit ) { + return; + } + + const blocks = []; + const [ before, after ] = split( record ); + const hasPastedBlocks = pastedBlocks.length > 0; + + // Create a block with the content before the caret if there's no pasted + // blocks, or if there are pasted blocks and the value is not empty. + // We do not want a leading empty block on paste, but we do if split + // with e.g. the enter key. + if ( ! hasPastedBlocks || ! isEmpty( before ) ) { + blocks.push( onSplit( toHTMLString( { + value: before, + multilineTag: multiline, + } ) ) ); + } + + if ( hasPastedBlocks ) { + blocks.push( ...pastedBlocks ); + } else if ( onSplitMiddle ) { + blocks.push( onSplitMiddle() ); + } + + // If there's pasted blocks, append a block with the content after the + // caret. Otherwise, do append and empty block if there is no + // `onSplitMiddle` prop, but if there is and the content is empty, the + // middle block is enough to set focus in. + if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { + blocks.push( onSplit( toHTMLString( { + value: after, + multilineTag: multiline, + } ) ) ); + } + + // If there are pasted blocks, set the selection to the last one. + // Otherwise, set the selection to the second block. + const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; + + onReplace( blocks, indexToSelect ); + } + + inputRule( value, valueToFormat ) { + const { onReplace } = this.props; + + if ( ! onReplace ) { + return; + } + + const { start, text } = value; + const characterBefore = text.slice( start - 1, start ); + + if ( ! /\s/.test( characterBefore ) ) { + return; + } + + const trimmedTextBefore = text.slice( 0, start ).trim(); + const prefixTransforms = getBlockTransforms( 'from' ) + .filter( ( { type } ) => type === 'prefix' ); + const transformation = findTransform( prefixTransforms, ( { prefix } ) => { + return trimmedTextBefore === prefix; + } ); + + if ( ! transformation ) { + return; + } + + const content = valueToFormat( slice( value, start, text.length ) ); + const block = transformation.transform( content ); + + onReplace( [ block ] ); + } + + render() { + const { + tagName, + value: originalValue, + onChange: originalOnChange, + selectionStart, + selectionEnd, + onSelectionChange, + multiline, + onTagNameChange, + inlineToolbar, + wrapperClassName, + className, + autocompleters, + onReplace, + isCaretWithinFormattedText, + onEnterFormattedText, + onExitFormattedText, + isSelected: originalIsSelected, + onCreateUndoLevel, + placeholder, + keepPlaceholderOnFocus, + // eslint-disable-next-line no-unused-vars + onRemove, + // eslint-disable-next-line no-unused-vars + onMerge, + // eslint-disable-next-line no-unused-vars + onSplit, + // eslint-disable-next-line no-unused-vars + canUserUseUnfilteredHTML, + // eslint-disable-next-line no-unused-vars + clientId, + // eslint-disable-next-line no-unused-vars + identifier, + // eslint-disable-next-line no-unused-vars + instanceId, + // From experimental filter. To do: pick props instead. + ...experimentalProps + } = this.props; + + let adjustedValue = originalValue; + let adjustedOnChange = originalOnChange; + + // Handle deprecated format. + if ( Array.isArray( originalValue ) ) { + adjustedValue = children.toHTML( originalValue ); + adjustedOnChange = ( newValue ) => originalOnChange( children.fromDOM( + __unstableCreateElement( document, newValue ).childNodes + ) ); + } + + return ( + <RichText + { ...experimentalProps } + value={ adjustedValue } + onChange={ adjustedOnChange } + selectionStart={ selectionStart } + selectionEnd={ selectionEnd } + onSelectionChange={ onSelectionChange } + tagName={ tagName } + wrapperClassName={ classnames( wrapperClasses, wrapperClassName ) } + className={ classnames( classes, className ) } + placeholder={ placeholder } + keepPlaceholderOnFocus={ keepPlaceholderOnFocus } + onEnter={ this.onEnter } + onDelete={ this.onDelete } + onPaste={ this.onPaste } + __unstableIsSelected={ originalIsSelected } + __unstableInputRule={ this.inputRule } + __unstableAutocomplete={ Autocomplete } + __unstableAutocompleters={ autocompleters } + __unstableOnReplace={ onReplace } + __unstableMultiline={ multiline } + __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText } + __unstableOnEnterFormattedText={ onEnterFormattedText } + __unstableOnExitFormattedText={ onExitFormattedText } + __unstableOnCreateUndoLevel={ onCreateUndoLevel } + > + { ( { isSelected, value, onChange } ) => + <> + { isSelected && multiline === 'li' && ( + <ListEdit + onTagNameChange={ onTagNameChange } + tagName={ tagName } + value={ value } + onChange={ onChange } + /> + ) } + { isSelected && ! inlineToolbar && ( + <BlockFormatControls> + <FormatToolbar /> + </BlockFormatControls> + ) } + { isSelected && inlineToolbar && ( + <IsolatedEventContainer + className="editor-rich-text__inline-toolbar block-editor-rich-text__inline-toolbar" + > + <FormatToolbar /> + </IsolatedEventContainer> + ) } + { isSelected && <RemoveBrowserShortcuts /> } + </> + } + </RichText> + ); + } } const RichTextContainer = compose( [ diff --git a/packages/block-editor/src/components/rich-text/patterns.js b/packages/block-editor/src/components/rich-text/patterns.js deleted file mode 100644 index 4c159c5002965f..00000000000000 --- a/packages/block-editor/src/components/rich-text/patterns.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * WordPress dependencies - */ -import { getBlockTransforms, findTransform } from '@wordpress/blocks'; -import { - remove, - applyFormat, - getTextContent, - slice, -} from '@wordpress/rich-text'; - -export function getPatterns() { - const prefixTransforms = getBlockTransforms( 'from' ) - .filter( ( { type } ) => type === 'prefix' ); - - return [ - ( record, onReplace, valueToFormat ) => { - if ( ! onReplace ) { - return record; - } - - const { start } = record; - const text = getTextContent( record ); - const characterBefore = text.slice( start - 1, start ); - - if ( ! /\s/.test( characterBefore ) ) { - return record; - } - - const trimmedTextBefore = text.slice( 0, start ).trim(); - const transformation = findTransform( prefixTransforms, ( { prefix } ) => { - return trimmedTextBefore === prefix; - } ); - - if ( ! transformation ) { - return record; - } - - const content = valueToFormat( slice( record, start, text.length ) ); - const block = transformation.transform( content ); - - onReplace( [ block ] ); - - return record; - }, - ( record ) => { - const BACKTICK = '`'; - const { start } = record; - const text = getTextContent( record ); - const characterBefore = text.slice( start - 1, start ); - - // Quick check the text for the necessary character. - if ( characterBefore !== BACKTICK ) { - return record; - } - - const textBefore = text.slice( 0, start - 1 ); - const indexBefore = textBefore.lastIndexOf( BACKTICK ); - - if ( indexBefore === -1 ) { - return record; - } - - const startIndex = indexBefore; - const endIndex = start - 2; - - if ( startIndex === endIndex ) { - return record; - } - - record = remove( record, startIndex, startIndex + 1 ); - record = remove( record, endIndex, endIndex + 1 ); - record = applyFormat( record, { type: 'code' }, startIndex, endIndex ); - - return record; - }, - ]; -} - -export function getEnterPatterns() { - const transforms = getBlockTransforms( 'from' ) - .filter( ( { type } ) => type === 'enter' ); - - return ( record, onReplace ) => { - const text = getTextContent( record ); - const transformation = findTransform( transforms, ( item ) => { - return item.regExp.test( text ); - } ); - - if ( ! transformation ) { - return record; - } - - onReplace( [ - transformation.transform( { content: text } ), - ] ); - }; -} diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 8b84cae37cf00f..e84f04d29956e6 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -26,6 +26,7 @@ "@wordpress/components": "file:../components", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", + "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", "@wordpress/rich-text": "file:../rich-text", diff --git a/packages/format-library/src/code/index.js b/packages/format-library/src/code/index.js index 4eb3cfb1118ef8..4d7f7522008396 100644 --- a/packages/format-library/src/code/index.js +++ b/packages/format-library/src/code/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { toggleFormat } from '@wordpress/rich-text'; +import { toggleFormat, remove, applyFormat } from '@wordpress/rich-text'; import { RichTextToolbarButton } from '@wordpress/block-editor'; const name = 'core/code'; @@ -13,6 +13,36 @@ export const code = { title, tagName: 'code', className: null, + __unstableInputRule( value ) { + const BACKTICK = '`'; + const { start, text } = value; + const characterBefore = text.slice( start - 1, start ); + + // Quick check the text for the necessary character. + if ( characterBefore !== BACKTICK ) { + return value; + } + + const textBefore = text.slice( 0, start - 1 ); + const indexBefore = textBefore.lastIndexOf( BACKTICK ); + + if ( indexBefore === -1 ) { + return value; + } + + const startIndex = indexBefore; + const endIndex = start - 2; + + if ( startIndex === endIndex ) { + return value; + } + + value = remove( value, startIndex, startIndex + 1 ); + value = remove( value, endIndex, endIndex + 1 ); + value = applyFormat( value, { type: name }, startIndex, endIndex ); + + return value; + }, edit( { value, onChange, isActive } ) { const onToggle = () => onChange( toggleFormat( value, { type: name } ) ); diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index f68d4932f62420..e1390fbdf5d537 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -9,9 +9,11 @@ import { applyFormat, removeFormat, slice, + isCollapsed, } from '@wordpress/rich-text'; import { isURL, isEmail } from '@wordpress/url'; import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -30,6 +32,28 @@ export const link = { url: 'href', target: 'target', }, + __unstablePasteRule( value, { html, plainText } ) { + if ( isCollapsed( value ) ) { + return value; + } + + const pastedText = ( html || plainText ).replace( /<[^>]+>/g, '' ).trim(); + + // A URL was pasted, turn the selection into a link + if ( ! isURL( pastedText ) ) { + return value; + } + + // Allows us to ask for this information when we get a report. + window.console.log( 'Created link:\n\n', pastedText ); + + return applyFormat( value, { + type: name, + attributes: { + url: decodeEntities( pastedText ), + }, + } ); + }, edit: withSpokenMessages( class LinkEdit extends Component { constructor() { super( ...arguments ); diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 602d8f15911128..d8672425a058a0 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -22,7 +22,6 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", - "@wordpress/blob": "file:../blob", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", @@ -30,10 +29,8 @@ "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", "@wordpress/hooks": "file:../hooks", - "@wordpress/html-entities": "file:../html-entities", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", - "@wordpress/url": "file:../url", "classnames": "^2.2.5", "lodash": "^4.17.11", "memize": "^1.0.5", diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 56e3cb3305bc32..c2f76c4a22c296 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -13,12 +13,9 @@ import { */ import { Component } from '@wordpress/element'; import { isHorizontalEdge } from '@wordpress/dom'; -import { createBlobURL } from '@wordpress/blob'; import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; import { withSelect } from '@wordpress/data'; import { withSafeTimeout, compose } from '@wordpress/compose'; -import { isURL } from '@wordpress/url'; -import { decodeEntities } from '@wordpress/html-entities'; import isShallowEqual from '@wordpress/is-shallow-equal'; /** @@ -27,14 +24,10 @@ import isShallowEqual from '@wordpress/is-shallow-equal'; import FormatEdit from './format-edit'; import Editable from './editable'; import { pickAriaProps } from './aria'; -import { isEmpty, isEmptyLine } from '../is-empty'; +import { isEmpty } from '../is-empty'; import { create } from '../create'; import { apply, toDom } from '../to-dom'; -import { applyFormat } from '../apply-format'; -import { split } from '../split'; import { toHTMLString } from '../to-html-string'; -import { insert } from '../insert'; -import { insertLineSeparator } from '../insert-line-separator'; import { remove } from '../remove'; import { removeFormat } from '../remove-format'; import { isCollapsed } from '../is-collapsed'; @@ -42,7 +35,6 @@ import { LINE_SEPARATOR } from '../special-characters'; import { indentListItems } from '../indent-list-items'; import { getActiveFormats } from '../get-active-formats'; import { updateFormats } from '../update-formats'; -import { replace } from '../replace'; import { removeLineSeparator } from '../remove-line-separator'; /** @@ -120,14 +112,12 @@ class RichText extends Component { this.getRecord = this.getRecord.bind( this ); this.createRecord = this.createRecord.bind( this ); this.applyRecord = this.applyRecord.bind( this ); - this.isEmpty = this.isEmpty.bind( this ); this.valueToFormat = this.valueToFormat.bind( this ); this.setRef = this.setRef.bind( this ); this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); this.onPointerDown = this.onPointerDown.bind( this ); this.formatToValue = this.formatToValue.bind( this ); - this.onSplit = this.onSplit.bind( this ); this.state = {}; @@ -194,10 +184,6 @@ class RichText extends Component { } ); } - isEmpty() { - return isEmpty( this.record ); - } - /** * Handles a paste event. * @@ -206,13 +192,7 @@ class RichText extends Component { * @param {PasteEvent} event The paste event. */ onPaste( event ) { - const { - tagName, - __unstableCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, - __unstablePasteHandler: pasteHandler, - __unstableOnReplace: onReplace, - __unstableOnSplit: onSplit, - } = this.props; + const { formatTypes, onPaste } = this.props; const clipboardData = event.clipboardData; let { items, files } = clipboardData; @@ -247,87 +227,35 @@ class RichText extends Component { window.console.log( 'Received HTML:\n\n', html ); window.console.log( 'Received plain text:\n\n', plainText ); - // Only process file if no HTML is present. - // Note: a pasted file may have the URL as plain text. - const item = find( [ ...items, ...files ], ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); const record = this.getRecord(); - - if ( item && ! html ) { - const file = item.getAsFile ? item.getAsFile() : item; - const content = pasteHandler( { - HTML: `<img src="${ createBlobURL( file ) }">`, - mode: 'BLOCKS', - tagName, - } ); - const shouldReplace = onReplace && this.isEmpty(); - - // Allows us to ask for this information when we get a report. - window.console.log( 'Received item:\n\n', file ); - - if ( shouldReplace ) { - onReplace( content ); - } else if ( this.onSplit ) { - this.onSplit( record, content ); + const transformed = formatTypes.reduce( ( accumlator, { __unstablePasteRule } ) => { + // Only allow one transform. + if ( __unstablePasteRule && accumlator === record ) { + accumlator = __unstablePasteRule( record, { html, plainText } ); } - return; - } - - // There is a selection, check if a URL is pasted. - if ( ! isCollapsed( record ) ) { - const pastedText = ( html || plainText ).replace( /<[^>]+>/g, '' ).trim(); - - // A URL was pasted, turn the selection into a link - if ( isURL( pastedText ) ) { - this.onChange( applyFormat( record, { - type: 'a', - attributes: { - href: decodeEntities( pastedText ), - }, - } ) ); - - // Allows us to ask for this information when we get a report. - window.console.log( 'Created link:\n\n', pastedText ); - - return; - } - } + return accumlator; + }, record ); - const canReplace = onReplace && this.isEmpty(); - const canSplit = onReplace && onSplit; - - let mode = 'INLINE'; - - if ( canReplace ) { - mode = 'BLOCKS'; - } else if ( canSplit ) { - mode = 'AUTO'; + if ( transformed !== record ) { + this.onChange( transformed ); + return; } - const content = pasteHandler( { - HTML: html, - plainText, - mode, - tagName, - canUserUseUnfilteredHTML, - } ); - - if ( typeof content === 'string' ) { - let valueToInsert = create( { html: content } ); - - // If the content should be multiline, we should process text - // separated by a line break as separate lines. - if ( this.multilineTag ) { - valueToInsert = replace( valueToInsert, /\n+/g, LINE_SEPARATOR ); - } + if ( onPaste ) { + // Only process file if no HTML is present. + // Note: a pasted file may have the URL as plain text. + const image = find( [ ...items, ...files ], ( { type } ) => + /^image\/(?:jpe?g|png|gif)$/.test( type ) + ); - this.onChange( insert( record, valueToInsert ) ); - } else if ( content.length > 0 ) { - if ( canReplace ) { - onReplace( content ); - } else { - this.onSplit( record, content ); - } + onPaste( { + value: this.removeEditorOnlyFormats( record ), + onChange: this.onChange, + html, + plainText, + image, + } ); } } @@ -422,25 +350,23 @@ class RichText extends Component { this.onChange( change, { withoutHistory: true } ); - const { - __unstablePatterns: patterns, - __unstableOnReplace: onReplace, - } = this.props; + const { __unstableInputRule: inputRule, formatTypes } = this.props; - if ( patterns ) { - const transformed = patterns.reduce( - ( accumlator, transform ) => transform( - accumlator, - onReplace, - this.valueToFormat - ), - change - ); + if ( inputRule ) { + inputRule( change, this.valueToFormat ); + } - if ( transformed !== change ) { - this.onCreateUndoLevel(); - this.onChange( { ...transformed, activeFormats } ); + const transformed = formatTypes.reduce( ( accumlator, { __unstableInputRule } ) => { + if ( __unstableInputRule ) { + accumlator = __unstableInputRule( accumlator ); } + + return accumlator; + }, change ); + + if ( transformed !== change ) { + this.onCreateUndoLevel(); + this.onChange( { ...transformed, activeFormats } ); } // Create an undo level when input stops for over a second. @@ -571,44 +497,36 @@ class RichText extends Component { * @param {KeyboardEvent} event Keydown event. */ onDeleteKeyDown( event ) { - const { __unstableOnMerge: onMerge, __unstableOnRemove: onRemove } = this.props; - if ( ! onMerge && ! onRemove ) { + const { onDelete } = this.props; + + if ( ! onDelete ) { return; } const { keyCode } = event; const isReverse = keyCode === BACKSPACE; + const record = this.createRecord(); // Only process delete if the key press occurs at uncollapsed edge. - if ( ! isCollapsed( this.createRecord() ) ) { + if ( ! isCollapsed( record ) ) { return; } - const empty = this.isEmpty(); - // It is important to consider emptiness because an empty container // will include a padding BR node _after_ the caret, so in a forward // deletion the isHorizontalEdge function will incorrectly interpret the // presence of the BR node as not being at the edge. - const isEdge = ( empty || isHorizontalEdge( this.editableRef, isReverse ) ); + const isEdge = ( isEmpty( record ) || isHorizontalEdge( this.editableRef, isReverse ) ); if ( ! isEdge ) { return; } - if ( onMerge ) { - onMerge( ! isReverse ); - } + event.preventDefault(); - // Only handle remove on Backspace. This serves dual-purpose of being - // an intentional user interaction distinguishing between Backspace and - // Delete to remove the empty field, but also to avoid merge & remove - // causing destruction of two fields (merge, then removed merged). - if ( onRemove && empty && isReverse ) { - onRemove( ! isReverse ); + if ( onDelete ) { + onDelete( { isReverse, value: record } ); } - - event.preventDefault(); } /** @@ -618,13 +536,7 @@ class RichText extends Component { */ onKeyDown( event ) { const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; - const { - __unstableOnReplace: onReplace, - __unstableOnSplit: onSplit, - __unstableEnterPatterns: enterPatterns, - } = this.props; - - const canSplit = onReplace && onSplit; + const { onEnter } = this.props; if ( // Only override left and right keys without modifiers pressed. @@ -674,30 +586,12 @@ class RichText extends Component { } else if ( keyCode === ENTER ) { event.preventDefault(); - const record = this.createRecord(); - - if ( enterPatterns ) { - if ( enterPatterns( - record, - onReplace, - this.valueToFormat, - ) !== record ) { - return; - } - } - - if ( this.multilineTag ) { - if ( event.shiftKey ) { - this.onChange( insert( record, '\n' ) ); - } else if ( canSplit && isEmptyLine( record ) ) { - this.onSplit( record ); - } else { - this.onChange( insertLineSeparator( record ) ); - } - } else if ( event.shiftKey || ! canSplit ) { - this.onChange( insert( record, '\n' ) ); - } else { - this.onSplit( record ); + if ( onEnter ) { + onEnter( { + value: this.removeEditorOnlyFormats( this.createRecord() ), + onChange: this.onChange, + shiftKey: event.shiftKey, + } ); } } } @@ -799,59 +693,6 @@ class RichText extends Component { this.setState( { activeFormats: newActiveFormats } ); } - /** - * Signals to the RichText owner that the block can be replaced with two - * blocks as a result of splitting the block by pressing enter, or with - * blocks as a result of splitting the block by pasting block content in the - * instance. - * - * @param {Object} record The rich text value to split. - * @param {Array} pastedBlocks The pasted blocks to insert, if any. - */ - onSplit( record, pastedBlocks = [] ) { - const { - __unstableOnReplace: onReplace, - __unstableOnSplit: onSplit, - __unstableOnSplitMiddle: onSplitMiddle, - } = this.props; - - if ( ! onReplace || ! onSplit ) { - return; - } - - const blocks = []; - const [ before, after ] = split( record ); - const hasPastedBlocks = pastedBlocks.length > 0; - - // Create a block with the content before the caret if there's no pasted - // blocks, or if there are pasted blocks and the value is not empty. - // We do not want a leading empty block on paste, but we do if split - // with e.g. the enter key. - if ( ! hasPastedBlocks || ! isEmpty( before ) ) { - blocks.push( onSplit( this.valueToFormat( before ) ) ); - } - - if ( hasPastedBlocks ) { - blocks.push( ...pastedBlocks ); - } else if ( onSplitMiddle ) { - blocks.push( onSplitMiddle() ); - } - - // If there's pasted blocks, append a block with the content after the - // caret. Otherwise, do append and empty block if there is no - // `onSplitMiddle` prop, but if there is and the content is empty, the - // middle block is enough to set focus in. - if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { - blocks.push( onSplit( this.valueToFormat( after ) ) ); - } - - // If there are pasted blocks, set the selection to the last one. - // Otherwise, set the selection to the second block. - const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; - - onReplace( blocks, indexToSelect ); - } - /** * Select object when they are clicked. The browser will not set any * selection when clicking e.g. an image. @@ -1031,8 +872,8 @@ class RichText extends Component { const key = Tagname; const MultilineTag = this.multilineTag; const ariaProps = pickAriaProps( this.props ); - const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && this.isEmpty(); const record = this.getRecord(); + const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && isEmpty( record ); const autoCompleteContent = ( { listBoxId, activeId } ) => ( <> From 9f1657de89fd2fd55b6079f85b0b9ceb515393de Mon Sep 17 00:00:00 2001 From: Vadim Nicolai <nicolai.vadim@gmail.com> Date: Wed, 10 Jul 2019 09:20:53 +0300 Subject: [PATCH 469/664] Applied max height to html block. (#16187) --- packages/block-library/src/html/editor.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/html/editor.scss b/packages/block-library/src/html/editor.scss index 2990803cb8eaae..2638318a4b7391 100644 --- a/packages/block-library/src/html/editor.scss +++ b/packages/block-library/src/html/editor.scss @@ -7,6 +7,7 @@ padding: 0.8em 1em; border: 1px solid $light-gray-500; border-radius: 4px; + max-height: 250px; /* Fonts smaller than 16px causes mobile safari to zoom. */ font-size: $mobile-text-min-font-size; From 1731607c8db21e70e63261ff7f6fb025eb953a22 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Wed, 10 Jul 2019 22:50:44 +1000 Subject: [PATCH 470/664] Show Preview button on mobile viewports (#16496) Displays the Preview button and renames 'Switch to Draft' to 'Draft' on mobile viewports. This allows mobile users to easily preview their published posts. --- packages/edit-post/src/components/header/style.scss | 8 -------- .../test/__snapshots__/index.js.snap | 2 +- .../components/post-switch-to-draft-button/index.js | 12 ++++++++++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index 05ea111d6ab82c..64699eeab73a2d 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -36,14 +36,6 @@ } } - .editor-post-switch-to-draft + .editor-post-preview { - display: none; - - @include break-small { - display: inline-flex; - } - } - // Some browsers, most notably IE11, honor an older version of the flexbox spec // which takes absolutely positioned items into account when calculating `space-between`. // https://www.w3.org/TR/2012/WD-css3-flexbox-20120612/#abspos-flex-items diff --git a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap index c881259580d2c5..e96dde9ca76942 100644 --- a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PostSavedState returns a switch to draft link if the post is published 1`] = `<WithSelect(WithDispatch(PostSwitchToDraftButton)) />`; +exports[`PostSavedState returns a switch to draft link if the post is published 1`] = `<WithSelect(WithDispatch(WithViewportMatch(PostSwitchToDraftButton))) />`; exports[`PostSavedState should return Save button if edits to be saved 1`] = ` <ForwardRef(Button) diff --git a/packages/editor/src/components/post-switch-to-draft-button/index.js b/packages/editor/src/components/post-switch-to-draft-button/index.js index 155f5711cf5e87..07270c4ed33a26 100644 --- a/packages/editor/src/components/post-switch-to-draft-button/index.js +++ b/packages/editor/src/components/post-switch-to-draft-button/index.js @@ -5,8 +5,15 @@ import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { withViewportMatch } from '@wordpress/viewport'; -function PostSwitchToDraftButton( { isSaving, isPublished, isScheduled, onClick } ) { +function PostSwitchToDraftButton( { + isSaving, + isPublished, + isScheduled, + onClick, + isMobileViewport, +} ) { if ( ! isPublished && ! isScheduled ) { return null; } @@ -31,7 +38,7 @@ function PostSwitchToDraftButton( { isSaving, isPublished, isScheduled, onClick disabled={ isSaving } isTertiary > - { __( 'Switch to Draft' ) } + { isMobileViewport ? __( 'Draft' ) : __( 'Switch to Draft' ) } </Button> ); } @@ -54,5 +61,6 @@ export default compose( [ }, }; } ), + withViewportMatch( { isMobileViewport: '< small' } ), ] )( PostSwitchToDraftButton ); From 5756b299a78458f6034f8f03535e59dc8174a9e4 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 10 Jul 2019 16:36:34 +0100 Subject: [PATCH 471/664] Remove usage of select editor from the block editor module (#16184) --- packages/block-editor/README.md | 35 +++++++++--------- .../src/components/rich-text/index.js | 7 ++-- packages/block-editor/src/store/defaults.js | 36 ++++++++++--------- .../editor/src/components/provider/index.js | 20 +++++++++-- 4 files changed, 58 insertions(+), 40 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index f279d2994600d3..63718286e9bb87 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -353,23 +353,24 @@ Undocumented declaration. The default editor settings - alignWide boolean Enable/Disable Wide/Full Alignments - availableLegacyWidgets Array Array of objects representing the legacy widgets available. - colors Array Palette colors - disableCustomColors boolean Whether or not the custom colors are disabled - fontSizes Array Available font sizes - disableCustomFontSizes boolean Whether or not the custom font sizes are disabled - imageSizes Array Available image sizes - maxWidth number Max width to constraint resizing - allowedBlockTypes boolean|Array Allowed block types - hasFixedToolbar boolean Whether or not the editor toolbar is fixed - hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. - focusMode boolean Whether the focus mode is enabled or not - styles Array Editor Styles - isRTL boolean Whether the editor is in RTL mode - bodyPlaceholder string Empty post placeholder - titlePlaceholder string Empty title placeholder - codeEditingEnabled string Whether or not the user can switch to the code editor + alignWide boolean Enable/Disable Wide/Full Alignments + availableLegacyWidgets Array Array of objects representing the legacy widgets available. + colors Array Palette colors + disableCustomColors boolean Whether or not the custom colors are disabled + fontSizes Array Available font sizes + disableCustomFontSizes boolean Whether or not the custom font sizes are disabled + imageSizes Array Available image sizes + maxWidth number Max width to constraint resizing + allowedBlockTypes boolean|Array Allowed block types + hasFixedToolbar boolean Whether or not the editor toolbar is fixed + hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. + focusMode boolean Whether the focus mode is enabled or not + styles Array Editor Styles + isRTL boolean Whether the editor is in RTL mode + bodyPlaceholder string Empty post placeholder + titlePlaceholder string Empty title placeholder + codeEditingEnabled string Whether or not the user can switch to the code editor + \_\_experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. <a name="SkipToSelectedBlock" href="#SkipToSelectedBlock">#</a> **SkipToSelectedBlock** diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 24ebe88d188a9b..48c2cf817f74ac 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -370,17 +370,16 @@ const RichTextContainer = compose( [ identifier = instanceId, isSelected, } ) => { - // This should probably be moved to the block editor settings. - const { canUserUseUnfilteredHTML } = select( 'core/editor' ); const { isCaretWithinFormattedText, getSelectionStart, getSelectionEnd, + getSettings, } = select( 'core/block-editor' ); const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); - + const { __experimentalCanUserUseUnfilteredHTML } = getSettings(); if ( isSelected === undefined ) { isSelected = ( selectionStart.clientId === clientId && @@ -389,7 +388,7 @@ const RichTextContainer = compose( [ } return { - canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(), + canUserUseUnfilteredHTML: __experimentalCanUserUseUnfilteredHTML, isCaretWithinFormattedText: isCaretWithinFormattedText(), selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 49cf289b58e0d6..dc950c9a426deb 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -10,23 +10,24 @@ export const PREFERENCES_DEFAULTS = { /** * The default editor settings * - * alignWide boolean Enable/Disable Wide/Full Alignments - * availableLegacyWidgets Array Array of objects representing the legacy widgets available. - * colors Array Palette colors - * disableCustomColors boolean Whether or not the custom colors are disabled - * fontSizes Array Available font sizes - * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled - * imageSizes Array Available image sizes - * maxWidth number Max width to constraint resizing - * allowedBlockTypes boolean|Array Allowed block types - * hasFixedToolbar boolean Whether or not the editor toolbar is fixed - * hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. - * focusMode boolean Whether the focus mode is enabled or not - * styles Array Editor Styles - * isRTL boolean Whether the editor is in RTL mode - * bodyPlaceholder string Empty post placeholder - * titlePlaceholder string Empty title placeholder - * codeEditingEnabled string Whether or not the user can switch to the code editor + * alignWide boolean Enable/Disable Wide/Full Alignments + * availableLegacyWidgets Array Array of objects representing the legacy widgets available. + * colors Array Palette colors + * disableCustomColors boolean Whether or not the custom colors are disabled + * fontSizes Array Available font sizes + * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled + * imageSizes Array Available image sizes + * maxWidth number Max width to constraint resizing + * allowedBlockTypes boolean|Array Allowed block types + * hasFixedToolbar boolean Whether or not the editor toolbar is fixed + * hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. + * focusMode boolean Whether the focus mode is enabled or not + * styles Array Editor Styles + * isRTL boolean Whether the editor is in RTL mode + * bodyPlaceholder string Empty post placeholder + * titlePlaceholder string Empty title placeholder + * codeEditingEnabled string Whether or not the user can switch to the code editor + * __experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. */ export const SETTINGS_DEFAULTS = { alignWide: false, @@ -137,5 +138,6 @@ export const SETTINGS_DEFAULTS = { availableLegacyWidgets: {}, hasPermissionsToManageWidgets: false, + __experimentalCanUserUseUnfilteredHTML: false, }; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index cc9533a0a79a7b..d241aedac6fd5b 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -72,7 +72,14 @@ class EditorProvider extends Component { } } - getBlockEditorSettings( settings, meta, onMetaChange, reusableBlocks, hasUploadPermissions ) { + getBlockEditorSettings( + settings, + meta, + onMetaChange, + reusableBlocks, + hasUploadPermissions, + canUserUseUnfilteredHTML + ) { return { ...pick( settings, [ 'alignWide', @@ -102,6 +109,7 @@ class EditorProvider extends Component { __experimentalReusableBlocks: reusableBlocks, __experimentalMediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalFetchLinkSuggestions: fetchLinkSuggestions, + __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, }; } @@ -131,6 +139,7 @@ class EditorProvider extends Component { render() { const { + canUserUseUnfilteredHTML, children, blocks, resetEditorBlocks, @@ -148,7 +157,12 @@ class EditorProvider extends Component { } const editorSettings = this.getBlockEditorSettings( - settings, meta, onMetaChange, reusableBlocks, hasUploadPermissions + settings, + meta, + onMetaChange, + reusableBlocks, + hasUploadPermissions, + canUserUseUnfilteredHTML ); return ( @@ -171,6 +185,7 @@ export default compose( [ withRegistryProvider, withSelect( ( select ) => { const { + canUserUseUnfilteredHTML, __unstableIsEditorReady: isEditorReady, getEditorBlocks, getEditedPostAttribute, @@ -179,6 +194,7 @@ export default compose( [ const { canUser } = select( 'core' ); return { + canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(), isReady: isEditorReady(), blocks: getEditorBlocks(), meta: getEditedPostAttribute( 'meta' ), From 8995efde26c7eea6790f0d593dc7bfb5137d0084 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 10 Jul 2019 13:50:14 -0400 Subject: [PATCH 472/664] Project Management: Milestone It: Read version from master branch package.json (#16511) * Project Management: Milestone It: Read version from master branch package.json * Project Management: Milestone It: Exit neutral when skipping milestoned * Project Management: Milestone It: Apply milestone only on master merges --- .github/actions/milestone-it/entrypoint.sh | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/actions/milestone-it/entrypoint.sh b/.github/actions/milestone-it/entrypoint.sh index 835875dc61a1b4..2f06c892df3a01 100755 --- a/.github/actions/milestone-it/entrypoint.sh +++ b/.github/actions/milestone-it/entrypoint.sh @@ -1,7 +1,16 @@ #!/bin/bash set -e -# 1. Determine if milestone already exists (don't replace one which has already +# 1. Proceed only when merge occurs to `master` base branch. + +base=$(jq -r '.pull_request.base.ref' $GITHUB_EVENT_PATH) + +if [ "$base" != 'master' ]; then + echo 'Milestones apply only to master merge. Aborting.' + exit 78; +fi + +# 2. Determine if milestone already exists (don't replace one which has already # been assigned). pr=$(jq -r '.number' $GITHUB_EVENT_PATH) @@ -16,18 +25,18 @@ current_milestone=$( if [ "$current_milestone" != 'null' ]; then echo 'Milestone already applied. Aborting.' - exit 1; + exit 78; fi -# 2. Read current version. +# 3. Read current version. -version=$(jq -r '.version' package.json) +version=$(git show master:package.json | jq -r '.version') IFS='.' read -ra parts <<< "$version" major=${parts[0]} minor=${parts[1]} -# 3. Determine next milestone. +# 4. Determine next milestone. if [[ $minor == 9* ]]; then major=$((major+1)) @@ -38,7 +47,7 @@ fi milestone="Gutenberg $major.$minor" -# 4. Calculate next milestone due date, using a static reference of an earlier +# 5. Calculate next milestone due date, using a static reference of an earlier # release (v5.0) as a reference point for the biweekly release schedule. reference_major=5 @@ -48,7 +57,7 @@ num_versions_elapsed=$(((major-reference_major)*10+(minor-reference_minor))) weeks=$((num_versions_elapsed*2)) due=$(date -u --iso-8601=seconds -d "$(date -d @$(echo $reference_date)) + $(echo $weeks) weeks") -# 5. Create milestone. This may fail for duplicates, which is expected and +# 6. Create milestone. This may fail for duplicates, which is expected and # ignored. curl \ @@ -59,7 +68,7 @@ curl \ -d "{\"title\":\"$milestone\",\"due_on\":\"$due\",\"description\":\"Tasks to be included in the $milestone plugin release.\"}" \ "https://api.github.com/repos/$GITHUB_REPOSITORY/milestones" > /dev/null -# 6. Find milestone number. This could be improved to allow for non-open status +# 7. Find milestone number. This could be improved to allow for non-open status # or paginated results. number=$( @@ -70,7 +79,7 @@ number=$( | jq ".[] | select(.title == \"$milestone\") | .number" ) -# 7. Assign pull request to milestone. +# 8. Assign pull request to milestone. curl \ --silent \ From 1b5fc6a593581bfe1ee75128f0d05c154c4b8fdf Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 10 Jul 2019 14:49:38 -0400 Subject: [PATCH 473/664] Editor: Implement meta as custom source (#16402) * Editor: Implement meta as custom source Co-Authored-By: Riad Benguella <benguella@gmail.com> * Block Editor: Skip outgoing sync next value only if matches by reference * Editor: Implement editor tearDown to un-ready state * Editor: Add subscription action generator for managing sources dependencies * Editor: Track source dependencies by registry * Editor: Prepare initial blocks reset for applied post-sourced values * Editor: Yield editor setup action after state preparation Ensure initial edits set to expected values, since preceding resetPost would unset them otherwise if already assigned * Editor: Update block sources as part of reset step * Editor: Retrieve registry at most once in applying sourced attributes * Editor: Account for source values apply / update in inner blocks * Editor: Update block sources documentation per interface changes * Block Editor: Add lastBlockAttributesChange unit tests * Block Editor: Document, test getLastBlockAttributesChange selector * Editor: Fix block sources getDependencies grammar Co-Authored-By: Enrique Piqueras <epiqueras@users.noreply.github.com> * Editor: Fix block sources meta getDependencies JSDoc typo Co-Authored-By: Enrique Piqueras <epiqueras@users.noreply.github.com> * Editor: Align block sources getDependencies JSDoc tags Co-Authored-By: Enrique Piqueras <epiqueras@users.noreply.github.com> * Editor: Resolve awaitNextStateChange JSDoc typo Co-Authored-By: Enrique Piqueras <epiqueras@users.noreply.github.com> * Editor: Resolve getRegistry JSDoc typo Co-Authored-By: Enrique Piqueras <epiqueras@users.noreply.github.com> * Block Editor: Always unset expected outbound sync value in provider update * Editor: Detect block attributes change as direct result of last action Infer change as non-null state from lastBlockAttributesChange * Documentation: Regenerate editor, block-editor data documentation * Block Editor: Unset outbound sync value in editor provider reset condition * Editor: Update getDependencies JSDoc to separate yield grouping * Editor: Dispatch SETUP_EDITOR prior to blocks reset SETUP_EDITOR is responsible for setting the initial edits, which are expected to be unset by a subsequent block reset * Editor: Update actions tests per reordered SETUP_EDITOR * Block API: Stub default attributes, keywords values for block type registration * Block API: Update documentation per formation of WPBlockTypeIconDescriptor typedef * Editor: Iterate last block changes in updating sources * Editor: Update source even if already updated for block updates A block may have multiple attributes which independently source uniquely from the same source type (e.g. two different meta properties) * Editor: Iterate updated attributes from block reset source update Optimization since the attributes are likely a subset (most likely a single attribute), so we can avoid iterating all attributes defined in a block type. * Editor: Mark custom sources selectors, actions as experimental * Editor: Remove redundant Object#hasOwnProperty in iterating attributes * Block Editor: Rename getLastBlockAttributesChange to getLastBlockAttributeChanges * Editor: Use getRegistry control for next change subscription --- .../src/components/block-list/block.js | 40 +-- .../src/components/provider/README.md | 40 +++ .../src/components/provider/index.js | 23 +- packages/block-editor/src/store/reducer.js | 78 +++--- packages/block-editor/src/store/selectors.js | 71 ++---- .../block-editor/src/store/test/reducer.js | 67 +++++ .../block-editor/src/store/test/selectors.js | 71 ++---- packages/blocks/CHANGELOG.md | 4 + packages/blocks/README.md | 4 +- packages/blocks/src/api/registration.js | 80 ++++-- packages/blocks/src/api/test/registration.js | 46 ++++ packages/blocks/src/api/utils.js | 12 +- .../editor/src/components/provider/index.js | 22 +- packages/editor/src/store/actions.js | 230 +++++++++++++++++- .../editor/src/store/block-sources/README.md | 22 ++ .../store/block-sources/__mocks__/index.js | 1 + .../editor/src/store/block-sources/index.js | 6 + .../editor/src/store/block-sources/meta.js | 55 +++++ packages/editor/src/store/controls.js | 38 +++ packages/editor/src/store/index.js | 8 +- packages/editor/src/store/reducer.js | 3 + packages/editor/src/store/test/actions.js | 14 +- 22 files changed, 688 insertions(+), 247 deletions(-) create mode 100644 packages/block-editor/src/components/provider/README.md create mode 100644 packages/editor/src/store/block-sources/README.md create mode 100644 packages/editor/src/store/block-sources/__mocks__/index.js create mode 100644 packages/editor/src/store/block-sources/index.js create mode 100644 packages/editor/src/store/block-sources/meta.js create mode 100644 packages/editor/src/store/controls.js diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 3556b829ba188d..320f63555946c8 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, reduce, size, first, last } from 'lodash'; +import { first, last } from 'lodash'; import { animated } from 'react-spring/web.cjs'; /** @@ -658,42 +658,8 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { return { setAttributes( newAttributes ) { - const { name, clientId } = ownProps; - const type = getBlockType( name ); - - function isMetaAttribute( key ) { - return get( type, [ 'attributes', key, 'source' ] ) === 'meta'; - } - - // Partition new attributes to delegate update behavior by source. - // - // TODO: A consolidated approach to external attributes sourcing - // should be devised to avoid specific handling for meta, enable - // additional attributes sources. - // - // See: https://github.com/WordPress/gutenberg/issues/2759 - const { - blockAttributes, - metaAttributes, - } = reduce( newAttributes, ( result, value, key ) => { - if ( isMetaAttribute( key ) ) { - result.metaAttributes[ type.attributes[ key ].meta ] = value; - } else { - result.blockAttributes[ key ] = value; - } - - return result; - }, { blockAttributes: {}, metaAttributes: {} } ); - - if ( size( blockAttributes ) ) { - updateBlockAttributes( clientId, blockAttributes ); - } - - if ( size( metaAttributes ) ) { - const { getSettings } = select( 'core/block-editor' ); - const onChangeMeta = getSettings().__experimentalMetaSource.onChange; - onChangeMeta( metaAttributes ); - } + const { clientId } = ownProps; + updateBlockAttributes( clientId, newAttributes ); }, onSelect( clientId = ownProps.clientId, initialPosition ) { selectBlock( clientId, initialPosition ); diff --git a/packages/block-editor/src/components/provider/README.md b/packages/block-editor/src/components/provider/README.md new file mode 100644 index 00000000000000..647c5854d0e21a --- /dev/null +++ b/packages/block-editor/src/components/provider/README.md @@ -0,0 +1,40 @@ +BlockEditorProvider +=================== + +BlockEditorProvider is a component which establishes a new block editing context, and serves as the entry point for a new block editor. It is implemented as a [controlled input](https://reactjs.org/docs/forms.html#controlled-components), expected to receive a value of a blocks array, calling `onChange` and/or `onInput` when the user interacts to change blocks in the editor. It is intended to be used as a wrapper component, where its children comprise the user interface through which a user modifies the blocks value, notably via other components made available from this `block-editor` module. + +## Props + +### `value` + +* **Type:** `Array` +* **Required** `no` + +The current array of blocks. + +### `onChange` + +* **Type:** `Function` +* **Required** `no` + +A callback invoked when the blocks have been modified in a persistent manner. Contrasted with `onInput`, a "persistent" change is one which is not an extension of a composed input. Any update to a distinct block or block attribute is treated as persistent. + +The distinction between these two callbacks is akin to the [differences between `input` and `change` events](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event) in the DOM API: + +>The input event is fired every time the value of the element changes. **This is unlike the change event, which only fires when the value is committed**, such as by pressing the enter key, selecting a value from a list of options, and the like. + +In the context of an editor, an example usage of this distinction is for managing a history of blocks values (an "Undo"/"Redo" mechanism). While value updates should always be reflected immediately (`onInput`), you may only want history entries to reflect change milestones (`onChange`). + +### `onInput` + +* **Type:** `Function` +* **Required** `no` + +A callback invoked when the blocks have been modified in a non-persistent manner. Contrasted with `onChange`, a "non-persistent" change is one which is part of a composed input. Any sequence of updates to the same block attribute are treated as non-persistent, except for the first. + +### `children` + +* **Type:** `WPElement` +* **Required** `no` + +Children elements for which the BlockEditorProvider context should apply. diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index 6d06246ca9170a..33eff22a08892b 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -34,10 +34,21 @@ class BlockEditorProvider extends Component { this.attachChangeObserver( registry ); } - if ( this.isSyncingOutcomingValue ) { - this.isSyncingOutcomingValue = false; + if ( this.isSyncingOutcomingValue !== null && this.isSyncingOutcomingValue === value ) { + // Skip block reset if the value matches expected outbound sync + // triggered by this component by a preceding change detection. + // Only skip if the value matches expectation, since a reset should + // still occur if the value is modified (not equal by reference), + // to allow that the consumer may apply modifications to reflect + // back on the editor. + this.isSyncingOutcomingValue = null; } else if ( value !== prevProps.value ) { - this.isSyncingIncomingValue = true; + // Reset changing value in all other cases than the sync described + // above. Since this can be reached in an update following an out- + // bound sync, unset the outbound value to avoid considering it in + // subsequent renders. + this.isSyncingOutcomingValue = null; + this.isSyncingIncomingValue = value; resetBlocks( value ); } } @@ -79,15 +90,17 @@ class BlockEditorProvider extends Component { onChange, onInput, } = this.props; + const newBlocks = getBlocks(); const newIsPersistent = isLastBlockChangePersistent(); + if ( newBlocks !== blocks && ( this.isSyncingIncomingValue || __unstableIsLastBlockChangeIgnored() ) ) { - this.isSyncingIncomingValue = false; + this.isSyncingIncomingValue = null; blocks = newBlocks; isPersistent = newIsPersistent; return; @@ -101,7 +114,7 @@ class BlockEditorProvider extends Component { // When knowing the blocks value is changing, assign instance // value to skip reset in subsequent `componentDidUpdate`. if ( newBlocks !== blocks ) { - this.isSyncingOutcomingValue = true; + this.isSyncingOutcomingValue = newBlocks; } blocks = newBlocks; diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index b4cd52a08886e3..8df16d7bda9eb8 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -195,29 +195,6 @@ export function isUpdatingSameBlockAttribute( action, lastAction ) { ); } -/** - * Higher-order reducer intended to reset the cache key of all blocks - * whenever the post meta values change. - * - * @param {Function} reducer Original reducer function. - * - * @return {Function} Enhanced reducer function. - */ -const withPostMetaUpdateCacheReset = ( reducer ) => ( state, action ) => { - const newState = reducer( state, action ); - const previousMetaValues = get( state, [ 'settings', '__experimentalMetaSource', 'value' ] ); - const nextMetaValues = get( newState.settings.__experimentalMetaSource, [ 'value' ] ); - // If post meta values change, reset the cache key for all blocks - if ( previousMetaValues !== nextMetaValues ) { - newState.blocks = { - ...newState.blocks, - cache: mapValues( newState.blocks.cache, () => ( {} ) ), - }; - } - - return newState; -}; - /** * Utility returning an object with an empty object value for each key. * @@ -1228,17 +1205,44 @@ export const blockListSettings = ( state = {}, action ) => { return state; }; -export default withPostMetaUpdateCacheReset( - combineReducers( { - blocks, - isTyping, - isCaretWithinFormattedText, - blockSelection, - blocksMode, - blockListSettings, - insertionPoint, - template, - settings, - preferences, - } ) -); +/** + * Reducer return an updated state representing the most recent block attribute + * update. The state is structured as an object where the keys represent the + * client IDs of blocks, the values a subset of attributes from the most recent + * block update. The state is always reset to null if the last action is + * anything other than an attributes update. + * + * @param {Object<string,Object>} state Current state. + * @param {Object} action Action object. + * + * @return {[string,Object]} Updated state. + */ +export function lastBlockAttributesChange( state, action ) { + switch ( action.type ) { + case 'UPDATE_BLOCK': + if ( ! action.updates.attributes ) { + break; + } + + return { [ action.clientId ]: action.updates.attributes }; + + case 'UPDATE_BLOCK_ATTRIBUTES': + return { [ action.clientId ]: action.attributes }; + } + + return null; +} + +export default combineReducers( { + blocks, + isTyping, + isCaretWithinFormattedText, + blockSelection, + blocksMode, + blockListSettings, + insertionPoint, + template, + settings, + preferences, + lastBlockAttributesChange, +} ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 85a7442cd8613d..5e8771ff94935b 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -64,14 +64,6 @@ const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; */ const EMPTY_ARRAY = []; -/** - * Shared reference to an empty object for cases where it is important to avoid - * returning a new object reference on every invocation. - * - * @type {Object} - */ -const EMPTY_OBJECT = {}; - /** * Returns a block's name given its client ID, or null if no block exists with * the client ID. @@ -108,42 +100,14 @@ export function isBlockValid( state, clientId ) { * * @return {Object?} Block attributes. */ -export const getBlockAttributes = createSelector( - ( state, clientId ) => { - const block = state.blocks.byClientId[ clientId ]; - if ( ! block ) { - return null; - } - - let attributes = state.blocks.attributes[ clientId ]; - - // Inject custom source attribute values. - // - // TODO: Create generic external sourcing pattern, not explicitly - // targeting meta attributes. - const type = getBlockType( block.name ); - if ( type ) { - attributes = reduce( type.attributes, ( result, value, key ) => { - if ( value.source === 'meta' ) { - if ( result === attributes ) { - result = { ...result }; - } - - result[ key ] = getPostMeta( state, value.meta ); - } - - return result; - }, attributes ); - } +export function getBlockAttributes( state, clientId ) { + const block = state.blocks.byClientId[ clientId ]; + if ( ! block ) { + return null; + } - return attributes; - }, - ( state, clientId ) => [ - state.blocks.byClientId[ clientId ], - state.blocks.attributes[ clientId ], - getPostMeta( state ), - ] -); + return state.blocks.attributes[ clientId ]; +} /** * Returns a block given its client ID. This is a parsed copy of the block, @@ -193,7 +157,7 @@ export const __unstableGetBlockWithoutInnerBlocks = createSelector( }, ( state, clientId ) => [ state.blocks.byClientId[ clientId ], - ...getBlockAttributes.getDependants( state, clientId ), + state.blocks.attributes[ clientId ], ] ); @@ -296,7 +260,6 @@ export const getBlocksByClientId = createSelector( ( clientId ) => getBlock( state, clientId ) ), ( state ) => [ - getPostMeta( state ), state.blocks.byClientId, state.blocks.order, state.blocks.attributes, @@ -656,7 +619,6 @@ export const getMultiSelectedBlocks = createSelector( state.blocks.byClientId, state.blocks.order, state.blocks.attributes, - getPostMeta( state ), ] ); @@ -1421,19 +1383,16 @@ export function __unstableIsLastBlockChangeIgnored( state ) { } /** - * Returns the value of a post meta from the editor settings. + * Returns the block attributes changed as a result of the last dispatched + * action. * - * @param {Object} state Global application state. - * @param {string} key Meta Key to retrieve + * @param {Object} state Block editor state. * - * @return {*} Meta value + * @return {Object<string,Object>} Subsets of block attributes changed, keyed + * by block client ID. */ -function getPostMeta( state, key ) { - if ( key === undefined ) { - return get( state, [ 'settings', '__experimentalMetaSource', 'value' ], EMPTY_OBJECT ); - } - - return get( state, [ 'settings', '__experimentalMetaSource', 'value', key ] ); +export function __experimentalGetLastBlockAttributeChanges( state ) { + return state.lastBlockAttributesChange; } /** diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 99f0ecb16282a4..0626f57fb41291 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -28,6 +28,7 @@ import { insertionPoint, template, blockListSettings, + lastBlockAttributesChange, } from '../reducer'; describe( 'state', () => { @@ -2390,4 +2391,70 @@ describe( 'state', () => { expect( state ).toEqual( {} ); } ); } ); + + describe( 'lastBlockAttributesChange', () => { + it( 'defaults to null', () => { + const state = lastBlockAttributesChange( undefined, {} ); + + expect( state ).toBe( null ); + } ); + + it( 'does not return updated value when block update does not include attributes', () => { + const original = null; + + const state = lastBlockAttributesChange( original, { + type: 'UPDATE_BLOCK', + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + updates: {}, + } ); + + expect( state ).toBe( original ); + } ); + + it( 'returns updated value when block update includes attributes', () => { + const original = null; + + const state = lastBlockAttributesChange( original, { + type: 'UPDATE_BLOCK', + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + updates: { + attributes: { + food: 'banana', + }, + }, + } ); + + expect( state ).toEqual( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { food: 'banana' }, + } ); + } ); + + it( 'returns updated value when explicit block attributes update', () => { + const original = null; + + const state = lastBlockAttributesChange( original, { + type: 'UPDATE_BLOCK_ATTRIBUTES', + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + attributes: { + food: 'banana', + }, + } ); + + expect( state ).toEqual( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { food: 'banana' }, + } ); + } ); + + it( 'returns null on anything other than block attributes update', () => { + const original = deepFreeze( { + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { food: 'banana' }, + } ); + + const state = lastBlockAttributesChange( original, { + type: '__INERT__', + } ); + + expect( state ).toBe( null ); + } ); + } ); } ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 7bc0390317ece5..4e1289e44aefc3 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -58,6 +58,7 @@ const { getTemplate, getTemplateLock, getBlockListSettings, + __experimentalGetLastBlockAttributeChanges, INSERTER_UTILITY_HIGH, INSERTER_UTILITY_MEDIUM, INSERTER_UTILITY_LOW, @@ -263,60 +264,6 @@ describe( 'selectors', () => { } ], } ); } ); - - it( 'should merge meta attributes for the block', () => { - registerBlockType( 'core/meta-block', { - save: ( props ) => props.attributes.text, - category: 'common', - title: 'test block', - attributes: { - foo: { - type: 'string', - source: 'meta', - meta: 'foo', - }, - }, - } ); - - const state = { - settings: { - __experimentalMetaSource: { - value: { - foo: 'bar', - }, - }, - }, - blocks: { - byClientId: { - 123: { clientId: 123, name: 'core/meta-block' }, - }, - attributes: { - 123: {}, - }, - order: { - '': [ 123 ], - 123: [], - }, - parents: { - 123: '', - }, - cache: { - 123: {}, - }, - }, - }; - - expect( getBlock( state, 123 ) ).toEqual( { - clientId: 123, - name: 'core/meta-block', - attributes: { - foo: 'bar', - }, - innerBlocks: [], - } ); - - unregisterBlockType( 'core/meta-block' ); - } ); } ); describe( 'getBlocks', () => { @@ -2492,4 +2439,20 @@ describe( 'selectors', () => { expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined ); } ); } ); + + describe( '__experimentalGetLastBlockAttributeChanges', () => { + it( 'returns the last block attributes change', () => { + const state = { + lastBlockAttributesChange: { + block1: { fruit: 'bananas' }, + }, + }; + + const result = __experimentalGetLastBlockAttributeChanges( state ); + + expect( result ).toEqual( { + block1: { fruit: 'bananas' }, + } ); + } ); + } ); } ); diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index d38762e51694a2..fdbc957dd802e7 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,5 +1,9 @@ ## Master +### Improvements + +- Omitting `attributes` or `keywords` settings will now stub default values (an empty object or empty array, respectively). + ### Bug Fixes - The `'blocks.registerBlockType'` filter is now applied to each of a block's deprecated settings as well as the block's main settings. Ensures `supports` settings like `anchor` work for deprecations. diff --git a/packages/blocks/README.md b/packages/blocks/README.md index e456eb546ffbb8..331be71c3a15f4 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -548,11 +548,11 @@ in the codebase. _Parameters_ -- _icon_ `(Object|string|WPElement)`: Slug of the Dashicon to be shown as the icon for the block in the inserter, or element or an object describing the icon. +- _icon_ `WPBlockTypeIconRender`: Render behavior of a block type icon; one of a Dashicon slug, an element, or a component. _Returns_ -- `Object`: Object describing the icon. +- `WPBlockTypeIconDescriptor`: Object describing the icon. <a name="parse" href="#parse">#</a> **parse** diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 83dcc057c94e65..498f83627e6fff 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -21,30 +21,72 @@ import { select, dispatch } from '@wordpress/data'; */ import { isValidIcon, normalizeIconObject } from './utils'; +/** + * Render behavior of a block type icon; one of a Dashicon slug, an element, + * or a component. + * + * @typedef {(string|WPElement|WPComponent)} WPBlockTypeIconRender + * + * @see https://developer.wordpress.org/resource/dashicons/ + */ + +/** + * An object describing a normalized block type icon. + * + * @typedef {WPBlockTypeIconDescriptor} + * + * @property {WPBlockTypeIconRender} src Render behavior of the icon, + * one of a Dashicon slug, an + * element, or a component. + * @property {string} background Optimal background hex string + * color when displaying icon. + * @property {string} foreground Optimal foreground hex string + * color when displaying icon. + * @property {string} shadowColor Optimal shadow hex string + * color when displaying icon. + */ + +/** + * Value to use to render the icon for a block type in an editor interface, + * either a Dashicon slug, an element, a component, or an object describing + * the icon. + * + * @typedef {(WPBlockTypeIconDescriptor|WPBlockTypeIconRender)} WPBlockTypeIcon + */ + /** * Defined behavior of a block type. * * @typedef {WPBlockType} * - * @property {string} name Block's namespaced name. - * @property {string} title Human-readable label for a block. - * Shown in the block inserter. - * @property {string} category Category classification of block, - * impacting where block is shown in - * inserter results. - * @property {(Object|string|WPElement)} icon Slug of the Dashicon to be shown - * as the icon for the block in the - * inserter, or element or an object describing the icon. - * @property {?string[]} keywords Additional keywords to produce - * block as inserter search result. - * @property {?Object} attributes Block attributes. - * @property {?Function} save Serialize behavior of a block, - * returning an element describing - * structure of the block's post - * content markup. - * @property {WPComponent} edit Component rendering element to be - * interacted with in an editor. + * @property {string} name Block type's namespaced name. + * @property {string} title Human-readable block type label. + * @property {string} category Block type category classification, + * used in search interfaces to arrange + * block types by category. + * @property {?WPBlockTypeIcon} icon Block type icon. + * @property {?string[]} keywords Additional keywords to produce block + * type as result in search interfaces. + * @property {?Object} attributes Block type attributes. + * @property {?WPComponent} save Optional component describing + * serialized markup structure of a + * block type. + * @property {WPComponent} edit Component rendering an element to + * manipulate the attributes of a block + * in the context of an editor. + */ + +/** + * Default values to assign for omitted optional block type settings. + * + * @type {Object} */ +const DEFAULT_BLOCK_TYPE_SETTINGS = { + icon: 'block-default', + attributes: {}, + keywords: [], + save: () => null, +}; let serverSideBlockDefinitions = {}; @@ -74,7 +116,7 @@ export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { / export function registerBlockType( name, settings ) { settings = { name, - save: () => null, + ...DEFAULT_BLOCK_TYPE_SETTINGS, ...get( serverSideBlockDefinitions, name ), ...settings, }; diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index cd8c9692c21a56..30df36c9359d4c 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -95,6 +95,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], save: noop, category: 'common', title: 'block title', @@ -111,6 +113,8 @@ describe( 'blocks', () => { it( 'should reject blocks with invalid save function', () => { const block = registerBlockType( 'my-plugin/fancy-block-5', { ...defaultBlockSettings, + attributes: {}, + keywords: [], save: 'invalid', } ); expect( console ).toHaveErroredWith( 'The "save" property must be a valid function.' ); @@ -159,6 +163,25 @@ describe( 'blocks', () => { expect( block ).toBeUndefined(); } ); + it( 'should assign default settings', () => { + registerBlockType( 'core/test-block-with-defaults', { + title: 'block title', + category: 'common', + } ); + + expect( getBlockType( 'core/test-block-with-defaults' ) ).toEqual( { + name: 'core/test-block-with-defaults', + title: 'block title', + category: 'common', + icon: { + src: 'block-default', + }, + attributes: {}, + keywords: [], + save: expect.any( Function ), + } ); + } ); + it( 'should default to browser-initialized global attributes', () => { const attributes = { ok: { type: 'boolean' } }; unstable__bootstrapServerSideBlockDefinitions( { @@ -181,6 +204,7 @@ describe( 'blocks', () => { type: 'boolean', }, }, + keywords: [], } ); } ); @@ -218,6 +242,8 @@ describe( 'blocks', () => { fill="red" stroke="blue" strokeWidth="10" /> </svg> ), }, + attributes: {}, + keywords: [], } ); } ); @@ -237,6 +263,8 @@ describe( 'blocks', () => { icon: { src: 'foo', }, + attributes: {}, + keywords: [], } ); } ); @@ -262,6 +290,8 @@ describe( 'blocks', () => { icon: { src: MyTestIcon, }, + attributes: {}, + keywords: [], } ); } ); @@ -293,6 +323,8 @@ describe( 'blocks', () => { fill="red" stroke="blue" strokeWidth="10" /> </svg> ), }, + attributes: {}, + keywords: [], } ); } ); @@ -309,6 +341,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], } ); } ); @@ -394,6 +428,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], }, ] ); const oldBlock = unregisterBlockType( 'core/test-block' ); @@ -406,6 +442,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], } ); expect( getBlockTypes() ).toEqual( [] ); } ); @@ -478,6 +516,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], } ); } ); @@ -493,6 +533,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], } ); } ); } ); @@ -515,6 +557,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], }, { name: 'core/test-block-with-settings', @@ -525,6 +569,8 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, + keywords: [], }, ] ); } ); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 6dcabbcbcea012..99dc98b5c3204b 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -77,17 +77,13 @@ export function isValidIcon( icon ) { * and returns a new icon object that is normalized so we can rely on just on possible icon structure * in the codebase. * - * @param {(Object|string|WPElement)} icon Slug of the Dashicon to be shown - * as the icon for the block in the - * inserter, or element or an object describing the icon. + * @param {WPBlockTypeIconRender} icon Render behavior of a block type icon; + * one of a Dashicon slug, an element, or a + * component. * - * @return {Object} Object describing the icon. + * @return {WPBlockTypeIconDescriptor} Object describing the icon. */ export function normalizeIconObject( icon ) { - if ( ! icon ) { - icon = 'block-default'; - } - if ( isValidIcon( icon ) ) { return { src: icon }; } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index d241aedac6fd5b..cfa67ddbb615d7 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -74,8 +74,6 @@ class EditorProvider extends Component { getBlockEditorSettings( settings, - meta, - onMetaChange, reusableBlocks, hasUploadPermissions, canUserUseUnfilteredHTML @@ -102,10 +100,6 @@ class EditorProvider extends Component { 'templateLock', 'titlePlaceholder', ] ), - __experimentalMetaSource: { - value: meta, - onChange: onMetaChange, - }, __experimentalReusableBlocks: reusableBlocks, __experimentalMediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalFetchLinkSuggestions: fetchLinkSuggestions, @@ -137,6 +131,10 @@ class EditorProvider extends Component { } } + componentWillUnmount() { + this.props.tearDownEditor(); + } + render() { const { canUserUseUnfilteredHTML, @@ -145,8 +143,6 @@ class EditorProvider extends Component { resetEditorBlocks, isReady, settings, - meta, - onMetaChange, reusableBlocks, resetEditorBlocksWithoutUndoLevel, hasUploadPermissions, @@ -158,8 +154,6 @@ class EditorProvider extends Component { const editorSettings = this.getBlockEditorSettings( settings, - meta, - onMetaChange, reusableBlocks, hasUploadPermissions, canUserUseUnfilteredHTML @@ -188,7 +182,6 @@ export default compose( [ canUserUseUnfilteredHTML, __unstableIsEditorReady: isEditorReady, getEditorBlocks, - getEditedPostAttribute, __experimentalGetReusableBlocks, } = select( 'core/editor' ); const { canUser } = select( 'core' ); @@ -197,7 +190,6 @@ export default compose( [ canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(), isReady: isEditorReady(), blocks: getEditorBlocks(), - meta: getEditedPostAttribute( 'meta' ), reusableBlocks: __experimentalGetReusableBlocks(), hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), }; @@ -207,8 +199,8 @@ export default compose( [ setupEditor, updatePostLock, resetEditorBlocks, - editPost, updateEditorSettings, + __experimentalTearDownEditor, } = dispatch( 'core/editor' ); const { createWarningNotice } = dispatch( 'core/notices' ); @@ -223,9 +215,7 @@ export default compose( [ __unstableShouldCreateUndoLevel: false, } ); }, - onMetaChange( meta ) { - editPost( { meta } ); - }, + tearDownEditor: __experimentalTearDownEditor, }; } ), ] )( EditorProvider ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 073887663bd1b9..8ea2c9a408de84 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -13,6 +13,7 @@ import { parse, synchronizeBlocksWithTemplate, } from '@wordpress/blocks'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -32,6 +33,120 @@ import { getNotificationArgumentsForSaveFail, getNotificationArgumentsForTrashFail, } from './utils/notice-builder'; +import { awaitNextStateChange, getRegistry } from './controls'; +import * as sources from './block-sources'; + +/** + * Map of Registry instance to WeakMap of dependencies by custom source. + * + * @type WeakMap<WPDataRegistry,WeakMap<WPBlockAttributeSource,Object>> + */ +const lastBlockSourceDependenciesByRegistry = new WeakMap; + +/** + * Given a blocks array, returns a blocks array with sourced attribute values + * applied. The reference will remain consistent with the original argument if + * no attribute values must be overridden. If sourced values are applied, the + * return value will be a modified copy of the original array. + * + * @param {WPBlock[]} blocks Original blocks array. + * + * @return {WPBlock[]} Blocks array with sourced values applied. + */ +function* getBlocksWithSourcedAttributes( blocks ) { + const registry = yield getRegistry(); + + let workingBlocks = blocks; + for ( let i = 0; i < blocks.length; i++ ) { + let block = blocks[ i ]; + const blockType = yield select( 'core/blocks', 'getBlockType', block.name ); + + for ( const [ attributeName, schema ] of Object.entries( blockType.attributes ) ) { + if ( ! sources[ schema.source ] || ! sources[ schema.source ].apply ) { + continue; + } + + if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { + continue; + } + + const blockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); + if ( ! blockSourceDependencies.has( sources[ schema.source ] ) ) { + continue; + } + + const dependencies = blockSourceDependencies.get( sources[ schema.source ] ); + const sourcedAttributeValue = sources[ schema.source ].apply( schema, dependencies ); + + // It's only necessary to apply the value if it differs from the + // block's locally-assigned value, to avoid needlessly resetting + // the block editor. + if ( sourcedAttributeValue === block.attributes[ attributeName ] ) { + continue; + } + + // Create a shallow clone to mutate, leaving the original intact. + if ( workingBlocks === blocks ) { + workingBlocks = [ ...workingBlocks ]; + } + + block = { + ...block, + attributes: { + ...block.attributes, + [ attributeName ]: sourcedAttributeValue, + }, + }; + + workingBlocks.splice( i, 1, block ); + } + + // Recurse to apply source attributes to inner blocks. + if ( block.innerBlocks.length ) { + const appliedInnerBlocks = yield* getBlocksWithSourcedAttributes( block.innerBlocks ); + if ( appliedInnerBlocks !== block.innerBlocks ) { + if ( workingBlocks === blocks ) { + workingBlocks = [ ...workingBlocks ]; + } + + block = { + ...block, + innerBlocks: appliedInnerBlocks, + }; + + workingBlocks.splice( i, 1, block ); + } + } + } + + return workingBlocks; +} + +/** + * Refreshes the last block source dependencies, optionally for a given subset + * of sources (defaults to the full set of sources). + * + * @param {?Array} sourcesToUpdate Optional subset of sources to reset. + * + * @yield {Object} Yielded actions or control descriptors. + */ +function* resetLastBlockSourceDependencies( sourcesToUpdate = Object.values( sources ) ) { + if ( ! sourcesToUpdate.length ) { + return; + } + + const registry = yield getRegistry(); + + for ( const source of sourcesToUpdate ) { + if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { + lastBlockSourceDependenciesByRegistry.set( registry, new WeakMap ); + } + + const lastBlockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); + const dependencies = yield* source.getDependencies(); + lastBlockSourceDependencies.set( source, dependencies ); + } +} /** * Returns an action generator used in signalling that editor has initialized with @@ -42,13 +157,6 @@ import { * @param {Array?} template Block Template. */ export function* setupEditor( post, edits, template ) { - yield { - type: 'SETUP_EDITOR', - post, - edits, - template, - }; - // In order to ensure maximum of a single parse during setup, edits are // included as part of editor setup action. Assume edited content as // canonical if provided, falling back to post. @@ -67,8 +175,76 @@ export function* setupEditor( post, edits, template ) { blocks = synchronizeBlocksWithTemplate( blocks, template ); } + yield resetPost( post ); + yield* resetLastBlockSourceDependencies(); + yield { + type: 'SETUP_EDITOR', + post, + edits, + template, + }; yield resetEditorBlocks( blocks ); yield setupEditorState( post ); + yield* __experimentalSubscribeSources(); +} + +/** + * Returns an action object signalling that the editor is being destroyed and + * that any necessary state or side-effect cleanup should occur. + * + * @return {Object} Action object. + */ +export function __experimentalTearDownEditor() { + return { type: 'TEAR_DOWN_EDITOR' }; +} + +/** + * Returns an action generator which loops to await the next state change, + * calling to reset blocks when a block source dependencies change. + * + * @yield {Object} Action object. + */ +export function* __experimentalSubscribeSources() { + while ( true ) { + yield awaitNextStateChange(); + + // The bailout case: If the editor becomes unmounted, it will flag + // itself as non-ready. Effectively unsubscribes from the registry. + const isStillReady = yield select( 'core/editor', '__unstableIsEditorReady' ); + if ( ! isStillReady ) { + break; + } + + const registry = yield getRegistry(); + + let reset = false; + for ( const source of Object.values( sources ) ) { + if ( ! source.getDependencies ) { + continue; + } + + const dependencies = yield* source.getDependencies(); + + if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { + lastBlockSourceDependenciesByRegistry.set( registry, new WeakMap ); + } + + const lastBlockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); + const lastDependencies = lastBlockSourceDependencies.get( source ); + + if ( ! isShallowEqual( dependencies, lastDependencies ) ) { + lastBlockSourceDependencies.set( source, dependencies ); + + // Allow the loop to continue in order to assign latest + // dependencies values, but mark for reset. + reset = true; + } + } + + if ( reset ) { + yield resetEditorBlocks( yield select( 'core/editor', 'getEditorBlocks' ) ); + } + } } /** @@ -730,10 +906,46 @@ export function unlockPostSaving( lockName ) { * * @return {Object} Action object */ -export function resetEditorBlocks( blocks, options = {} ) { +export function* resetEditorBlocks( blocks, options = {} ) { + const lastBlockAttributesChange = yield select( 'core/block-editor', '__experimentalGetLastBlockAttributeChanges' ); + + // Sync to sources from block attributes updates. + if ( lastBlockAttributesChange ) { + const updatedSources = new Set; + const updatedBlockTypes = new Set; + for ( const [ clientId, attributes ] of Object.entries( lastBlockAttributesChange ) ) { + const blockName = yield select( 'core/block-editor', 'getBlockName', clientId ); + if ( updatedBlockTypes.has( blockName ) ) { + continue; + } + + updatedBlockTypes.add( blockName ); + const blockType = yield select( 'core/blocks', 'getBlockType', blockName ); + + for ( const [ attributeName, newAttributeValue ] of Object.entries( attributes ) ) { + if ( ! blockType.attributes.hasOwnProperty( attributeName ) ) { + continue; + } + + const schema = blockType.attributes[ attributeName ]; + const source = sources[ schema.source ]; + + if ( source && source.update ) { + yield* source.update( schema, newAttributeValue ); + updatedSources.add( source ); + } + } + } + + // Dependencies are reset so that source dependencies subscription + // skips a reset which would otherwise occur by dependencies change. + // This assures that at most one reset occurs per block change. + yield* resetLastBlockSourceDependencies( Array.from( updatedSources ) ); + } + return { type: 'RESET_EDITOR_BLOCKS', - blocks, + blocks: yield* getBlocksWithSourcedAttributes( blocks ), shouldCreateUndoLevel: options.__unstableShouldCreateUndoLevel !== false, }; } diff --git a/packages/editor/src/store/block-sources/README.md b/packages/editor/src/store/block-sources/README.md new file mode 100644 index 00000000000000..0c16d12b3159d2 --- /dev/null +++ b/packages/editor/src/store/block-sources/README.md @@ -0,0 +1,22 @@ +Block Sources +============= + +By default, the blocks module supports only attributes serialized into a block's comment demarcations, or those sourced from a [standard set of sources](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/). Since the blocks module is intended to be used in a number of contexts outside the post editor, the implementation of additional context-specific sources must be implemented as an external process. + +The post editor supports such additional sources for attributes (e.g. `meta` source). + +These sources are implemented here using a uniform interface for applying and responding to block updates to sourced attributes. In the future, this interface may be generalized to allow third-party extensions to either extend the post editor sources or implement their own in custom renderings of a block editor. + +## Source API + +### `getDependencies` + +Store control called on every store change, expected to return an object whose values represent the data blocks assigned this source depend on. When these values change, all blocks assigned this source are automatically updated. The value returned from this function is passed as the second argument of the source's `apply` function, where it is expected to be used as shared data relevant for sourcing the attribute value. + +### `apply` + +Function called to retrieve an attribute value for a block. Given the attribute schema and the dependencies defined by the source's `getDependencies`, the function should return the expected attribute value. + +### `update` + +Store control called when a single block's attributes have been updated, before the new block value has taken effect (i.e. before `apply` and `applyAll` are once again called). Given the attribute schema and updated value, the control should reflect the update on the source. diff --git a/packages/editor/src/store/block-sources/__mocks__/index.js b/packages/editor/src/store/block-sources/__mocks__/index.js new file mode 100644 index 00000000000000..cb0ff5c3b541f6 --- /dev/null +++ b/packages/editor/src/store/block-sources/__mocks__/index.js @@ -0,0 +1 @@ +export {}; diff --git a/packages/editor/src/store/block-sources/index.js b/packages/editor/src/store/block-sources/index.js new file mode 100644 index 00000000000000..542d774c313ce9 --- /dev/null +++ b/packages/editor/src/store/block-sources/index.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import * as meta from './meta'; + +export { meta }; diff --git a/packages/editor/src/store/block-sources/meta.js b/packages/editor/src/store/block-sources/meta.js new file mode 100644 index 00000000000000..3910395c4a740d --- /dev/null +++ b/packages/editor/src/store/block-sources/meta.js @@ -0,0 +1,55 @@ +/** + * WordPress dependencies + */ +import { select } from '@wordpress/data-controls'; + +/** + * Internal dependencies + */ +import { editPost } from '../actions'; + +/** + * Store control invoked upon a state change, responsible for returning an + * object of dependencies. When a change in dependencies occurs (by shallow + * equality of the returned object), blocks are reset to apply the new sourced + * value. + * + * @yield {Object} Optional yielded controls. + * + * @return {Object} Dependencies as object. + */ +export function* getDependencies() { + return { + meta: yield select( 'core/editor', 'getEditedPostAttribute', 'meta' ), + }; +} + +/** + * Given an attribute schema and dependencies data, returns a source value. + * + * @param {Object} schema Block type attribute schema. + * @param {Object} dependencies Source dependencies. + * @param {Object} dependencies.meta Post meta. + * + * @return {Object} Block attribute value. + */ +export function apply( schema, { meta } ) { + return meta[ schema.meta ]; +} + +/** + * Store control invoked upon a block attributes update, responsible for + * reflecting an update in a meta value. + * + * @param {Object} schema Block type attribute schema. + * @param {*} value Updated block attribute value. + * + * @yield {Object} Yielded action objects or store controls. + */ +export function* update( schema, value ) { + yield editPost( { + meta: { + [ schema.meta ]: value, + }, + } ); +} diff --git a/packages/editor/src/store/controls.js b/packages/editor/src/store/controls.js new file mode 100644 index 00000000000000..3b0288d00b0a5b --- /dev/null +++ b/packages/editor/src/store/controls.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import { createRegistryControl } from '@wordpress/data'; + +/** + * Returns a control descriptor signalling to subscribe to the registry and + * resolve the control promise only when the next state change occurs. + * + * @return {Object} Control descriptor. + */ +export function awaitNextStateChange() { + return { type: 'AWAIT_NEXT_STATE_CHANGE' }; +} + +/** + * Returns a control descriptor signalling to resolve with the current data + * registry. + * + * @return {Object} Control descriptor. + */ +export function getRegistry() { + return { type: 'GET_REGISTRY' }; +} + +const controls = { + AWAIT_NEXT_STATE_CHANGE: createRegistryControl( + ( registry ) => () => new Promise( ( resolve ) => { + const unsubscribe = registry.subscribe( () => { + unsubscribe(); + resolve(); + } ); + } ) + ), + GET_REGISTRY: createRegistryControl( ( registry ) => () => registry ), +}; + +export default controls; diff --git a/packages/editor/src/store/index.js b/packages/editor/src/store/index.js index 33c5686396097e..8f377151e08d43 100644 --- a/packages/editor/src/store/index.js +++ b/packages/editor/src/store/index.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { registerStore } from '@wordpress/data'; -import { controls } from '@wordpress/data-controls'; +import { controls as dataControls } from '@wordpress/data-controls'; /** * Internal dependencies @@ -11,6 +11,7 @@ import reducer from './reducer'; import applyMiddlewares from './middlewares'; import * as selectors from './selectors'; import * as actions from './actions'; +import controls from './controls'; import { STORE_KEY } from './constants'; /** @@ -24,7 +25,10 @@ export const storeConfig = { reducer, selectors, actions, - controls, + controls: { + ...dataControls, + ...controls, + }, }; const store = registerStore( STORE_KEY, { diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index a6f2bd4a4565ac..6676b74dcbc395 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -558,6 +558,9 @@ export function isReady( state = false, action ) { switch ( action.type ) { case 'SETUP_EDITOR_STATE': return true; + + case 'TEAR_DOWN_EDITOR': + return false; } return state; diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index dd78100a4385db..ff10dcf0a9044b 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -20,6 +20,7 @@ import { } from '../constants'; jest.mock( '@wordpress/data-controls' ); +jest.mock( '../block-sources' ); select.mockImplementation( ( ...args ) => { const { select: actualSelect } = jest @@ -640,15 +641,24 @@ describe( 'Post generator actions', () => { describe( 'Editor actions', () => { describe( 'setupEditor()', () => { + const post = { content: { raw: '' }, status: 'publish' }; + let fulfillment; - const reset = ( post, edits, template ) => fulfillment = actions + const reset = ( edits, template ) => fulfillment = actions .setupEditor( post, edits, template, ); + beforeAll( () => { + reset(); + } ); + + it( 'should yield action object for resetPost', () => { + const { value } = fulfillment.next(); + expect( value ).toEqual( actions.resetPost( post ) ); + } ); it( 'should yield the SETUP_EDITOR action', () => { - reset( { content: { raw: '' }, status: 'publish' } ); const { value } = fulfillment.next(); expect( value ).toEqual( { type: 'SETUP_EDITOR', From f1d6db6a9ebe3440928c1a16c8afd27de4a845e3 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 10 Jul 2019 15:02:14 -0400 Subject: [PATCH 474/664] Project Management: Milestone It: Read package.json via GitHub raw URL Theory is that the working copy in the Docker container contains only the single branch from which the action is triggered, and hasn't fetched `master` to be able to read `package.json` --- .github/actions/milestone-it/entrypoint.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/actions/milestone-it/entrypoint.sh b/.github/actions/milestone-it/entrypoint.sh index 2f06c892df3a01..2dd48a92e0d8a8 100755 --- a/.github/actions/milestone-it/entrypoint.sh +++ b/.github/actions/milestone-it/entrypoint.sh @@ -30,7 +30,12 @@ fi # 3. Read current version. -version=$(git show master:package.json | jq -r '.version') +version=$( + curl \ + --silent \ + "https://raw.githubusercontent.com/$GITHUB_REPOSITORY/master/package.json" + | jq -r '.version' +) IFS='.' read -ra parts <<< "$version" major=${parts[0]} From 593b88797f0c909ae0e3e878af681b79678e48e0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 10 Jul 2019 15:03:21 -0400 Subject: [PATCH 475/664] Project Management: Milestone It: Fix bash syntax error with multi-line command --- .github/actions/milestone-it/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/milestone-it/entrypoint.sh b/.github/actions/milestone-it/entrypoint.sh index 2dd48a92e0d8a8..c7548d65ce4dbf 100755 --- a/.github/actions/milestone-it/entrypoint.sh +++ b/.github/actions/milestone-it/entrypoint.sh @@ -33,7 +33,7 @@ fi version=$( curl \ --silent \ - "https://raw.githubusercontent.com/$GITHUB_REPOSITORY/master/package.json" + "https://raw.githubusercontent.com/$GITHUB_REPOSITORY/master/package.json" \ | jq -r '.version' ) From 4c5527066f8a3052aa2e12c799bc1282b47c33ab Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.p.richards@gmail.com> Date: Thu, 11 Jul 2019 03:55:55 +0200 Subject: [PATCH 476/664] Document and rename showSuggestionsOverride to something more sensible (#16497) * Document and rename showSuggestionsOverride to something more sensible * s/progrmatically/programmatically/ --- packages/block-editor/src/components/url-input/README.md | 8 ++++++++ packages/block-editor/src/components/url-input/index.js | 4 ++-- packages/block-library/src/button/edit.js | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/url-input/README.md b/packages/block-editor/src/components/url-input/README.md index 89001d45b3c4e1..d2d11f7573b47b 100644 --- a/packages/block-editor/src/components/url-input/README.md +++ b/packages/block-editor/src/components/url-input/README.md @@ -135,6 +135,14 @@ If you are not conditionally rendering this component set this property to `fals *Optional.* Adds and optional class to the parent `div` that wraps the URLInput field and popover +### `disableSuggestions: Boolean` + +*Optional.* Provides additional control over whether suggestions are disabled. + +When hiding the URLInput using CSS (as is sometimes done for accessibility purposes), the suggestions can still be displayed. This is because they're rendered in a popover in a different part of the DOM, so any styles applied to the URLInput's container won't affect the popover. + +This prop allows the suggestions list to be programmatically not rendered by passing a boolean—it can be `true` to make sure suggestions aren't rendered, or `false`/`undefined` to fall back to the default behaviour of showing suggestions when matching autocompletion items are found. + ## Example {% codetabs %} diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 39035bd1e9d4c8..9652df56c9da74 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -223,9 +223,9 @@ class URLInput extends Component { this.inputRef.current.focus(); } - static getDerivedStateFromProps( { showSuggestionsOverride }, { showSuggestions } ) { + static getDerivedStateFromProps( { disableSuggestions }, { showSuggestions } ) { return { - showSuggestions: showSuggestionsOverride !== undefined ? showSuggestionsOverride : showSuggestions, + showSuggestions: disableSuggestions === true ? false : showSuggestions, }; } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index f4d38282c196ce..4e8f0dfedc3265 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -141,7 +141,7 @@ class ButtonEdit extends Component { autoFocus={ false } /* eslint-enable jsx-a11y/no-autofocus */ onChange={ ( value ) => setAttributes( { url: value } ) } - showSuggestionsOverride={ ! isSelected ? false : undefined } + disableSuggestions={ ! isSelected } id={ linkId } isFullWidth hasBorder From 99a7eb496dcd70d6b743d9557d3df66707af6d41 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani <hardeepasrani@gmail.com> Date: Thu, 11 Jul 2019 07:37:17 +0530 Subject: [PATCH 477/664] Expose position prop in DotTip component (#14972) * Add position prop to DotTip Added `position` prop to DotTip component. Closes https://github.com/WordPress/gutenberg/issues/14923 * Update index.js --- packages/nux/src/components/dot-tip/README.md | 8 ++++++++ packages/nux/src/components/dot-tip/index.js | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/nux/src/components/dot-tip/README.md b/packages/nux/src/components/dot-tip/README.md index 5cde47c52293d8..a5328fc5a7f0f6 100644 --- a/packages/nux/src/components/dot-tip/README.md +++ b/packages/nux/src/components/dot-tip/README.md @@ -26,6 +26,14 @@ A string that uniquely identifies the tip. Identifiers should be prefixed with t - Type: `string` - Required: Yes +### position + +The direction in which the popover should open relative to its parent node. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"middle"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis. + +- Type: `String` +- Required: No +- Default: `"middle right"` + ### children Any React element or elements can be passed as children. They will be rendered within the tip bubble. diff --git a/packages/nux/src/components/dot-tip/index.js b/packages/nux/src/components/dot-tip/index.js index cd2356f8026690..55479ce6b9efaf 100644 --- a/packages/nux/src/components/dot-tip/index.js +++ b/packages/nux/src/components/dot-tip/index.js @@ -20,6 +20,7 @@ function onClick( event ) { } export function DotTip( { + position = 'middle right', children, isVisible, hasNextTip, @@ -33,7 +34,7 @@ export function DotTip( { return ( <Popover className="nux-dot-tip" - position="middle right" + position={ position } noArrow focusOnMount="container" getAnchorRect={ getAnchorRect } From c23228fb78887ae3146c49b2e370150d025a256f Mon Sep 17 00:00:00 2001 From: Jonathan Goldford <jonathan@wiredimpact.com> Date: Wed, 10 Jul 2019 21:14:24 -0500 Subject: [PATCH 478/664] Add documentation for the RichText component to the Block Editor Handbook (#15956) * add documentation for the RichText component * modify internal links to use the appropriate format * Modify link to RichText properties to make it absolute Co-Authored-By: Robert Anderson <robert@noisysocks.com> * Modify links to core block source code to make them absolute URLs * Update manifest.json by running npm run docs:build --- .../developers/richtext.md | 119 ++++++++++++++++++ docs/manifest-devhub.json | 6 + docs/toc.json | 1 + 3 files changed, 126 insertions(+) create mode 100644 docs/designers-developers/developers/richtext.md diff --git a/docs/designers-developers/developers/richtext.md b/docs/designers-developers/developers/richtext.md new file mode 100644 index 00000000000000..6e0752f64f8752 --- /dev/null +++ b/docs/designers-developers/developers/richtext.md @@ -0,0 +1,119 @@ +# RichText Reference + +RichText is a component that allows developers to render a [`contenteditable` input](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content), providing users with the option to format block content to make it bold, italics, linked, or use other formatting. + +The RichText component is extremely powerful because it provides built-in functionality you won't find in other components: + +* **Consistent Styling in the Admin and Frontend:** The editable container can be set to any block-level element, such as a `div`, `h2` or `p` tag. This allows the styles you apply in style.css to more easily apply on the frontend and admin, without having to rewrite them in editor.css. +* **Cohesive Placeholder Text:** Before the user writes their content, it's easy to include placeholder text that's already styled to match the rest of the block editor. +* **Control Over Formatting Options:** It's possible to dictate exactly which formatting options you want to allow for the RichText field. For example, you can dictate whether to allow the user to make text bold, italics or both. + +Unlike other components that exist in the [Component Reference](/packages/components/README.md) section, RichText lives separately because it only makes sense within the block editor, and not within other areas of WordPress. + +## Property Reference + +For a list of the possible properties to pass your RichText component, [check out the component documentation on Github](https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/rich-text/README.md). + +## Core Blocks Using the RichText Component + +There are a number of core blocks using the RichText component. The JavaScript edit function linked below for each block can be used as a best practice reference while creating your own blocks. + +* **[Button](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/button/edit.js):** RichText is used to enter the button's text. +* **[Heading](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/heading/edit.js):** RichText is used to enter the heading's text. +* **[Quote](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/quote/edit.js):** RichText is used in two places, for both the quotation and citation text. +* **[Search](https://github.com/WordPress/gutenberg/blob/master/packages/block-library/src/search/edit.js):** RichText is used in two places, for both the label above the search field and the submit button text. + +## Example + +{% codetabs %} +{% ES5 %} +```js +wp.blocks.registerBlockType( /* ... */, { + // ... + + attributes: { + content: { + type: 'string', + source: 'html', + selector: 'h2', + }, + }, + + edit: function( props ) { + return wp.element.createElement( wp.editor.RichText, { + tagName: 'h2', // The tag here is the element output and editable in the admin + className: props.className, + value: props.attributes.content, // Any existing content, either from the database or an attribute default + formattingControls: [ 'bold', 'italic' ], // Allow the content to be made bold or italic, but do not allow other formatting options + onChange: function( content ) { + props.setAttributes( { content: content } ); // Store updated content as a block attribute + }, + placeholder: __( 'Heading...' ), // Display this text before any content has been added by the user + keepPlaceholderOnFocus: true // Keep the placeholder text showing even when the field is focused (leave this property off to remove placeholder content on focus) + } ); + }, + + save: function( props ) { + return wp.element.createElement( wp.editor.RichText.Content, { + tagName: 'h2', value: props.attributes.content // Saves <h2>Content added in the editor...</h2> to the database for frontend display + } ); + } +} ); +``` +{% ESNext %} +```js +const { registerBlockType } = wp.blocks; +const { RichText } = wp.editor; + +registerBlockType( /* ... */, { + // ... + + attributes: { + content: { + type: 'string', + source: 'html', + selector: 'h2', + }, + }, + + edit( { className, attributes, setAttributes } ) { + return ( + <RichText + tagName="h2" // The tag here is the element output and editable in the admin + className={ className } + value={ attributes.content } // Any existing content, either from the database or an attribute default + formattingControls={ [ 'bold', 'italic' ] } // Allow the content to be made bold or italic, but do not allow other formatting options + onChange={ ( content ) => setAttributes( { content } ) } // Store updated content as a block attribute + placeholder={ __( 'Heading...' ) } // Display this text before any content has been added by the user + keepPlaceholderOnFocus // Keep the placeholder text showing even when the field is focused (leave this property off to remove placeholder content on focus) + /> + ); + }, + + save( { attributes } ) { + return <RichText.Content tagName="h2" value={ attributes.content } />; // Saves <h2>Content added in the editor...</h2> to the database for frontend display + } +} ); +``` +{% end %} + +## Common Issues & Solutions + +While using the RichText component a number of common issues tend to appear. + +### Placeholder Content Separates from the Input + +In some cases the placeholder content on RichText can appear separate from the input where you would write your content. This is likely due to one of two reasons: + +1. You can't use an [inline HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements) as the RichText component. If your `tagName` property is using an inline element such as `span`, `a` or `code`, it needs to be changed to a [block-level element](https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements). +2. The `position` CSS property value for the element must be set to `relative` or `absolute` within the admin. If the styles within style.css or editor.css modify the `position` property value for this element, you may see issues with how it displays. + +### HTML Formatting Tags Display in the Content + +If the HTML tags from text formatting such as `<strong>` or `<em>` are being escaped and displayed on the frontend of the site, this is likely due to an issue in your save function. Make sure your code looks something like `<RichText.Content tagName="h2" value={ heading } />` (ESNext) within your save function instead of simply outputting the value with `<h2>{ heading }</h2>`. + +### Unwanted Formatting Options Still Display + +Before moving forward, consider if using the RichText component makes sense at all. Would it be better to use a basic `input` or `textarea` element? If you don't think any formatting should be possible, these HTML tags may make more sense. + +If you'd still like to use RichText, you can eliminate all of the formatting options by specifying the `formattingControls` property as `formattingControls={ [] }` (ESNext). It's possible you'll continue to see formatting options for adding code, an inline image or other formatting. Don't worry, you've found an existing bug that should be fixed soon. diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 559d963aa2a494..1a5dc5fd60f139 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -137,6 +137,12 @@ "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md", "parent": "slotfills" }, + { + "title": "RichText Reference", + "slug": "richtext", + "markdown_source": "../docs/designers-developers/developers/richtext.md", + "parent": "developers" + }, { "title": "Internationalization", "slug": "internationalization", diff --git a/docs/toc.json b/docs/toc.json index c26aec92dd5f82..8b3f3b4d8a4fe3 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -25,6 +25,7 @@ { "docs/designers-developers/developers/slotfills/plugin-sidebar.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-sidebar-more-menu-item.md": [] } ]}, + { "docs/designers-developers/developers/richtext.md": [] }, { "docs/designers-developers/developers/internationalization.md": [] }, { "docs/designers-developers/developers/accessibility.md": [] }, { "docs/designers-developers/developers/feature-flags.md": [] }, From 4bd0556a41776ffb17040a033155ffc5339fccc4 Mon Sep 17 00:00:00 2001 From: Benjamin Zekavica <info@benjamin-zekavica.de> Date: Thu, 11 Jul 2019 11:09:12 +0200 Subject: [PATCH 479/664] Docs: JSX Tag Name is not same | Snipped works now (#16526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * JSX Tag Name is not same | Snipped works now * Update packages/edit-post/README.md Co-Authored-By: Sören Wrede <soerenwrede@gmail.com> * Documentation Change -> Change Plugin Documentation Setting Panel Readme --- packages/edit-post/README.md | 2 +- .../components/sidebar/plugin-document-setting-panel/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index 6b895c79e75fb5..bf6092367aa36f 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -136,7 +136,7 @@ const { PluginDocumentSettingPanel } = wp.editPost; const MyDocumentSettingTest = () => ( <PluginDocumentSettingPanel className="my-document-setting-plugin"> <p>My Document Setting Panel</p> - </PluginDocumentSetting> + </PluginDocumentSettingPanel> ); registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } ); diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js index a4ba4b924273ba..d06b4134b45593 100644 --- a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js @@ -72,7 +72,7 @@ const PluginDocumentSettingFill = ( { isEnabled, opened, onToggle, className, ti * const MyDocumentSettingTest = () => ( * <PluginDocumentSettingPanel className="my-document-setting-plugin"> * <p>My Document Setting Panel</p> - * </PluginDocumentSetting> + * </PluginDocumentSettingPanel> * ); * * registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } ); From 686085798b51fdac629ee43323e25dff53af32e1 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 11 Jul 2019 12:26:01 +0100 Subject: [PATCH 480/664] Fix issue where the toolbar can be hidden for the block after a wide aligned image (#16530) --- .../src/components/block-list/moving-animation.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js index e0fdf75ed6d6b3..393bfa33e2a7ca 100644 --- a/packages/block-editor/src/components/block-list/moving-animation.js +++ b/packages/block-editor/src/components/block-list/moving-animation.js @@ -43,7 +43,9 @@ function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationO x: previous ? previous.left - destination.left : 0, y: previous ? previous.top - destination.top : 0, }; - ref.current.style.transform = `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; + ref.current.style.transform = newTransform.x === 0 && newTransform.y === 0 ? + undefined : + `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; setResetAnimation( true ); setTransform( newTransform ); }, [ triggerAnimationOnChange ] ); From 924f895856ff51516768a4116d686b50dd3c9e02 Mon Sep 17 00:00:00 2001 From: Ryan McCue <me@ryanmccue.info> Date: Thu, 11 Jul 2019 15:12:53 +0100 Subject: [PATCH 481/664] Document the disabled prop for Button (#16531) --- packages/components/src/button/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 0e713086c17de7..722740d443af54 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -122,8 +122,11 @@ const MyButton = () => ( The presence of a `href` prop determines whether an `anchor` element is rendered instead of a `button`. +Props not included in this set will be applied to the `a` or `button` element. + Name | Type | Default | Description --- | --- | --- | --- +`disabled` | `bool` | `false` | Whether the button is disabled. If `true`, this will force a `button` element to be rendered. `href` | `string` | `undefined` | If provided, renders `a` instead of `button`. `isDefault` | `bool` | `false` | Renders a default button style. `isPrimary` | `bool` | `false` | Renders a primary button style. From 9aab86db129607808e5b8c5f0f9ee6530d50519b Mon Sep 17 00:00:00 2001 From: etoledom <etoledom@icloud.com> Date: Thu, 11 Jul 2019 17:24:54 +0200 Subject: [PATCH 482/664] Run native tests in Gutenberg CI (#16404) * Added needed rn dependencies to run native tests * Adding native test's config files * Remove unnecessary mock files from test * Add native mocks * Added commands to run native tests * Fix native editor tests * Add native tests to Travis as allow_failures * Added native unit test as travis job * Experiment - Add native tests as allow_failures * Clean up native jest.config files * Remove dependencies not needed from package.json * Remove more dependencies not needed from package.json * Adding some missing native mocks * Renamed travis native tests job to `JavaScript native mobile tests` --- .gitignore | 3 + .travis.yml | 11 + package-lock.json | 3895 +++++++++++++++-- package.json | 9 + .../media-upload/test/index.native.js | 8 - packages/edit-post/src/test/editor.native.js | 9 +- .../__mocks__/react-native-aztec/index.js | 22 + .../react-native-gutenberg-bridge/index.js | 0 .../native/__mocks__/react-native-hr/index.js | 0 .../__mocks__/react-native-modal/index.js | 0 .../react-native-recyclerview-list/index.js | 0 .../__mocks__/react-native-safe-area/index.js | 0 .../__mocks__/react-native-svg/index.js | 0 .../__mocks__/react-native-video/index.js | 0 test/native/__mocks__/styleMock.js | 73 + test/native/babel.config.js | 57 + test/native/enzyme.config.js | 6 + test/native/jest.config.js | 73 + test/native/setup.js | 80 + 19 files changed, 3786 insertions(+), 460 deletions(-) create mode 100644 test/native/__mocks__/react-native-aztec/index.js create mode 100644 test/native/__mocks__/react-native-gutenberg-bridge/index.js create mode 100644 test/native/__mocks__/react-native-hr/index.js create mode 100644 test/native/__mocks__/react-native-modal/index.js create mode 100644 test/native/__mocks__/react-native-recyclerview-list/index.js create mode 100644 test/native/__mocks__/react-native-safe-area/index.js create mode 100644 test/native/__mocks__/react-native-svg/index.js create mode 100644 test/native/__mocks__/react-native-video/index.js create mode 100644 test/native/__mocks__/styleMock.js create mode 100644 test/native/babel.config.js create mode 100644 test/native/enzyme.config.js create mode 100644 test/native/jest.config.js create mode 100644 test/native/setup.js diff --git a/.gitignore b/.gitignore index 3ad35285fa658e..5e48a472bc5cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ docker-compose.override.yml playground/dist .cache + +# Report generated from jest-junit +test/native/junit.xml diff --git a/.travis.yml b/.travis.yml index 9e42ed6163f3a5..9a414f619ff17c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,6 +61,16 @@ jobs: - npx lerna run build - npm run test-unit -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" + - name: JavaScript native mobile tests + install: + - npm ci + script: + # It's not necessary to run the full build, since Jest can interpret + # source files with `babel-jest`. Some packages have their own custom + # build tasks, however. These must be run. + - npx lerna run build + - npm run test-unit:native -- --ci --maxWorkers=2 --cacheDirectory="$HOME/.jest-cache" + - name: PHP unit tests (Docker) env: WP_VERSION=latest DOCKER=true script: @@ -150,6 +160,7 @@ jobs: - $( npm bin )/wp-scripts test-e2e --config=./packages/e2e-tests/jest.config.js --cacheDirectory="$HOME/.jest-cache" --runTestsByPath $( awk 'NR % 4 == 3' < ~/.jest-e2e-tests ) allow_failures: + - name: JavaScript native mobile tests - name: PHP unit tests (PHP 5.3) env: WP_VERSION=latest SWITCH_TO_PHP=5.3 script: diff --git a/package-lock.json b/package-lock.json index 03cdd6b0786b90..c3743ee2db665c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,6 +120,20 @@ "@babel/types": "^7.4.4" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.0.tgz", + "integrity": "sha512-EAoMc3hE5vE5LNhMqDOwB1usHvmRjCDAnH8CD4PVkX9/Yr3W/tcz8xE8QvdZxfsFBDICwZnF2UTHIqslRpvxmA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-member-expression-to-functions": "^7.0.0", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-split-export-declaration": "^7.4.4" + } + }, "@babel/helper-define-map": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", @@ -310,6 +324,15 @@ "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", "dev": true }, + "@babel/plugin-external-helpers": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.2.0.tgz", + "integrity": "sha512-QFmtcCShFkyAsNtdCM3lJPmRe1iB+vPZymlB4LnDIKEBj2yKQLQKtoxXxJ8ePT5fwMl4QGg303p4mB0UsSI2/g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-proposal-async-generator-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", @@ -321,6 +344,26 @@ "@babel/plugin-syntax-async-generators": "^7.2.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.0.tgz", + "integrity": "sha512-9L/JfPCT+kShiiTTzcnBJ8cOwdKVmlC1RcCf9F0F9tERVrM4iWtWnXtjWCRqNm2la2BxO1MPArWNsU9zsSJWSQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.5.0", + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-proposal-export-default-from": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.5.2.tgz", + "integrity": "sha512-wr9Itk05L1/wyyZKVEmXWCdcsp/e185WUNl6AfYZeEKYaUPPvHXRDqO5K1VH7/UamYqGJowFRuCv30aDYZawsg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.2.0" + } + }, "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", @@ -331,6 +374,16 @@ "@babel/plugin-syntax-json-strings": "^7.2.0" } }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.4.4.tgz", + "integrity": "sha512-Amph7Epui1Dh/xxUxS2+K22/MUi6+6JVTvy3P58tja3B6yKTSjwwx0/d83rF7551D6PVSSoplQb8GCwqec7HRw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.2.0" + } + }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz", @@ -351,6 +404,16 @@ "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" } }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.2.0.tgz", + "integrity": "sha512-ea3Q6edZC/55wEBVZAEz42v528VulyO0eir+7uky/sT4XRcdkWJcFi1aPtitTlwUzGnECWJNExWww1SStt+yWw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.2.0" + } + }, "@babel/plugin-proposal-unicode-property-regex": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", @@ -371,6 +434,33 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-class-properties": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.2.0.tgz", + "integrity": "sha512-UxYaGXYQ7rrKJS/PxIKRkv3exi05oH7rokBAsmCSsCxz1sVPZ7Fu6FzKoGgUvmY+0YgSkYHgUoCh5R5bCNBQlw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", + "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-export-default-from": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.2.0.tgz", + "integrity": "sha512-c7nqUnNST97BWPtoe+Ssi+fJukc9P9/JMZ71IOMNQWza2E+Psrd46N6AEvtw6pqK+gt7ChjXyrw4SPDO79f3Lw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-syntax-flow": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz", @@ -398,6 +488,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.2.0.tgz", + "integrity": "sha512-lRCEaKE+LTxDQtgbYajI04ddt6WW0WJq57xqkAZ+s11h4YgfRHhVA/Y2VhfPzzFD4qeLHWg32DMp9HooY4Kqlg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", @@ -416,6 +515,24 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.2.0.tgz", + "integrity": "sha512-HtGCtvp5Uq/jH/WNUPkK6b7rufnCPLLlDAFN7cmACoIjaOOiXxUt3SswU5loHqrhtqTsa/WoLQ1OQ1AGuZqaWA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz", + "integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", @@ -625,6 +742,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-object-assign": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.2.0.tgz", + "integrity": "sha512-nmE55cZBPFgUktbF2OuoZgPRadfxosLOpSgzEPYotKSls9J4pEPcembi8r78RU37Rph6UApCpNmsQA4QMWK9Ng==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-transform-object-super": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz", @@ -655,6 +781,15 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-react-display-name": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", + "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, "@babel/plugin-transform-react-jsx": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", @@ -666,6 +801,16 @@ "@babel/plugin-syntax-jsx": "^7.2.0" } }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz", + "integrity": "sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.2.0" + } + }, "@babel/plugin-transform-regenerator": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.4.tgz", @@ -751,6 +896,17 @@ "@babel/helper-plugin-utils": "^7.0.0" } }, + "@babel/plugin-transform-typescript": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.5.2.tgz", + "integrity": "sha512-r4zJOMbKY5puETm8+cIpaa0RQZG/sSASW1u0pj8qYklcERgVIbxVbP2wyJA7zI1//h7lEagQmXi9IL9iI5rfsA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.5.0", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.2.0" + } + }, "@babel/plugin-transform-unicode-regex": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", @@ -849,10 +1005,111 @@ } } }, - "@babel/runtime": { + "@babel/register": { "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz", - "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.4.4.tgz", + "integrity": "sha512-sn51H88GRa00+ZoMqCVgOphmswG4b7mhf9VOB0LUBAieykq2GnRFerlN+JQkO/ntT7wz4jaHNSRPg9IdMPEUkA==", + "dev": true, + "requires": { + "core-js": "^3.0.0", + "find-cache-dir": "^2.0.0", + "lodash": "^4.17.11", + "mkdirp": "^0.5.1", + "pirates": "^4.0.0", + "source-map-support": "^0.5.9" + }, + "dependencies": { + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "@babel/runtime": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.4.tgz", + "integrity": "sha512-Na84uwyImZZc3FKf4aUF1tysApzwf3p2yuFBIyBfbzT5glzKTdvYI4KVW4kcgjrzoGUjC7w3YyCHcJKaRxsr2Q==", "requires": { "regenerator-runtime": "^0.13.2" } @@ -933,6 +1190,53 @@ "minimist": "^1.2.0" } }, + "@hapi/address": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", + "integrity": "sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw==", + "dev": true + }, + "@hapi/hoek": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-6.2.4.tgz", + "integrity": "sha512-HOJ20Kc93DkDVvjwHyHawPwPkX44sIrbXazAUDiUXaY2R9JwQGo2PhFfnQtdrsIe4igjG2fPgMra7NYw7qhy0A==", + "dev": true + }, + "@hapi/joi": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.0.tgz", + "integrity": "sha512-n6kaRQO8S+kepUTbXL9O/UOL788Odqs38/VOfoCrATDtTvyfiO3fgjlSRaNkHabpTLgM7qru9ifqXlXbXk8SeQ==", + "dev": true, + "requires": { + "@hapi/address": "2.x.x", + "@hapi/hoek": "6.x.x", + "@hapi/marker": "1.x.x", + "@hapi/topo": "3.x.x" + } + }, + "@hapi/marker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@hapi/marker/-/marker-1.0.0.tgz", + "integrity": "sha512-JOfdekTXnJexfE8PyhZFyHvHjt81rBFSAbTIRAhF2vv/2Y1JzoKsGqxH/GpZJoF7aEfYok8JVcAHmSz1gkBieA==", + "dev": true + }, + "@hapi/topo": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.2.tgz", + "integrity": "sha512-r+aumOqJ5QbD6aLPJWqVjMAPsx5pZKz+F5yPqXZ/WWG9JTtHbQqlzrJoknJ0iJxLj9vlXtmpSdjlkszseeG8OA==", + "dev": true, + "requires": { + "@hapi/hoek": "8.x.x" + }, + "dependencies": { + "@hapi/hoek": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.0.2.tgz", + "integrity": "sha512-O6o6mrV4P65vVccxymuruucb+GhP2zl9NLCG8OdoFRS8BEGw3vwpPp20wpAtpbQQxz1CEUtmxJGgWhjq1XA3qw==", + "dev": true + } + } + }, "@iarna/toml": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.3.tgz", @@ -2900,46 +3204,185 @@ "physical-cpu-count": "^2.0.0" } }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "@react-native-community/cli-platform-android": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-2.1.1.tgz", + "integrity": "sha512-M6HNSJYTPNAt8iV8RiclSySyBavo9dsMKAIvKsHJqwbIRNWbRPHONQ71hvw7PzxC+RTwFTHUFKMotWgjKGEKPw==", "dev": true, "requires": { - "any-observable": "^0.3.0" - } - }, - "@tannin/compile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.3.tgz", - "integrity": "sha512-OkPHvaM/hIHdSco3+ZO1hzkOtfEddn5a0veWft2aDLvKnbdj9VusiLKNdEE9by3hCZIIcb9aWF+iBorhfrQOfw==", - "requires": { - "@tannin/evaluate": "^1.1.1", - "@tannin/postfix": "^1.0.2" - } - }, - "@tannin/evaluate": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.1.1.tgz", - "integrity": "sha512-ALuSZHjrLHGnw0WxsHDHde74FJ2WW0Ck4rg3QBxFBCmxd6Wsac+e0HXfJ++Qion15LIOCmFhyVpWzawMgeBA8Q==" - }, - "@tannin/plural-forms": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.0.3.tgz", - "integrity": "sha512-IUr9+FiCnzCiB9aRio3FVNR8TNL9SmX2zkV6tmfWWwSclX4uTCykoGsDhTGKK+sZnMrdPCTmb/OxbtGNdVyV4g==", - "requires": { - "@tannin/compile": "^1.0.3" + "@react-native-community/cli-tools": "^2.0.2", + "logkitty": "^0.5.0", + "slash": "^2.0.0", + "xmldoc": "^0.4.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } } }, - "@tannin/postfix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz", - "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g==" - }, - "@types/babel__core": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", - "integrity": "sha512-+hjBtgcFPYyCTo0A15+nxrCVJL7aC6Acg87TXd5OW3QhHswdrOLoles+ldL2Uk8q++7yIfl4tURtztccdeeyOw==", + "@react-native-community/cli-platform-ios": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-2.2.0.tgz", + "integrity": "sha512-rgqksYcolGZNkG4EnWEX2nECWyTxYRUZXRb+pXc4niBsCvNMU88sXoMTxFIHb3wOAaVT2Eix3nS78yG6TA7dnA==", + "dev": true, + "requires": { + "@react-native-community/cli-tools": "^2.0.2", + "chalk": "^1.1.1", + "xcode": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "@react-native-community/cli-tools": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-2.0.2.tgz", + "integrity": "sha512-6OOKrE1Sdq1Lmcdp2K68J5PsG5G80a9USa9I1Kv92wvPHUup6IRt+Dy7E8IZqxmskzC/mlOR62Oh1CB3sFm84g==", + "dev": true, + "requires": { + "chalk": "^1.1.1", + "lodash": "^4.17.5", + "mime": "^2.4.1", + "node-fetch": "^2.5.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "requires": { + "any-observable": "^0.3.0" + } + }, + "@tannin/compile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.0.3.tgz", + "integrity": "sha512-OkPHvaM/hIHdSco3+ZO1hzkOtfEddn5a0veWft2aDLvKnbdj9VusiLKNdEE9by3hCZIIcb9aWF+iBorhfrQOfw==", + "requires": { + "@tannin/evaluate": "^1.1.1", + "@tannin/postfix": "^1.0.2" + } + }, + "@tannin/evaluate": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.1.1.tgz", + "integrity": "sha512-ALuSZHjrLHGnw0WxsHDHde74FJ2WW0Ck4rg3QBxFBCmxd6Wsac+e0HXfJ++Qion15LIOCmFhyVpWzawMgeBA8Q==" + }, + "@tannin/plural-forms": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.0.3.tgz", + "integrity": "sha512-IUr9+FiCnzCiB9aRio3FVNR8TNL9SmX2zkV6tmfWWwSclX4uTCykoGsDhTGKK+sZnMrdPCTmb/OxbtGNdVyV4g==", + "requires": { + "@tannin/compile": "^1.0.3" + } + }, + "@tannin/postfix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.0.2.tgz", + "integrity": "sha512-Nggtk7/ljfNPpAX8CjxxLkMKuO6u2gH1ozmTvGclWF2pNcxTf6YGghYNYNWZRKrimXGhQ8yZqvAHep7h80K04g==" + }, + "@types/babel__core": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", + "integrity": "sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -2969,9 +3412,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", - "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.7.tgz", + "integrity": "sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -2983,6 +3426,25 @@ "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==", "dev": true }, + "@types/istanbul-lib-report": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", + "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, "@types/node": { "version": "10.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.2.tgz", @@ -3909,6 +4371,21 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "absolute-path": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", + "integrity": "sha1-p4di+9rftSl76ZsV01p4Wy8JW/c=", + "dev": true + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -4022,12 +4499,87 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-escapes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, + "ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "dev": true, + "requires": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -4051,6 +4603,12 @@ "entities": "^1.1.1" } }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, "any-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", @@ -4189,6 +4747,24 @@ "integrity": "sha512-1hWSHTIlG/8wtYD+PPX5AOBtKWngpDFjrsrHgZpe+JdgNGz0udYu6ZIkAa/xuenIUEqFv7DvE2Yr60jxweJSrQ==", "dev": true }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -4235,6 +4811,12 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "art": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/art/-/art-0.10.3.tgz", + "integrity": "sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ==", + "dev": true + }, "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -4448,13 +5030,13 @@ } }, "babel-jest": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.7.1.tgz", - "integrity": "sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg==", + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.8.0.tgz", + "integrity": "sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==", "dev": true, "requires": { - "@jest/transform": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/transform": "^24.8.0", + "@jest/types": "^24.8.0", "@types/babel__core": "^7.1.0", "babel-plugin-istanbul": "^5.1.0", "babel-preset-jest": "^24.6.0", @@ -4462,6 +5044,62 @@ "slash": "^2.0.0" }, "dependencies": { + "@jest/fake-timers": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.8.0.tgz", + "integrity": "sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-mock": "^24.8.0" + } + }, + "@jest/test-result": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.8.0.tgz", + "integrity": "sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/types": "^24.8.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/transform": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.8.0.tgz", + "integrity": "sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.8.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.8.0", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.8.0", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + } + }, + "@jest/types": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.8.0.tgz", + "integrity": "sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^12.0.9" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4473,11 +5111,114 @@ "supports-color": "^5.3.0" } }, - "slash": { + "ci-info": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "jest-haste-map": { + "version": "24.8.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.8.1.tgz", + "integrity": "sha512-SwaxMGVdAZk3ernAx2Uv2sorA7jm3Kx+lR0grp6rMmnY06Kn/urtKx1LPN2mGTea4fCT38impYT28FfcLUhX0g==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.4.0", + "jest-util": "^24.8.0", + "jest-worker": "^24.6.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-message-util": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.8.0.tgz", + "integrity": "sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.8.0.tgz", + "integrity": "sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0" + } + }, + "jest-util": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.8.0.tgz", + "integrity": "sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/fake-timers": "^24.8.0", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } } } }, @@ -4681,6 +5422,62 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-plugin-react-native-classname-to-style": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-classname-to-style/-/babel-plugin-react-native-classname-to-style-1.2.2.tgz", + "integrity": "sha512-kavZRZ56YoYmrwuCRMY0UuCYfMPszoN7v0DLST1D+rTqkl+cFWVW3W8l//AfWsyMNHkY+UTX+RrB5K7GY9mq3w==", + "dev": true + }, + "babel-plugin-react-native-platform-specific-extensions": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-platform-specific-extensions/-/babel-plugin-react-native-platform-specific-extensions-1.1.1.tgz", + "integrity": "sha512-ZNN2ImlUlcaPMfzaCw9fWmTc/Ht5ARI0n9RUqiyCdK7LalPJ7yKc+F60BcSHvMHcmA2BgVO8TpSot+dpeZPAMQ==", + "dev": true, + "requires": { + "@babel/template": "^7.0.0-beta.49" + } + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "7.0.0-beta.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", + "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", + "dev": true + }, + "babel-preset-fbjs": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.2.0.tgz", + "integrity": "sha512-5Jo+JeWiVz2wHUUyAlvb/sSYnXNig9r+HqGAOSfh5Fzxp7SnAaR/tEGRJ1ZX7C77kfk82658w6R5Z+uPATTD9g==", + "dev": true, + "requires": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-syntax-class-properties": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoped-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-member-expression-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-super": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-property-literals": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" + } + }, "babel-preset-jest": { "version": "24.6.0", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", @@ -4819,6 +5616,15 @@ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", "dev": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -4857,6 +5663,12 @@ "tryer": "^1.0.0" } }, + "big-integer": { + "version": "1.6.44", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.44.tgz", + "integrity": "sha512-7MzElZPTyJ2fNvBkPxtFQ2fWIkVmuzw41+BZHSzpEq3ymB2MfeKp1+yXl/tS75xCx+WnyV+yb0kp+K1C3UNwmQ==", + "dev": true + }, "big.js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", @@ -4999,6 +5811,24 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, + "bplist-creator": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", + "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=", + "dev": true, + "requires": { + "stream-buffers": "~2.2.0" + } + }, + "bplist-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", + "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=", + "dev": true, + "requires": { + "big-integer": "^1.6.7" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5215,6 +6045,12 @@ "isarray": "^1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-equal": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", @@ -5548,8 +6384,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5570,14 +6405,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5592,20 +6425,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5722,8 +6552,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5735,7 +6564,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5750,7 +6578,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5758,14 +6585,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5784,7 +6609,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5865,8 +6689,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5878,7 +6701,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5965,8 +6787,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -6002,7 +6823,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6022,7 +6842,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6066,14 +6885,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -6413,6 +7230,18 @@ "color-name": "^1.0.0" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colorette": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", + "integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==", + "dev": true + }, "colors": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", @@ -6506,6 +7335,55 @@ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "dev": true, + "requires": { + "mime-db": ">= 1.40.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + } + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "computed-style": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz", @@ -6629,6 +7507,56 @@ "proto-list": "~1.2.1" } }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + } + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -7310,6 +8238,17 @@ "sha.js": "^2.4.8" } }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "dev": true, + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, "cross-env": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-3.2.4.tgz", @@ -7987,6 +8926,12 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha1-OjYof1A05pnnV3kBBSwubJQlFjE=", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -8112,6 +9057,12 @@ } } }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", + "dev": true + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -8337,6 +9288,12 @@ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, + "envinfo": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.3.1.tgz", + "integrity": "sha512-GvXiDTqLYrORVSCuJCsWHPXF5BFvoWMQA9xX4YVjPT1jyS3aZEHUBwjzxU/6LTPF9ReHgVEbX7IEN5UvSXHw/A==", + "dev": true + }, "enzyme": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.9.0.tgz", @@ -8367,17 +9324,19 @@ } }, "enzyme-adapter-react-16": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.10.0.tgz", - "integrity": "sha512-0QqwEZcBv1xEEla+a3H7FMci+y4ybLia9cZzsdIrId7qcig4MK0kqqf6iiCILH1lsKS6c6AVqL3wGPhCevv5aQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz", + "integrity": "sha512-7PcOF7pb4hJUvjY7oAuPGpq3BmlCig3kxXGi2kFx0YzJHppqX1K8IIV9skT1IirxXlu8W7bneKi+oQ10QRnhcA==", "dev": true, "requires": { - "enzyme-adapter-utils": "^1.10.0", + "enzyme-adapter-utils": "^1.12.0", + "has": "^1.0.3", "object.assign": "^4.1.0", "object.values": "^1.1.0", - "prop-types": "^15.6.2", - "react-is": "^16.7.0", - "react-test-renderer": "^16.0.0-0" + "prop-types": "^15.7.2", + "react-is": "^16.8.6", + "react-test-renderer": "^16.0.0-0", + "semver": "^5.7.0" }, "dependencies": { "define-properties": { @@ -8400,46 +9359,104 @@ "function-bind": "^1.1.1", "has": "^1.0.3" } + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true } } }, "enzyme-adapter-utils": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.10.0.tgz", - "integrity": "sha512-VnIXJDYVTzKGbdW+lgK8MQmYHJquTQZiGzu/AseCZ7eHtOMAj4Rtvk8ZRopodkfPves0EXaHkXBDkVhPa3t0jA==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz", + "integrity": "sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA==", "dev": true, "requires": { + "airbnb-prop-types": "^2.13.2", "function.prototype.name": "^1.1.0", "object.assign": "^4.1.0", "object.fromentries": "^2.0.0", - "prop-types": "^15.6.2", + "prop-types": "^15.7.2", "semver": "^5.6.0" }, "dependencies": { - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - } - } - }, - "enzyme-to-json": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.5.tgz", - "integrity": "sha512-DmH1wJ68HyPqKSYXdQqB33ZotwfUhwQZW3IGXaNXgR69Iodaoj8TF/D9RjLdz4pEhGq2Tx2zwNUIjBuqoZeTgA==", - "dev": true, - "requires": { - "lodash": "^4.17.4" - } - }, - "equivalent-key-map": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/equivalent-key-map/-/equivalent-key-map-0.2.2.tgz", - "integrity": "sha512-xvHeyCDbZzkpN4VHQj/n+j2lOwL0VWszG30X4cOrc9Y7Tuo2qCdZK/0AMod23Z5dCtNUbaju6p0rwOhHUk05ew==" - }, - "err-code": { - "version": "1.1.2", + "airbnb-prop-types": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz", + "integrity": "sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ==", + "dev": true, + "requires": { + "array.prototype.find": "^2.0.4", + "function.prototype.name": "^1.1.0", + "has": "^1.0.3", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.8.6" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "enzyme-to-json": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.5.tgz", + "integrity": "sha512-DmH1wJ68HyPqKSYXdQqB33ZotwfUhwQZW3IGXaNXgR69Iodaoj8TF/D9RjLdz4pEhGq2Tx2zwNUIjBuqoZeTgA==", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } + }, + "equivalent-key-map": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/equivalent-key-map/-/equivalent-key-map-0.2.2.tgz", + "integrity": "sha512-xvHeyCDbZzkpN4VHQj/n+j2lOwL0VWszG30X4cOrc9Y7Tuo2qCdZK/0AMod23Z5dCtNUbaju6p0rwOhHUk05ew==" + }, + "err-code": { + "version": "1.1.2", "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", "dev": true @@ -8472,6 +9489,49 @@ "is-arrayish": "^0.2.1" } }, + "errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + } + } + }, "es-abstract": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", @@ -8992,6 +10052,12 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, "eventemitter3": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", @@ -9337,6 +10403,18 @@ } } }, + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + }, "fast-average-color": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/fast-average-color/-/fast-average-color-4.3.0.tgz", @@ -9424,6 +10502,44 @@ } } }, + "fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "dev": true + }, + "fbjs-scripts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-1.2.0.tgz", + "integrity": "sha512-5krZ8T0Bf8uky0abPoCLrfa7Orxd8UH4Qq8hRUF2RZYNMu+FmEOrBc7Ib3YVONmxTXTlLAvyrrdrVmksDb2OqQ==", + "dev": true, + "requires": { + "@babel/core": "^7.0.0", + "ansi-colors": "^1.0.1", + "babel-preset-fbjs": "^3.2.0", + "core-js": "^2.4.1", + "cross-spawn": "^5.1.0", + "fancy-log": "^1.3.2", + "object-assign": "^4.0.1", + "plugin-error": "^0.1.2", + "semver": "^5.1.0", + "through2": "^2.0.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -9830,8 +10946,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -9852,14 +10967,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9874,20 +10987,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -10004,8 +11114,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -10017,7 +11126,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10032,7 +11140,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10040,14 +11147,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10066,7 +11171,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -10147,8 +11251,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -10160,7 +11263,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -10247,8 +11349,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -10284,7 +11385,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10304,7 +11404,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10348,14 +11447,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -10868,6 +11965,16 @@ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "global-cache": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/global-cache/-/global-cache-1.2.1.tgz", @@ -11380,6 +12487,12 @@ "minimatch": "^3.0.4" } }, + "image-size": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", + "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", + "dev": true + }, "import-fresh": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", @@ -12872,6 +13985,18 @@ "throat": "^4.0.0" } }, + "jest-junit": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-6.4.0.tgz", + "integrity": "sha512-GXEZA5WBeUich94BARoEUccJumhCgCerg7mXDFLxWwI2P7wL3Z7sGWk+53x343YdBLjiMR9aD/gYMVKO+0pE4Q==", + "dev": true, + "requires": { + "jest-validate": "^24.0.0", + "mkdirp": "^0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.1" + } + }, "jest-leak-detector": { "version": "24.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz", @@ -13272,6 +14397,32 @@ "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", "dev": true }, + "jest-serializer-enzyme": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jest-serializer-enzyme/-/jest-serializer-enzyme-1.0.0.tgz", + "integrity": "sha1-+LUJDRrk0QW7rr8E0zvl3ymI8Fk=", + "dev": true, + "requires": { + "enzyme-to-json": "^1.4.4" + }, + "dependencies": { + "enzyme-to-json": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-1.6.0.tgz", + "integrity": "sha512-izMrbriQySEiWDUR0NeAyzCiRBncgDjfX5bt3xobkyUinEA79q8UuBNUfWFyjX2ahhP2G8k1GN4kG9NAUF405g==", + "dev": true, + "requires": { + "lodash.filter": "^4.6.0", + "lodash.isnil": "^4.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.omitby": "^4.6.0", + "lodash.range": "^3.2.0", + "object-values": "^1.0.0", + "object.entries": "^1.0.4" + } + } + } + }, "jest-snapshot": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.7.1.tgz", @@ -13500,6 +14651,12 @@ "dev": true, "optional": true }, + "jsc-android": { + "version": "245459.0.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-245459.0.0.tgz", + "integrity": "sha512-wkjURqwaB1daNkDi2OYYbsLnIdC/lUM2nPXQKRs5pqEU9chDg435bjvo+LSaHotDENygHQDHe+ntUkkw2gwMtg==", + "dev": true + }, "jsdom": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", @@ -13596,6 +14753,15 @@ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -13623,6 +14789,12 @@ "graceful-fs": "^4.1.6" } }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -13656,6 +14828,15 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "kleur": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.2.tgz", @@ -14125,6 +15306,12 @@ "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", "dev": true }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=", + "dev": true + }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -14149,12 +15336,36 @@ "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, + "lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.omitby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.omitby/-/lodash.omitby-4.6.0.tgz", + "integrity": "sha1-XBX/R1StVVAWtTwEExHo8HkgR5E=", + "dev": true + }, + "lodash.range": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.range/-/lodash.range-3.2.0.tgz", + "integrity": "sha1-9GHliPZmg/fq3q3lE+OKaaVloV0=", + "dev": true + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -14192,6 +15403,12 @@ "lodash._reinterpolate": "~3.0.0" } }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -14230,56 +15447,249 @@ } } }, - "long": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", - "dev": true - }, - "longest-streak": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.2.tgz", - "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "logkitty": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.5.0.tgz", + "integrity": "sha512-UA06TmPaSPiHxMBlo5uxL3ZvjJ2Gx/rEECrqowHsIsNoAoSB8aBSP553Fr2FJhOp3it2ulLsd520DZWS1IaYOw==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "macos-release": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.2.0.tgz", - "integrity": "sha512-iV2IDxZaX8dIcM7fG6cI46uNmHUxHE4yN+Z8tKHAW1TBPMZDIKHf/3L+YnOuj/FK9il14UaVdHmiQ1tsi90ltA==", - "dev": true - }, - "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "ansi-fragments": "^0.2.1", + "yargs": "^12.0.5" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true + }, + "longest-streak": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.2.tgz", + "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "macos-release": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.2.0.tgz", + "integrity": "sha512-iV2IDxZaX8dIcM7fG6cI46uNmHUxHE4yN+Z8tKHAW1TBPMZDIKHf/3L+YnOuj/FK9il14UaVdHmiQ1tsi90ltA==", + "dev": true + }, + "magic-string": { + "version": "0.22.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", "dev": true, "requires": { "vlq": "^0.2.2" @@ -14772,216 +16182,987 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "metro": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.54.1.tgz", + "integrity": "sha512-6ODPT4mEo4FCpbExRNnQAcZmf1VeNvYOTMj2Na03FjGqhNODHhI2U/wF/Ul5gqTyJ2dVdkXeyvKW3gl/LrnJRg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "@babel/core": "^7.0.0", + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/plugin-external-helpers": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "absolute-path": "^0.0.0", + "async": "^2.4.0", + "babel-preset-fbjs": "^3.1.2", + "buffer-crc32": "^0.2.13", + "chalk": "^2.4.1", + "concat-stream": "^1.6.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "eventemitter3": "^3.0.0", + "fbjs": "^1.0.0", + "fs-extra": "^1.0.0", + "graceful-fs": "^4.1.3", + "image-size": "^0.6.0", + "invariant": "^2.2.4", + "jest-haste-map": "^24.7.1", + "jest-worker": "^24.6.0", + "json-stable-stringify": "^1.0.1", + "lodash.throttle": "^4.1.1", + "merge-stream": "^1.0.1", + "metro-babel-register": "0.54.1", + "metro-babel-transformer": "0.54.1", + "metro-cache": "0.54.1", + "metro-config": "0.54.1", + "metro-core": "0.54.1", + "metro-inspector-proxy": "0.54.1", + "metro-minify-uglify": "0.54.1", + "metro-react-native-babel-preset": "0.54.1", + "metro-resolver": "0.54.1", + "metro-source-map": "0.54.1", + "mime-types": "2.1.11", + "mkdirp": "^0.5.1", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.0", + "react-transform-hmr": "^1.0.4", + "resolve": "^1.5.0", + "rimraf": "^2.5.4", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "temp": "0.8.3", + "throat": "^4.1.0", + "wordwrap": "^1.0.0", + "write-file-atomic": "^1.2.0", + "ws": "^1.1.5", + "xpipe": "^1.0.5", + "yargs": "^9.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "fbjs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz", + "integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==", + "dev": true, + "requires": { + "core-js": "^2.4.1", + "fbjs-css-vars": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + } + }, + "fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "metro-react-native-babel-preset": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.54.1.tgz", + "integrity": "sha512-Hfr32+u5yYl3qhYQJU8NQ26g4kQlc3yFMg7keVR/3H8rwBIbFqXgsKt8oe0dOrv7WvrMqBHhDtVdU9ls3sSq8g==", + "dev": true, + "requires": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-assign": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.0.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "metro-babel7-plugin-react-transform": "0.54.1", + "react-transform-hmr": "^1.0.4" + } + }, + "mime-db": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz", + "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk=", + "dev": true + }, + "mime-types": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz", + "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=", + "dev": true, + "requires": { + "mime-db": "~1.23.0" + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "metro-babel-register": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-babel-register/-/metro-babel-register-0.54.1.tgz", + "integrity": "sha512-j3VydgncUG8HP6AZala6GTIt3V01nptodnnOke3JMYLqgk8EJ1LOVOdotK9pXi80o7EmmNKFs/LyyH8z+uAJzQ==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/register": "^7.0.0", + "core-js": "^2.2.2", + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + } } }, - "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", - "dev": true - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "metro-babel-transformer": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.54.1.tgz", + "integrity": "sha512-2aiAnuYBdcLV1VINb8ENAA4keIaJIepHgR9+iRvIde+9GSjKnexqx4nNmJN392285gRDp1fVZ7uY0uQawK/A5g==", "dev": true, "requires": { - "mime-db": "~1.33.0" + "@babel/core": "^7.0.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "metro-babel7-plugin-react-transform": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-babel7-plugin-react-transform/-/metro-babel7-plugin-react-transform-0.54.1.tgz", + "integrity": "sha512-jWm5myuMoZAOhoPsa8ItfDxdTcOzKhTTzzhFlbZnRamE7i9qybeMdrZt8KHQpF7i2p/mKzE9Yhf4ouOz5K/jHg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0" + } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "metro-cache": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.54.1.tgz", + "integrity": "sha512-RxCFoNcANHXZYi4MIQNnqh68gUnC3bMpzCFJY5pBoqqdrkkn8ibYglBweA0/DW7hx1OZTJWelwS1Dp8xxmE2CA==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "jest-serializer": "^24.4.0", + "metro-core": "0.54.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "metro-config": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.54.1.tgz", + "integrity": "sha512-FpxrA+63rGkPGvGI653dvuSreJzU+eOTILItVnnhmqwn2SAK5V00N/qGTOIJe2YIuWEFXwCzw9lXmANrXbwuGg==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.5", + "jest-validate": "^24.7.0", + "metro": "0.54.1", + "metro-cache": "0.54.1", + "metro-core": "0.54.1", + "pretty-format": "^24.7.0" + } }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "metro-core": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.54.1.tgz", + "integrity": "sha512-8oz3Ck7QFBzW9dG9tKFhrXHKPu2Ajx3R7eatf61Gl6Jf/tF7PNouv3wHxPsJW3oXDFiwKLszd89+OgleTGkB5g==", "dev": true, "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" + "jest-haste-map": "^24.7.1", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.54.1", + "wordwrap": "^1.0.0" } }, - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "metro-inspector-proxy": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.54.1.tgz", + "integrity": "sha512-sf6kNu7PgFW6U+hU7YGZfbAUKAPVvCJhY8YVu/A1RMKH9nNULrCo+jlWh0gWgmFfWRQiAPCElevROg+5somk8A==", "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "connect": "^3.6.5", + "debug": "^2.2.0", + "rxjs": "^5.4.3", + "ws": "^1.1.5", + "yargs": "^9.0.0" }, "dependencies": { - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } } } }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "metro-minify-uglify": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.54.1.tgz", + "integrity": "sha512-z+pOPna/8IxD4OhjW6Xo1mV2EszgqqQHqBm1FdmtdF6IpWkQp33qpDBNEi9NGZTOr7pp2bvcxZnvNJdC2lrK9Q==", "dev": true, "requires": { - "minipass": "^2.2.1" + "uglify-es": "^3.1.9" + }, + "dependencies": { + "commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "dev": true, + "requires": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + } + } } }, - "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^2.0.1", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "metro-react-native-babel-preset": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.55.0.tgz", + "integrity": "sha512-HUI+dEiVym8f1NYIF1grY9PdoY0d3SSS/HED2dDDvTORwndsAEWuXiUgKFOGWX18+RUAQog8obVQuBMgrr8ZBQ==", + "dev": true, + "requires": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-assign": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.0.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "react-refresh": "^0.2.0" } }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "metro-react-native-babel-transformer": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.55.0.tgz", + "integrity": "sha512-K5dJh/HXkebKnSgJJ8XeWliwJFmit1u7CnswbhoYd0XTQQIYj6HV+neN/FzJV5tvkFkQC3asH+5xAW92HE6uRg==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "@babel/core": "^7.0.0", + "babel-preset-fbjs": "^3.1.2", + "metro-babel-transformer": "0.55.0", + "metro-react-native-babel-preset": "0.55.0", + "metro-source-map": "0.55.0" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "metro-babel-transformer": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.55.0.tgz", + "integrity": "sha512-eKslZokx7g0xKinJztOGELlR5N3F9oKVN5Eb9srwgFlfldY2VXN1BIYj8EBKgCA7JWgK4Rqo18/r+u+ktXzlPA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "@babel/core": "^7.0.0", + "metro-source-map": "0.55.0" + } + }, + "metro-source-map": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.55.0.tgz", + "integrity": "sha512-HZODA0KPl5onJNGIztfTHHWurR2nL6Je/X8wwj+bL4ZBB/hSMVeDk7rWReCAvO3twVz7Ztp8Si0jfMmmH4Ruuw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.55.0", + "ob1": "0.55.0", + "source-map": "^0.5.6", + "vlq": "^1.0.0" } + }, + "vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "dev": true } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "metro-resolver": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.54.1.tgz", + "integrity": "sha512-Byv1LIawYAASy9CFRwzrncYnqaFGLe8vpw178EtzStqP05Hu6hXSqkNTrfoXa+3V9bPFGCrVzFx2NY3gFp2btg==", "dev": true, "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } + "absolute-path": "^0.0.0" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "metro-source-map": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.54.1.tgz", + "integrity": "sha512-E9iSYMSUSq5qYi1R2hTQtxH4Mxjzfgr/jaSmQIWi7h3fG2P1qOZNNSzeaeUeTK+s2N/ksVlkcL5kMikol8CDrQ==", "dev": true, "requires": { - "minimist": "0.0.8" + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "source-map": "^0.5.6" + } + }, + "metro-symbolicate": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.55.0.tgz", + "integrity": "sha512-3r3Gpv5L4U7rBGpIqw5S1nun5MelfUMLRiScJsPRGZVTX3WY1w+zpaQKlWBi5yuHf5dMQ+ZUVbhb02IdrfJ2Fg==", + "dev": true, + "requires": { + "metro-source-map": "0.55.0", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" }, "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "metro-source-map": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.55.0.tgz", + "integrity": "sha512-HZODA0KPl5onJNGIztfTHHWurR2nL6Je/X8wwj+bL4ZBB/hSMVeDk7rWReCAvO3twVz7Ztp8Si0jfMmmH4Ruuw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "invariant": "^2.2.4", + "metro-symbolicate": "0.55.0", + "ob1": "0.55.0", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + } + }, + "vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", "dev": true } } }, - "modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true - }, - "moment": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", - "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" - }, - "moment-timezone": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.16.tgz", - "integrity": "sha512-4d1l92plNNqnMkqI/7boWNVXJvwGL2WyByl1Hxp3h/ao3HZiAqaoQY+6KBkYdiN5QtNDpndq+58ozl8W4GVoNw==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "dev": true + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "requires": { + "mime-db": "~1.33.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "moment": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz", + "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==" + }, + "moment-timezone": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.16.tgz", + "integrity": "sha512-4d1l92plNNqnMkqI/7boWNVXJvwGL2WyByl1Hxp3h/ao3HZiAqaoQY+6KBkYdiN5QtNDpndq+58ozl8W4GVoNw==", "requires": { "moment": ">= 2.9.0" } @@ -14992,6 +17173,30 @@ "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", "dev": true }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dev": true, + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "mousetrap": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.2.tgz", @@ -15868,6 +18073,12 @@ "boolbase": "~1.0.0" } }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -15885,6 +18096,12 @@ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true }, + "ob1": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.55.0.tgz", + "integrity": "sha512-pfyiMVsUItl8WiRKMT15eCi662pCRAuYTq2+V3UpE+PpFErJI/TvRh/M/l/9TaLlbFr7krJ7gdl+FXJNcybmvw==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -15943,6 +18160,12 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" }, + "object-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object-values/-/object-values-1.0.0.tgz", + "integrity": "sha1-cq+DljARnluYw7AruMJ+MjcVgQU=", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -16031,6 +18254,12 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -16049,6 +18278,15 @@ "mimic-fn": "^1.0.0" } }, + "open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, "opener": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", @@ -16102,6 +18340,12 @@ "wordwrap": "~1.0.0" } }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "dev": true + }, "ora": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ora/-/ora-2.1.0.tgz", @@ -16840,6 +19084,12 @@ "error-ex": "^1.2.0" } }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -17048,17 +19298,74 @@ "semver-compare": "^1.0.0" } }, - "plur": { + "plist": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.0.1.tgz", - "integrity": "sha512-lJl0ojUynAM1BZn58Pas2WT/TXeC1+bS+UqShl0x9+49AtOn7DixRXVzaC8qrDOIxNDmepKnLuMTH7NQmkX0PA==", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", + "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==", "dev": true, "requires": { - "irregular-plurals": "^2.0.0" + "base64-js": "^1.2.3", + "xmlbuilder": "^9.0.7", + "xmldom": "0.1.x" } }, - "pn": { - "version": "1.1.0", + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + } + } + }, + "plur": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/plur/-/plur-3.0.1.tgz", + "integrity": "sha512-lJl0ojUynAM1BZn58Pas2WT/TXeC1+bS+UqShl0x9+49AtOn7DixRXVzaC8qrDOIxNDmepKnLuMTH7NQmkX0PA==", + "dev": true, + "requires": { + "irregular-plurals": "^2.0.0" + } + }, + "pn": { + "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true @@ -18369,103 +20676,613 @@ } } }, - "re-resizable": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-5.0.1.tgz", - "integrity": "sha512-Iy8v5li7bhNBDxCN1DbA4l6G2Hk8NCZtcExoI1D+5pfvKyQcH8LH2P5h3DGoEfHhs0uyyRC1Qx8bHBomfrmxgA==", - "requires": { - "fast-memoize": "^2.5.1" - } - }, - "react": { - "version": "16.8.4", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.4.tgz", - "integrity": "sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.4" - } - }, - "react-addons-shallow-compare": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.2.tgz", - "integrity": "sha1-GYoAuR/DdiPbZKKP0XtZa6NicC8=", - "requires": { - "fbjs": "^0.8.4", - "object-assign": "^4.1.0" - } - }, - "react-autosize-textarea": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-3.0.2.tgz", - "integrity": "sha1-K2hApp9xOHGavOpaQp7PcwF2jAc=", - "requires": { - "autosize": "^4.0.0", - "line-height": "^0.3.1", - "prop-types": "^15.5.6" - } - }, - "react-click-outside": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-click-outside/-/react-click-outside-3.0.1.tgz", - "integrity": "sha512-d0KWFvBt+esoZUF15rL2UBB7jkeAqLU8L/Ny35oLK6fW6mIbOv/ChD+ExF4sR9PD26kVx+9hNfD0FTIqRZEyRQ==", - "requires": { - "hoist-non-react-statics": "^2.1.1" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - } - } - }, - "react-dates": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/react-dates/-/react-dates-17.2.0.tgz", - "integrity": "sha512-RDlerU8DdRRrlYS0MQ7Z9igPWABGLDwz6+ykBNff67RM3Sset2TDqeuOr+R5o00Ggn5U47GeLsGcSDxlZd9cHw==", - "requires": { - "airbnb-prop-types": "^2.10.0", - "consolidated-events": "^1.1.1 || ^2.0.0", - "is-touch-device": "^1.0.1", - "lodash": "^4.1.1", - "object.assign": "^4.1.0", - "object.values": "^1.0.4", - "prop-types": "^15.6.1", - "react-addons-shallow-compare": "^15.6.2", - "react-moment-proptypes": "^1.6.0", - "react-outside-click-handler": "^1.2.0", - "react-portal": "^4.1.5", - "react-with-styles": "^3.2.0", - "react-with-styles-interface-css": "^4.0.2" - } - }, - "react-dom": { - "version": "16.8.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.4.tgz", - "integrity": "sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.4" - } - }, - "react-is": { - "version": "16.8.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", - "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==" - }, - "react-moment-proptypes": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/react-moment-proptypes/-/react-moment-proptypes-1.6.0.tgz", - "integrity": "sha512-4h7EuhDMTzQqZ+02KUUO+AVA7PqhbD88yXB740nFpNDyDS/bj9jiPyn2rwr9sa8oDyaE1ByFN9+t5XPyPTmN6g==", - "requires": { - "moment": ">=1.6.0" - } - }, + "re-resizable": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-5.0.1.tgz", + "integrity": "sha512-Iy8v5li7bhNBDxCN1DbA4l6G2Hk8NCZtcExoI1D+5pfvKyQcH8LH2P5h3DGoEfHhs0uyyRC1Qx8bHBomfrmxgA==", + "requires": { + "fast-memoize": "^2.5.1" + } + }, + "react": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.4.tgz", + "integrity": "sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.4" + } + }, + "react-addons-shallow-compare": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.2.tgz", + "integrity": "sha1-GYoAuR/DdiPbZKKP0XtZa6NicC8=", + "requires": { + "fbjs": "^0.8.4", + "object-assign": "^4.1.0" + } + }, + "react-autosize-textarea": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-3.0.2.tgz", + "integrity": "sha1-K2hApp9xOHGavOpaQp7PcwF2jAc=", + "requires": { + "autosize": "^4.0.0", + "line-height": "^0.3.1", + "prop-types": "^15.5.6" + } + }, + "react-click-outside": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-click-outside/-/react-click-outside-3.0.1.tgz", + "integrity": "sha512-d0KWFvBt+esoZUF15rL2UBB7jkeAqLU8L/Ny35oLK6fW6mIbOv/ChD+ExF4sR9PD26kVx+9hNfD0FTIqRZEyRQ==", + "requires": { + "hoist-non-react-statics": "^2.1.1" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, + "react-dates": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/react-dates/-/react-dates-17.2.0.tgz", + "integrity": "sha512-RDlerU8DdRRrlYS0MQ7Z9igPWABGLDwz6+ykBNff67RM3Sset2TDqeuOr+R5o00Ggn5U47GeLsGcSDxlZd9cHw==", + "requires": { + "airbnb-prop-types": "^2.10.0", + "consolidated-events": "^1.1.1 || ^2.0.0", + "is-touch-device": "^1.0.1", + "lodash": "^4.1.1", + "object.assign": "^4.1.0", + "object.values": "^1.0.4", + "prop-types": "^15.6.1", + "react-addons-shallow-compare": "^15.6.2", + "react-moment-proptypes": "^1.6.0", + "react-outside-click-handler": "^1.2.0", + "react-portal": "^4.1.5", + "react-with-styles": "^3.2.0", + "react-with-styles-interface-css": "^4.0.2" + } + }, + "react-deep-force-update": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/react-deep-force-update/-/react-deep-force-update-1.1.2.tgz", + "integrity": "sha512-WUSQJ4P/wWcusaH+zZmbECOk7H5N2pOIl0vzheeornkIMhu+qrNdGFm0bDZLCb0hSF0jf/kH1SgkNGfBdTc4wA==", + "dev": true + }, + "react-devtools-core": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-3.6.1.tgz", + "integrity": "sha512-I/LSX+tpeTrGKaF1wXSfJ/kP+6iaP2JfshEjW8LtQBdz6c6HhzOJtjZXhqOUrAdysuey8M1/JgPY1flSVVt8Ig==", + "dev": true, + "requires": { + "shell-quote": "^1.6.1", + "ws": "^3.3.1" + }, + "dependencies": { + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } + } + }, + "react-dom": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.4.tgz", + "integrity": "sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.4" + } + }, + "react-is": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", + "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==" + }, + "react-moment-proptypes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/react-moment-proptypes/-/react-moment-proptypes-1.6.0.tgz", + "integrity": "sha512-4h7EuhDMTzQqZ+02KUUO+AVA7PqhbD88yXB740nFpNDyDS/bj9jiPyn2rwr9sa8oDyaE1ByFN9+t5XPyPTmN6g==", + "requires": { + "moment": ">=1.6.0" + } + }, + "react-native": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.60.0.tgz", + "integrity": "sha512-Leo1MfUpQFCLchr60HCDZAk7M6Bd2yPplSDBuCrC9gUtsRO2P4nLxwrX6P+vbjF7Td2sQbcGqW2E809Oi41K0g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "@react-native-community/cli": "^2.0.1", + "@react-native-community/cli-platform-android": "^2.0.1", + "@react-native-community/cli-platform-ios": "^2.0.1", + "abort-controller": "^3.0.0", + "art": "^0.10.0", + "base64-js": "^1.1.2", + "connect": "^3.6.5", + "create-react-class": "^15.6.3", + "escape-string-regexp": "^1.0.5", + "event-target-shim": "^5.0.1", + "fbjs": "^1.0.0", + "fbjs-scripts": "^1.1.0", + "invariant": "^2.2.4", + "jsc-android": "245459.0.0", + "metro-babel-register": "0.54.1", + "metro-react-native-babel-transformer": "0.54.1", + "nullthrows": "^1.1.0", + "pretty-format": "^24.7.0", + "promise": "^7.1.1", + "prop-types": "^15.7.2", + "react-devtools-core": "^3.6.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.14.0", + "stacktrace-parser": "^0.1.3", + "whatwg-fetch": "^3.0.0" + }, + "dependencies": { + "@react-native-community/cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-2.3.0.tgz", + "integrity": "sha512-Vf11SAW1ZwSfZ3JsYiEhU1/Oq9ozKyh7Uawbg8D4IrBM8dCHSpPGhONNfzSQ6x4k9YQjFHH+bOY274/fcYtNXQ==", + "dev": true, + "requires": { + "@hapi/joi": "^15.0.3", + "@react-native-community/cli-platform-android": "^2.1.1", + "@react-native-community/cli-platform-ios": "^2.2.0", + "@react-native-community/cli-tools": "^2.0.2", + "chalk": "^1.1.1", + "command-exists": "^1.2.8", + "commander": "^2.19.0", + "compression": "^1.7.1", + "connect": "^3.6.5", + "cosmiconfig": "^5.1.0", + "deepmerge": "^3.2.0", + "envinfo": "^7.1.0", + "errorhandler": "^1.5.0", + "execa": "^1.0.0", + "fs-extra": "^7.0.1", + "glob": "^7.1.1", + "graceful-fs": "^4.1.3", + "inquirer": "^3.0.6", + "lodash": "^4.17.5", + "metro": "^0.54.1", + "metro-config": "^0.54.1", + "metro-core": "^0.54.1", + "metro-react-native-babel-transformer": "^0.54.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "morgan": "^1.9.0", + "node-notifier": "^5.2.1", + "open": "^6.2.0", + "ora": "^3.4.0", + "plist": "^3.0.0", + "semver": "^5.0.3", + "serve-static": "^1.13.1", + "shell-quote": "1.6.1", + "ws": "^1.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "cli-spinners": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "dev": true + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "fbjs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz", + "integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==", + "dev": true, + "requires": { + "core-js": "^2.4.1", + "fbjs-css-vars": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "metro-react-native-babel-preset": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.54.1.tgz", + "integrity": "sha512-Hfr32+u5yYl3qhYQJU8NQ26g4kQlc3yFMg7keVR/3H8rwBIbFqXgsKt8oe0dOrv7WvrMqBHhDtVdU9ls3sSq8g==", + "dev": true, + "requires": { + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.0.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.2.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.0.0", + "@babel/plugin-transform-exponentiation-operator": "^7.0.0", + "@babel/plugin-transform-flow-strip-types": "^7.0.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-object-assign": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-template-literals": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.0.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "metro-babel7-plugin-react-transform": "0.54.1", + "react-transform-hmr": "^1.0.4" + } + }, + "metro-react-native-babel-transformer": { + "version": "0.54.1", + "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.54.1.tgz", + "integrity": "sha512-ECw7xG91t8dk/PHdiyoC5SP1s9OQzfmJzG5m0YOZaKtHMe534qTDbncxaKfTI3CP99yti2maXFBRVj+xyvph/g==", + "dev": true, + "requires": { + "@babel/core": "^7.0.0", + "babel-preset-fbjs": "^3.1.2", + "metro-babel-transformer": "0.54.1", + "metro-react-native-babel-preset": "0.54.1" + } + }, + "ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "scheduler": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.14.0.tgz", + "integrity": "sha512-9CgbS06Kki2f4R9FjLSITjZo5BZxPsryiRNyL3LpvrM9WxcVmhlqAOc9E+KQbeI2nqej4JIIbOsfdL51cNb4Iw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==", + "dev": true + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dev": true, + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + } + } + }, "react-outside-click-handler": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/react-outside-click-handler/-/react-outside-click-handler-1.2.2.tgz", @@ -18485,6 +21302,22 @@ "prop-types": "^15.5.8" } }, + "react-proxy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.8.tgz", + "integrity": "sha1-nb/Z2SdSjDqp9ETkVYw3gwq4wmo=", + "dev": true, + "requires": { + "lodash": "^4.6.1", + "react-deep-force-update": "^1.0.0" + } + }, + "react-refresh": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.2.0.tgz", + "integrity": "sha512-ITw8t/HOFNose2yf1y9pPFSSeB9ISOq2JdHpuZvj/Qb+iSsLml8GkkHdDlURzieO7B3dFDtMrrneZLl3N5z/hg==", + "dev": true + }, "react-spring": { "version": "8.0.20", "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.20.tgz", @@ -18506,6 +21339,16 @@ "scheduler": "^0.13.4" } }, + "react-transform-hmr": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz", + "integrity": "sha1-4aQL0Krvxy6N/Xp82gmvhQZjl7s=", + "dev": true, + "requires": { + "global": "^4.3.0", + "react-proxy": "^1.1.7" + } + }, "react-with-direction": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.3.0.tgz", @@ -19354,6 +22197,21 @@ "integrity": "sha1-FPlQpCF9fjXapxu8vljv9o6ksrc=", "dev": true }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "*" + } + }, "rxjs": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", @@ -19730,6 +22588,12 @@ } } }, + "serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=", + "dev": true + }, "serialize-javascript": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", @@ -19863,6 +22727,26 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + }, + "dependencies": { + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + } + } + }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -19919,6 +22803,17 @@ "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.5.7.tgz", "integrity": "sha512-APW9iYbkJ5cijjX4Ljhf3VG8SwYPUJT5gZrwci/wieMabQxWFiV5VwsrP5c6GMRvXKEQMGkAB1d9dvW66dTqpg==" }, + "simple-plist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.0.0.tgz", + "integrity": "sha512-043L2rO80LVF7zfZ+fqhsEkoJFvW8o59rt/l4ctx1TJWoTx7/jkiS1R5TatD15Z1oYnuLJytzE7gcnnBuIPL2g==", + "dev": true, + "requires": { + "bplist-creator": "0.0.7", + "bplist-parser": "0.1.1", + "plist": "^3.0.1" + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -20304,6 +23199,15 @@ "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=", "dev": true }, + "stacktrace-parser": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.6.tgz", + "integrity": "sha512-wXhu0Z8YgCGigUtHQq+J7pjXCppk3Um5DwH4qskOKHMlJmKwuuUSm+wDAgU7t4sbVjvuDTNGwOfFKgjMEqSflA==", + "dev": true, + "requires": { + "type-fest": "^0.3.0" + } + }, "staged-git-files": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.1.2.tgz", @@ -20427,6 +23331,12 @@ "readable-stream": "^2.0.2" } }, + "stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=", + "dev": true + }, "stream-each": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", @@ -21177,6 +24087,24 @@ "inherits": "2" } }, + "temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", + "dev": true, + "requires": { + "os-tmpdir": "^1.0.0", + "rimraf": "~2.2.6" + }, + "dependencies": { + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, "temp-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", @@ -21424,6 +24352,12 @@ "xtend": "~4.0.1" } }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -21675,6 +24609,12 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -21762,6 +24702,12 @@ "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", "dev": true }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", + "dev": true + }, "umask": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", @@ -22986,12 +25932,63 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, + "xcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-2.0.0.tgz", + "integrity": "sha512-5xF6RCjAdDEiEsbbZaS/gBRt3jZ/177otZcpoLCjGN/u1LrfgH7/Sgeeavpr/jELpyDqN2im3AKosl2G2W8hfw==", + "dev": true, + "requires": { + "simple-plist": "^1.0.0", + "uuid": "^3.3.2" + } + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "xmldoc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-0.4.0.tgz", + "integrity": "sha1-0lciS+g5PqrL+DfvIn/Y7CWzaIg=", + "dev": true, + "requires": { + "sax": "~1.1.1" + }, + "dependencies": { + "sax": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz", + "integrity": "sha1-XWFr6KXmB9VOEUr65Vt+ry/MMkA=", + "dev": true + } + } + }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, + "xpipe": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/xpipe/-/xpipe-1.0.5.tgz", + "integrity": "sha1-jdi/Rfw/f1Xw4FS4ePQ6YmFNr98=", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index dff6817d315a51..06741f44c1a783 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,8 @@ "@wordpress/postcss-themes": "file:packages/postcss-themes", "@wordpress/scripts": "file:packages/scripts", "babel-plugin-inline-json-import": "0.3.2", + "babel-plugin-react-native-classname-to-style": "1.2.2", + "babel-plugin-react-native-platform-specific-extensions": "1.1.1", "benchmark": "2.1.4", "browserslist": "4.6.2", "chalk": "2.4.1", @@ -108,11 +110,15 @@ "husky": "0.14.3", "inquirer": "6.3.1", "is-equal-shallow": "0.1.3", + "jest-junit": "6.4.0", + "jest-serializer-enzyme": "1.0.0", "jsdom": "11.12.0", "lerna": "3.14.1", "lint-staged": "8.1.5", "lodash": "4.17.11", "make-dir": "3.0.0", + "metro-react-native-babel-preset": "0.55.0", + "metro-react-native-babel-transformer": "0.55.0", "mkdirp": "0.5.1", "node-sass": "4.12.0", "node-watch": "0.6.0", @@ -123,6 +129,7 @@ "progress": "2.0.3", "react": "16.8.4", "react-dom": "16.8.4", + "react-native": "0.60.0", "react-test-renderer": "16.8.4", "redux": "4.0.0", "rimraf": "2.6.2", @@ -210,6 +217,8 @@ "test-unit:watch": "npm run test-unit -- --watch", "test-unit-php": "docker-compose run --rm wordpress_phpunit phpunit", "test-unit-php-multisite": "docker-compose run -e WP_MULTISITE=1 --rm wordpress_phpunit phpunit", + "test-unit:native": "cd test/native/ && cross-env NODE_ENV=test jest --config ./jest.config.js", + "test-unit:native:debug": "cd test/native/ && node --inspect ../../node_modules/.bin/jest --runInBand --config ./jest.config.js", "playground:build": "npm run build:packages && parcel build playground/src/index.html -d playground/dist", "playground:start": "concurrently \"npm run dev:packages\" \"parcel playground/src/index.html -d playground/dist\"" }, diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js index 72a2015277e4df..6eca7575ac408b 100644 --- a/packages/block-editor/src/components/media-upload/test/index.native.js +++ b/packages/block-editor/src/components/media-upload/test/index.native.js @@ -23,14 +23,6 @@ import { OPTION_TAKE_PHOTO, } from '../index'; -jest.mock( 'react-native-gutenberg-bridge', () => ( - { - requestMediaPickFromMediaLibrary: jest.fn(), - requestMediaPickFromDeviceLibrary: jest.fn(), - requestMediaPickFromDeviceCamera: jest.fn(), - } -) ); - const MEDIA_URL = 'http://host.media.type'; const MEDIA_ID = 123; diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index fce8b23299b563..aee6c0fe505860 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -1,13 +1,16 @@ /** * External dependencies */ -import renderer from 'react-test-renderer'; import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; - +import { mount } from 'enzyme'; /** * WordPress dependencies */ import { registerCoreBlocks } from '@wordpress/block-library'; +// Force register 'core/editor' store. +import { store } from '@wordpress/editor'; // eslint-disable-line no-unused-vars + +jest.mock( '../components/layout', () => () => 'Layout' ); /** * Internal dependencies @@ -37,7 +40,7 @@ describe( 'Editor', () => { // Utilities const renderEditorWith = ( content ) => { - return renderer.create( + return mount( <Editor initialHtml={ content } initialHtmlModeEnabled={ false } diff --git a/test/native/__mocks__/react-native-aztec/index.js b/test/native/__mocks__/react-native-aztec/index.js new file mode 100644 index 00000000000000..6c4c0f0d1353b2 --- /dev/null +++ b/test/native/__mocks__/react-native-aztec/index.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import React from 'react'; + +class AztecView extends React.Component { + blur = () => { + } + + focus = () => { + } + + isFocused = () => { + return false; + } + + render() { + return ( <></> ); + } +} + +export default AztecView; diff --git a/test/native/__mocks__/react-native-gutenberg-bridge/index.js b/test/native/__mocks__/react-native-gutenberg-bridge/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/react-native-hr/index.js b/test/native/__mocks__/react-native-hr/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/react-native-modal/index.js b/test/native/__mocks__/react-native-modal/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/react-native-recyclerview-list/index.js b/test/native/__mocks__/react-native-recyclerview-list/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/react-native-safe-area/index.js b/test/native/__mocks__/react-native-safe-area/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/react-native-svg/index.js b/test/native/__mocks__/react-native-svg/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/react-native-video/index.js b/test/native/__mocks__/react-native-video/index.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js new file mode 100644 index 00000000000000..67e15440ddff34 --- /dev/null +++ b/test/native/__mocks__/styleMock.js @@ -0,0 +1,73 @@ +/** @flow + * @format */ + +module.exports = { + + //Mock block paragraph style with minimum height + blockText: { + minHeight: 50, + }, + container: { + height: 44, + }, + toolbar: { + height: 44, + }, + blockContainerFocused: { + paddingBottom: 0, + }, + blockCode: { + fontFamily: 'serif', + }, + 'rich-text': { + fontFamily: 'serif', + minHeight: 30, + }, + 'rich-text-placeholder': { + color: 'gray', + }, + 'block-editor-plain-text': { + fontFamily: 'serif', + }, + addBlockButton: { + color: '', + }, + blockHolderFocused: { + borderColor: 'gray', + }, + 'wp-block-heading': { + minHeight: 60, + }, + 'editor-rich-text': { + minHeight: 50, + }, + 'editor-plain-text': { + fontFamily: 'serif', + }, + background: { + maxWidth: 512, + }, + modalItem: { + paddingLeft: 8, + paddingRight: 8, + }, + content: { + paddingLeft: 8, + paddingRight: 8, + }, + modalIconWrapper: { + width: 104, + }, + modalIcon: { + fill: 'gray', + }, + icon: { + fill: 'gray', + }, + iconRetry: { + fill: 'gray', + }, + iconUploading: { + fill: 'gray', + }, +}; diff --git a/test/native/babel.config.js b/test/native/babel.config.js new file mode 100644 index 00000000000000..7fa61e90babc8f --- /dev/null +++ b/test/native/babel.config.js @@ -0,0 +1,57 @@ +module.exports = function( api ) { + api.cache( true ); + return { + presets: [ + 'module:metro-react-native-babel-preset', + ], + plugins: [ + '@babel/plugin-proposal-async-generator-functions', + '@babel/plugin-transform-runtime', + [ + 'react-native-platform-specific-extensions', + { + extensions: [ + 'css', + 'scss', + 'sass', + ], + }, + ], + ], + overrides: [ + { // Transforms JSX into JS function calls and use `createElement` instead of the default `React.createElement` + plugins: [ + [ + '@babel/plugin-transform-react-jsx', + { + pragma: 'createElement', + pragmaFrag: 'Fragment', + }, + ], + ], + exclude: /node_modules\/react-native/, + }, + { // Auto-add `import { createElement } from '@wordpress/element';` when JSX is found + plugins: [ + [ + '../../packages/babel-plugin-import-jsx-pragma', + { + scopeVariable: 'createElement', + scopeVariableFrag: 'Fragment', + source: '@wordpress/element', + isDefault: false, + }, + ], + ], + exclude: /node_modules\/react-native/, + }, + ], + env: { + development: { + plugins: [ + '@babel/transform-react-jsx-source', + ], + }, + }, + }; +}; diff --git a/test/native/enzyme.config.js b/test/native/enzyme.config.js new file mode 100644 index 00000000000000..da4dd944515db6 --- /dev/null +++ b/test/native/enzyme.config.js @@ -0,0 +1,6 @@ +/** + * External dependencies + */ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +configure( { adapter: new Adapter() } ); diff --git a/test/native/jest.config.js b/test/native/jest.config.js new file mode 100644 index 00000000000000..58a6f324cd6c62 --- /dev/null +++ b/test/native/jest.config.js @@ -0,0 +1,73 @@ +/** @flow + * @format */ + +/** + * External dependencies + */ +const glob = require( 'glob' ).sync; + +const defaultPlatform = 'android'; +const rnPlatform = process.env.TEST_RN_PLATFORM || defaultPlatform; +if ( process.env.TEST_RN_PLATFORM ) { + // eslint-disable-next-line no-console + console.log( 'Setting RN platform to: ' + process.env.TEST_RN_PLATFORM ); +} else { + // eslint-disable-next-line no-console + console.log( 'Setting RN platform to: default (' + defaultPlatform + ')' ); +} + +const configPath = 'test/native'; + +const transpiledPackageNames = glob( '../../packages/*/src/index.js' ) + .map( ( fileName ) => fileName.split( '/' )[ 3 ] ); + +module.exports = { + verbose: true, + rootDir: '../../', + // Automatically clear mock calls and instances between every test + clearMocks: true, + preset: 'react-native', + setupFiles: [ + '<rootDir>/' + configPath + '/setup.js', + '<rootDir>/' + configPath + '/enzyme.config.js', + ], + testEnvironment: 'jsdom', + testMatch: [ + '**/test/*.native.[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/node_modules/', + '/__device-tests__/', + ], + testURL: 'http://localhost/', + moduleDirectories: [ 'node_modules' ], + moduleNameMapper: { + // Mock the CSS modules. See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets + '\\.(scss)$': '<rootDir>/' + configPath + '/__mocks__/styleMock.js', + [ `@wordpress\\/(${ transpiledPackageNames.join( '|' ) })$` ]: '<rootDir>/packages/$1/src', + }, + haste: { + defaultPlatform: rnPlatform, + platforms: [ + 'android', + 'ios', + 'native', + ], + hasteImplModulePath: '<rootDir>/node_modules/react-native/jest/hasteImpl.js', + providesModuleNodeModules: [ + 'react-native', + 'react-native-svg', + ], + }, + transformIgnorePatterns: [ + // This is required for now to have jest transform some of our modules + // See: https://github.com/wordpress-mobile/gutenberg-mobile/pull/257#discussion_r234978268 + // There is no overloading in jest so we need to rewrite the config from react-native-jest-preset: + // https://github.com/facebook/react-native/blob/master/jest-preset.json#L20 + 'node_modules/(?!(simple-html-tokenizer|(jest-)?react-native|react-clone-referenced-element))', + ], + snapshotSerializers: [ + 'enzyme-to-json/serializer', + ], + reporters: [ 'default', 'jest-junit' ], +}; diff --git a/test/native/setup.js b/test/native/setup.js new file mode 100644 index 00000000000000..cfd2417a11ad9d --- /dev/null +++ b/test/native/setup.js @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import { NativeModules } from 'react-native'; + +jest.mock( 'react-native-gutenberg-bridge', () => { + return { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + subscribeParentGetHtml: jest.fn(), + subscribeParentToggleHTMLMode: jest.fn(), + subscribeSetTitle: jest.fn(), + subscribeSetFocusOnTitle: jest.fn(), + subscribeUpdateHtml: jest.fn(), + subscribeMediaAppend: jest.fn(), + editorDidMount: jest.fn(), + subscribeMediaUpload: jest.fn(), + requestMediaPickFromMediaLibrary: jest.fn(), + requestMediaPickFromDeviceLibrary: jest.fn(), + requestMediaPickFromDeviceCamera: jest.fn(), + }; +} ); + +jest.mock( 'react-native-modal', () => () => 'Modal' ); + +jest.mock( 'react-native-hr', () => () => 'Hr' ); + +jest.mock( 'react-native-svg', () => { + return { + Svg: () => 'Svg', + Path: () => 'Path', + Circle: () => 'Circle', + G: () => 'G', + Polygon: () => 'Polygon', + Rect: () => 'Rect', + }; +} ); + +jest.mock( 'react-native-safe-area', () => { + const addEventListener = jest.fn(); + addEventListener.mockReturnValue( { remove: () => {} } ); + return { + getSafeAreaInsetsForRootView: () => { + return new Promise( ( accept ) => { + accept( { safeAreaInsets: { bottom: 34 } } ); + } ); + }, + addEventListener, + removeEventListener: jest.fn(), + }; +} ); + +jest.mock( 'react-native-recyclerview-list' ); + +if ( ! global.window.matchMedia ) { + global.window.matchMedia = () => ( { + matches: false, + addListener: () => {}, + removeListener: () => {}, + } ); +} + +// Overwrite some native module mocks from `react-native` jest preset: +// https://github.com/facebook/react-native/blob/master/jest/setup.js +// to fix issue "TypeError: Cannot read property 'Commands' of undefined" +// raised when calling focus or blur on a native component +const mockNativeModules = { + UIManager: { + ...NativeModules.UIManager, + getViewManagerConfig: jest.fn( () => ( { Commands: {} } ) ), + }, +}; + +Object.keys( mockNativeModules ).forEach( ( module ) => { + try { + jest.doMock( module, () => mockNativeModules[ module ] ); // needed by FacebookSDK-test + } catch ( error ) { + jest.doMock( module, () => mockNativeModules[ module ], { virtual: true } ); + } +} ); From 1e9225a7dca5b1e5d3430e35c9231a5438e6c50d Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Thu, 11 Jul 2019 08:42:35 -0700 Subject: [PATCH 483/664] Match the primary button disabled state to Core's color contrast (#16103) * See #15280. This fixes the primary button's disabled view to match core primary disabled buttons. * Removed unecessary opacity attribute in css. * Revising tint and shade Sass values to get closer to Core. * Minor CSS edit to remove text shadow from disabled buttons. * Added active state to disabled state to remove active change of color. * Fixed the focus state of disabled primary button to keep the disabled text color. * A few minor CSS tweaks to fix the border inset shadow. * Move disabled focus styles alongside other disabled rules. * Ensure disabled state persists for alternate color schemes. --- packages/components/src/button/style.scss | 29 ++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index ba13650e492dab..27f3de079631a0 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -101,12 +101,30 @@ } &:disabled, - &[aria-disabled="true"] { - color: color(theme(button) tint(30%)); - background: color(theme(button) shade(30%)); - border-color: color(theme(button) shade(20%)); + &:disabled:active:enabled, + &[aria-disabled="true"], + &[aria-disabled="true"]:active:enabled { + color: color(theme(button) tint(40%)); + background: color(theme(button)); + border-color: color(theme(button) shade(7%)); box-shadow: none; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.1); + text-shadow: none; + + // This specificity is needed to override alternate color schemes in WP-Admin. + &.is-button, + &.is-button:hover, + &:active:enabled { + box-shadow: none; + text-shadow: none; + } + + &:focus:enabled { + color: color(theme(button) tint(40%)); + border-color: color(theme(button) shade(7%)); + box-shadow: + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; + } } &.is-busy, @@ -171,7 +189,6 @@ &:disabled, &[aria-disabled="true"] { cursor: default; - opacity: 0.3; } &:focus:not(:disabled) { From cb718ffdff57e35f05f4239ff03ceb476ae69a86 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Thu, 11 Jul 2019 17:33:29 -0500 Subject: [PATCH 484/664] Blocks: Include pre-filter settings in block type registration deprecation filter applications. (#16518) * Blocks: Include pre-filter settings in block type registration deprecation filter applications. * Blocks: Test new deprecation filter logic. * Blocks: Add JSDoc to new deprecation entry keys constant. * Blocks: Shallow copy settings passed to block type registration filter to avoid mutations in subsequent calls for deprecations. * Blocks: Add inline comments for new deprecation filter logic. * Blocks: Update tests for new deprecation filter logic to use new default block settings. --- packages/blocks/src/api/constants.js | 12 +++++++ packages/blocks/src/api/parser.js | 3 +- packages/blocks/src/api/registration.js | 27 ++++++++++++++-- packages/blocks/src/api/test/registration.js | 34 +++++++++++++++++--- 4 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 packages/blocks/src/api/constants.js diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js new file mode 100644 index 00000000000000..9207e7392f2958 --- /dev/null +++ b/packages/blocks/src/api/constants.js @@ -0,0 +1,12 @@ +/** + * Array of valid keys in a block type settings deprecation object. + * + * @type {string[]} + */ +export const DEPRECATED_ENTRY_KEYS = [ + 'attributes', + 'supports', + 'save', + 'migrate', + 'isEligible', +]; diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index cb3a35d5fffe5b..0e67b0219142d1 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -24,6 +24,7 @@ import { isValidBlockContent } from './validation'; import { getCommentDelimitedContent } from './serializer'; import { attr, html, text, query, node, children, prop } from './matchers'; import { normalizeBlockType } from './utils'; +import { DEPRECATED_ENTRY_KEYS } from './constants'; /** * Sources which are guaranteed to return a string value. @@ -324,7 +325,7 @@ export function getMigratedBlock( block, parsedAttributes ) { // parsing are not considered in the deprecated block type by default, // and must be explicitly provided. const deprecatedBlockType = Object.assign( - omit( blockType, [ 'attributes', 'save', 'supports' ] ), + omit( blockType, DEPRECATED_ENTRY_KEYS ), deprecatedDefinitions[ i ] ); diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 498f83627e6fff..b88eec96ecac7e 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -5,6 +5,8 @@ */ import { get, + omit, + pick, isFunction, isPlainObject, some, @@ -20,6 +22,7 @@ import { select, dispatch } from '@wordpress/data'; * Internal dependencies */ import { isValidIcon, normalizeIconObject } from './utils'; +import { DEPRECATED_ENTRY_KEYS } from './constants'; /** * Render behavior of a block type icon; one of a Dashicon slug, an element, @@ -81,14 +84,14 @@ import { isValidIcon, normalizeIconObject } from './utils'; * * @type {Object} */ -const DEFAULT_BLOCK_TYPE_SETTINGS = { +export const DEFAULT_BLOCK_TYPE_SETTINGS = { icon: 'block-default', attributes: {}, keywords: [], save: () => null, }; -let serverSideBlockDefinitions = {}; +export let serverSideBlockDefinitions = {}; /** * Sets the server side block definition of blocks. @@ -140,10 +143,28 @@ export function registerBlockType( name, settings ) { return; } + const preFilterSettings = { ...settings }; settings = applyFilters( 'blocks.registerBlockType', settings, name ); if ( settings.deprecated ) { - settings.deprecated = settings.deprecated.map( ( deprecation ) => applyFilters( 'blocks.registerBlockType', deprecation, name ) ); + settings.deprecated = settings.deprecated.map( ( deprecation ) => + pick( // Only keep valid deprecation keys. + applyFilters( + 'blocks.registerBlockType', + // Merge deprecation keys with pre-filter settings + // so that filters that depend on specific keys being + // present don't fail. + { + // Omit deprecation keys here so that deprecations + // can opt out of specific keys like "supports". + ...omit( preFilterSettings, DEPRECATED_ENTRY_KEYS ), + ...deprecation, + }, + name + ), + DEPRECATED_ENTRY_KEYS + ) + ); } if ( ! isPlainObject( settings ) ) { diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 30df36c9359d4c..9be18c0b7ffe5d 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -3,7 +3,7 @@ /** * External dependencies */ -import { noop } from 'lodash'; +import { noop, get, omit, pick } from 'lodash'; /** * WordPress dependencies @@ -29,8 +29,11 @@ import { getBlockSupport, hasBlockSupport, isReusableBlock, + serverSideBlockDefinitions, unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase + DEFAULT_BLOCK_TYPE_SETTINGS, } from '../registration'; +import { DEPRECATED_ENTRY_KEYS } from '../constants'; describe( 'blocks', () => { const defaultBlockSettings = { save: noop, category: 'common', title: 'block title' }; @@ -373,6 +376,7 @@ describe( 'blocks', () => { } ); it( 'should apply the blocks.registerBlockType filter to each of the deprecated settings as well as the main block settings', () => { + const name = 'my-plugin/fancy-block-13'; const blockSettingsWithDeprecations = { ...defaultBlockSettings, deprecated: [ @@ -389,7 +393,26 @@ describe( 'blocks', () => { ], }; + let i = 0; addFilter( 'blocks.registerBlockType', 'core/blocks/without-title', ( settings ) => { + // Verify that for deprecations, the filter is called with a merge of pre-filter + // settings with deprecation keys omitted and the deprecation entry. + if ( i > 0 ) { + expect( settings ).toEqual( { + ...omit( + { + name, + ...DEFAULT_BLOCK_TYPE_SETTINGS, + ...get( serverSideBlockDefinitions, name ), + ...blockSettingsWithDeprecations, + }, + DEPRECATED_ENTRY_KEYS + ), + ...blockSettingsWithDeprecations.deprecated[ i - 1 ], + } ); + } + i++; + return { ...settings, attributes: { @@ -401,11 +424,14 @@ describe( 'blocks', () => { }; } ); - const block = registerBlockType( 'my-plugin/fancy-block-13', blockSettingsWithDeprecations ); + const block = registerBlockType( name, blockSettingsWithDeprecations ); expect( block.attributes.id ).toEqual( { type: 'string' } ); - expect( block.deprecated[ 0 ].attributes.id ).toEqual( { type: 'string' } ); - expect( block.deprecated[ 1 ].attributes.id ).toEqual( { type: 'string' } ); + block.deprecated.forEach( ( deprecation ) => { + expect( deprecation.attributes.id ).toEqual( { type: 'string' } ); + // Verify that the deprecation's keys are a subset of deprecation keys. + expect( deprecation ).toEqual( pick( deprecation, DEPRECATED_ENTRY_KEYS ) ); + } ); } ); } ); } ); From 86c1562728819a79f54ab5fdfb0f7df4e1555ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Fri, 12 Jul 2019 09:00:03 +0200 Subject: [PATCH 485/664] Docs: Add title to example (#16528) --- packages/edit-post/README.md | 3 ++- .../components/sidebar/plugin-document-setting-panel/index.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index bf6092367aa36f..99ddf7a5b80a8b 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -118,6 +118,7 @@ function MyDocumentSettingPlugin() { PluginDocumentSettingPanel, { className: 'my-document-setting-plugin', + title: 'My Panel', }, __( 'My Document Setting Panel' ) ); @@ -134,7 +135,7 @@ const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost; const MyDocumentSettingTest = () => ( - <PluginDocumentSettingPanel className="my-document-setting-plugin"> + <PluginDocumentSettingPanel className="my-document-setting-plugin" title="My Panel"> <p>My Document Setting Panel</p> </PluginDocumentSettingPanel> ); diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js index d06b4134b45593..16ca9af32466b4 100644 --- a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js @@ -53,6 +53,7 @@ const PluginDocumentSettingFill = ( { isEnabled, opened, onToggle, className, ti * PluginDocumentSettingPanel, * { * className: 'my-document-setting-plugin', + * title: 'My Panel', * }, * __( 'My Document Setting Panel' ) * ); @@ -70,7 +71,7 @@ const PluginDocumentSettingFill = ( { isEnabled, opened, onToggle, className, ti * const { PluginDocumentSettingPanel } = wp.editPost; * * const MyDocumentSettingTest = () => ( - * <PluginDocumentSettingPanel className="my-document-setting-plugin"> + * <PluginDocumentSettingPanel className="my-document-setting-plugin" title="My Panel"> * <p>My Document Setting Panel</p> * </PluginDocumentSettingPanel> * ); From 9bea4a911222395cd04d449d9f2ca8f6e955b159 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Fri, 12 Jul 2019 05:01:56 -0500 Subject: [PATCH 486/664] Edit Widgets: Don't run Customizer setup on every preview refresh. (#16544) --- .../sync-customizer.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js index 724c3e5ea463d0..e3d2919f0c6de6 100644 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js @@ -78,9 +78,19 @@ const updateSettingInputValue = throttle( ( nextWidgetAreas ) => { // Check that all the necessary globals are present. if ( window.wp && window.wp.customize && window.wp.data ) { + let ran = false; // Wait for the Customizer to finish bootstrapping. window.wp.customize.bind( 'ready', () => window.wp.customize.previewer.bind( 'ready', () => { + // The Customizer will call this on every preview refresh, + // but we only want to run it once to avoid running another + // store setup that would set changeset edits and save + // widget blocks unintentionally. + if ( ran ) { + return; + } + ran = true; + // Try to parse a previous changeset from the hidden input. let widgetAreas; try { From 12103cd48ff41cde0960ff2c98ce8ed996e3c8b6 Mon Sep 17 00:00:00 2001 From: Jorge <jorge.costa@developer.pt> Date: Fri, 12 Jul 2019 12:30:59 +0100 Subject: [PATCH 487/664] Bump plugin version to 6.1.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 86c57cc651359e..64b6a24f8c9cab 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.1.0 + * Version: 6.1.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index c3743ee2db665c..69033fee71db56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.1.0", + "version": "6.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 06741f44c1a783..988184ac5a1baf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.1.0", + "version": "6.1.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From d417aa5b3b7a5a881266900649dba796eadfff66 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 12 Jul 2019 10:59:24 -0400 Subject: [PATCH 488/664] Build Tooling: Avoid Docker container automatic restart (#16547) --- docker-compose.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f7733df2c71b6b..723578cce7465f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,6 @@ services: wordpress: image: wordpress - restart: always ports: - 8888:80 environment: @@ -24,7 +23,6 @@ services: cli: image: wordpress:cli - restart: always user: xfs volumes: - wordpress_data:/var/www/html @@ -35,14 +33,12 @@ services: mysql: image: mysql:5.7 - restart: always environment: MYSQL_ROOT_PASSWORD: example MYSQL_DATABASE: wordpress_test wordpress_phpunit: image: chriszarate/wordpress-phpunit - restart: always environment: PHPUNIT_DB_HOST: mysql volumes: @@ -53,13 +49,11 @@ services: composer: image: composer - restart: always volumes: - .:/app wordpress_e2e_tests: image: wordpress - restart: always ports: - 8889:80 environment: @@ -80,7 +74,6 @@ services: cli_e2e_tests: image: wordpress:cli - restart: always user: xfs volumes: - wordpress_e2e_tests_data:/var/www/html From 8c77742fa1980a7a35a1d5f0b334f0cbd6d21158 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Fri, 12 Jul 2019 11:50:24 -0400 Subject: [PATCH 489/664] Try improving and standardizing the block styles focus + active states (#16545) * Improve and align focus styles. * Add padding to prevent focus style clipping in the block list. --- assets/stylesheets/_mixins.scss | 22 +++++++++++++++++-- .../src/components/block-styles/style.scss | 17 ++++++++------ .../components/block-types-list/style.scss | 2 +- .../components/inserter-list-item/style.scss | 11 ++++++++-- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index 5e92ec128eaaf5..b9b198631e895c 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -238,15 +238,33 @@ color: $dark-gray-900; } -@mixin block-style__focus-active() { +@mixin block-style__focus() { color: $dark-gray-900; - box-shadow: 0 0 0 2px $blue-medium-500; + box-shadow: 0 0 0 1px $white, 0 0 0 3px $blue-medium-500; // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; outline-offset: -2px; } +@mixin block-style__is-active() { + color: $dark-gray-900; + box-shadow: inset 0 0 0 2px $dark-gray-500; + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + outline-offset: -2px; +} + +@mixin block-style__is-active-focus() { + color: $dark-gray-900; + box-shadow: 0 0 0 1px $white, 0 0 0 3px $blue-medium-500, inset 0 0 0 2px $dark-gray-500; + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 4px solid transparent; + outline-offset: -4px; +} + /** * Applies editor left position to the selector passed as argument */ diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index d818bcb15349b3..988e7563878fc1 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -11,20 +11,23 @@ cursor: pointer; overflow: hidden; border-radius: $radius-round-rectangle; - padding: $grid-size-small; - - &.is-active { - @include block-style__focus-active(); - box-shadow: 0 0 0 2px $dark-gray-500; - } + padding: $grid-size-small * 1.5; &:focus { - @include block-style__focus-active(); + @include block-style__focus(); } &:hover { @include block-style__hover; } + + &.is-active { + @include block-style__is-active(); + + &:focus { + @include block-style__is-active-focus(); + } + } } .block-editor-block-styles__item-preview { diff --git a/packages/block-editor/src/components/block-types-list/style.scss b/packages/block-editor/src/components/block-types-list/style.scss index 73872e11ed5e0c..33c6db039f1d9f 100644 --- a/packages/block-editor/src/components/block-types-list/style.scss +++ b/packages/block-editor/src/components/block-types-list/style.scss @@ -1,6 +1,6 @@ .block-editor-block-types-list { list-style: none; - padding: 2px 0; + padding: 4px 0; overflow: hidden; display: flex; flex-wrap: wrap; diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index d9051da5c839aa..5725edf2f67b72 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -51,19 +51,26 @@ } &:active, - &.is-active, &:focus { position: relative; // Show the focus style in the icon inside instead. outline: none; - @include block-style__focus-active(); + @include block-style__focus(); .block-editor-block-types-list__item-icon, .block-editor-block-types-list__item-title { color: inherit; } } + + &.is-active { + @include block-style__is-active(); + + &:focus { + @include block-style__is-active-focus(); + } + } } } From 69bc538b1544f18753ad3b8bb8a9b3eddaeafb59 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 12 Jul 2019 17:21:47 +0100 Subject: [PATCH 490/664] chore: Remove duplicate switch cases in packages/block-editor/src/store/reducer.js (#16563) --- packages/block-editor/src/store/reducer.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 8df16d7bda9eb8..97a366e8884d2f 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -587,6 +587,7 @@ export const blocks = flow( return getFlattenedBlocksWithoutAttributes( action.blocks ); case 'RECEIVE_BLOCKS': + case 'INSERT_BLOCKS': return { ...state, ...getFlattenedBlocksWithoutAttributes( action.blocks ), @@ -612,12 +613,6 @@ export const blocks = flow( }, }; - case 'INSERT_BLOCKS': - return { - ...state, - ...getFlattenedBlocksWithoutAttributes( action.blocks ), - }; - case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': if ( ! action.blocks ) { return state; @@ -641,6 +636,7 @@ export const blocks = flow( return getFlattenedBlockAttributes( action.blocks ); case 'RECEIVE_BLOCKS': + case 'INSERT_BLOCKS': return { ...state, ...getFlattenedBlockAttributes( action.blocks ), @@ -688,12 +684,6 @@ export const blocks = flow( [ action.clientId ]: nextAttributes, }; - case 'INSERT_BLOCKS': - return { - ...state, - ...getFlattenedBlockAttributes( action.blocks ), - }; - case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': if ( ! action.blocks ) { return state; From 174b8f2dabb07b9a1df90973684d26c6d23059f8 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 12 Jul 2019 15:07:26 -0400 Subject: [PATCH 491/664] Framework: Bump Lodash dependencies to 4.17.14 (#16567) * Framework: Refresh package-lock.json by latest NPM See: https://github.com/WordPress/gutenberg/pull/16404/files#r303057081 * Framework: Bump Lodash to 4.17.4 for all packages * Framework: Pin vulnerable dependencies to updated minors --- lib/client-assets.php | 7 + package-lock.json | 189 ++++++++++-------- package.json | 2 +- packages/annotations/package.json | 2 +- packages/babel-plugin-makepot/package.json | 2 +- packages/block-library/package.json | 2 +- packages/blocks/package.json | 2 +- packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/package.json | 2 +- packages/data/package.json | 2 +- packages/dom/package.json | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/editor/package.json | 2 +- packages/element/package.json | 2 +- packages/i18n/package.json | 2 +- packages/jest-console/package.json | 2 +- packages/keycodes/package.json | 2 +- .../package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/media-utils/package.json | 2 +- packages/notices/package.json | 2 +- packages/nux/package.json | 2 +- packages/plugins/package.json | 2 +- packages/rich-text/package.json | 2 +- packages/server-side-render/package.json | 2 +- packages/shortcode/package.json | 2 +- packages/token-list/package.json | 2 +- packages/viewport/package.json | 2 +- packages/wordcount/package.json | 2 +- 32 files changed, 139 insertions(+), 117 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 724cf26f9c4023..4d07d1c51ac97e 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -439,6 +439,13 @@ function gutenberg_register_vendor_scripts() { 'https://unpkg.com/react-dom@16.8.4/umd/react-dom' . $react_suffix . '.js', array( 'react' ) ); + + // TODO: This is necessarily only so long as core ships with v4.17.11, and + // can be removed at such time a newer version is available. + gutenberg_register_vendor_script( + 'lodash', + 'https://unpkg.com/lodash@4.17.14/lodash' . $suffix . '.js' + ); } /** diff --git a/package-lock.json b/package-lock.json index 69033fee71db56..0e27a7383cf08a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3669,7 +3669,7 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/rich-text": "file:packages/rich-text", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0", "uuid": "^3.3.2" } @@ -3698,7 +3698,7 @@ "requires": { "@babel/runtime": "^7.4.4", "gettext-parser": "^1.3.1", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/babel-preset-default": { @@ -3776,7 +3776,7 @@ "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", "fast-average-color": "4.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "url": "^0.11.0" } @@ -3810,7 +3810,7 @@ "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/shortcode": "file:packages/shortcode", "hpq": "^1.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0", "showdown": "^1.8.6", "simple-html-tokenizer": "^0.5.7", @@ -3840,7 +3840,7 @@ "clipboard": "^2.0.1", "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", @@ -3859,7 +3859,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/element": "file:packages/element", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/core-data": { @@ -3871,7 +3871,7 @@ "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0" } }, @@ -3894,7 +3894,7 @@ "@wordpress/redux-routine": "file:packages/redux-routine", "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2" } @@ -3944,7 +3944,7 @@ "version": "file:packages/dom", "requires": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/dom-ready": { @@ -3960,7 +3960,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/url": "file:packages/url", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "node-fetch": "^1.7.3" } }, @@ -3973,7 +3973,7 @@ "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", "@wordpress/scripts": "file:packages/scripts", "expect-puppeteer": "^4.0.0", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/edit-post": { @@ -3999,7 +3999,7 @@ "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "refx": "^3.0.0" } }, @@ -4042,7 +4042,7 @@ "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.2.5", "inherits": "^2.0.3", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", "redux-optimist": "^1.0.0", @@ -4056,7 +4056,7 @@ "requires": { "@babel/runtime": "^7.4.4", "@wordpress/escape-html": "file:packages/escape-html", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "react": "^16.8.4", "react-dom": "^16.8.4" } @@ -4110,7 +4110,7 @@ "requires": { "@babel/runtime": "^7.4.4", "gettext-parser": "^1.3.1", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "sprintf-js": "^1.1.1", "tannin": "^1.1.0" @@ -4128,7 +4128,7 @@ "requires": { "@babel/runtime": "^7.4.4", "jest-matcher-utils": "^24.7.0", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/jest-preset-default": { @@ -4154,14 +4154,14 @@ "requires": { "@babel/runtime": "^7.4.4", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/library-export-default-webpack-plugin": { "version": "file:packages/library-export-default-webpack-plugin", "dev": true, "requires": { - "lodash": "^4.17.11", + "lodash": "^4.17.14", "webpack-sources": "^1.1.0" } }, @@ -4174,7 +4174,7 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/media-utils": { @@ -4185,7 +4185,7 @@ "@wordpress/blob": "file:packages/blob", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/notices": { @@ -4194,7 +4194,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:packages/a11y", "@wordpress/data": "file:packages/data", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/npm-package-json-lint-config": { @@ -4210,7 +4210,7 @@ "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0" } }, @@ -4221,7 +4221,7 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/postcss-themes": { @@ -4261,7 +4261,7 @@ "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", "classnames": "^2.2.5", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "rememo": "^3.0.0" } @@ -4307,14 +4307,14 @@ "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "@wordpress/url": "file:packages/url", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/shortcode": { "version": "file:packages/shortcode", "requires": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5" } }, @@ -4322,7 +4322,7 @@ "version": "file:packages/token-list", "requires": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/url": { @@ -4339,14 +4339,14 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/element": "file:packages/element", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "@wordpress/wordcount": { "version": "file:packages/wordcount", "requires": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "JSONStream": { @@ -6384,7 +6384,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -6405,12 +6406,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6425,17 +6428,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -6552,7 +6558,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6564,6 +6571,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6578,6 +6586,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6585,12 +6594,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6609,6 +6620,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -6689,7 +6701,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6701,6 +6714,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6787,7 +6801,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6823,6 +6838,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6842,6 +6858,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6885,12 +6902,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -10946,7 +10965,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -10967,12 +10987,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10987,17 +11009,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -11114,7 +11139,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -11126,6 +11152,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -11140,6 +11167,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -11147,12 +11175,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11171,6 +11201,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -11251,7 +11282,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -11263,6 +11295,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -11349,7 +11382,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -11385,6 +11419,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11404,6 +11439,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -11447,12 +11483,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -15278,9 +15316,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.14", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", + "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -17093,9 +17131,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -22628,9 +22666,9 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -24840,38 +24878,15 @@ } }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "uniq": { diff --git a/package.json b/package.json index 988184ac5a1baf..75acc7ab8c39f8 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "jsdom": "11.12.0", "lerna": "3.14.1", "lint-staged": "8.1.5", - "lodash": "4.17.11", + "lodash": "4.17.14", "make-dir": "3.0.0", "metro-react-native-babel-preset": "0.55.0", "metro-react-native-babel-transformer": "0.55.0", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 16bec17d283ed8..72d181b0fa90b0 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -26,7 +26,7 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/rich-text": "file:../rich-text", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0", "uuid": "^3.3.2" }, diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index 802f8bd3c509d9..dd32f8e4ae78f5 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -31,7 +31,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "gettext-parser": "^1.3.1", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "peerDependencies": { "@babel/core": "^7.0.0" diff --git a/packages/block-library/package.json b/packages/block-library/package.json index f57f2988524cee..e3be5d583fdc90 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -40,7 +40,7 @@ "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", "fast-average-color": "4.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "url": "^0.11.0" }, diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 72f5fc9e3af87d..a296a3ca6975b7 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -35,7 +35,7 @@ "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/shortcode": "file:../shortcode", "hpq": "^1.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0", "showdown": "^1.8.6", "simple-html-tokenizer": "^0.5.7", diff --git a/packages/components/package.json b/packages/components/package.json index f82a996cda1e65..bcb29085eeb835 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,7 +36,7 @@ "clipboard": "^2.0.1", "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "moment": "^2.22.1", "mousetrap": "^1.6.2", diff --git a/packages/compose/package.json b/packages/compose/package.json index 57eac024c3f47a..6ca617b8d43c40 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -25,7 +25,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/element": "file:../element", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 55f219865f0e69..ccf20e4efee3e6 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -28,7 +28,7 @@ "@wordpress/deprecated": "file:../deprecated", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/data/package.json b/packages/data/package.json index 30d331281569c7..6c256fedcc64b7 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -31,7 +31,7 @@ "@wordpress/redux-routine": "file:../redux-routine", "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2" }, diff --git a/packages/dom/package.json b/packages/dom/package.json index dbcb2b0ad335a1..1ee73ddb2dd0d6 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -23,7 +23,7 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index ab69632a58f6ee..25b6889ec1530a 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -31,7 +31,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/keycodes": "file:../keycodes", "@wordpress/url": "file:../url", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "node-fetch": "^1.7.3" }, "peerDependencies": { diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index d7b75699be7f83..c615a0e7541dfd 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -27,7 +27,7 @@ "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", "@wordpress/scripts": "file:../scripts", "expect-puppeteer": "^4.0.0", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "peerDependencies": { "jest": ">=24", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index b6c85e26311ece..09cebd91d061b5 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -41,7 +41,7 @@ "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "refx": "^3.0.0" }, "publishConfig": { diff --git a/packages/editor/package.json b/packages/editor/package.json index ed99bfb65a924e..88ddf540a926aa 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -46,7 +46,7 @@ "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.2.5", "inherits": "^2.0.3", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", "redux-optimist": "^1.0.0", diff --git a/packages/element/package.json b/packages/element/package.json index 4e4a096b31fe24..f793bd4414bc6d 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -24,7 +24,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/escape-html": "file:../escape-html", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "react": "^16.8.4", "react-dom": "^16.8.4" }, diff --git a/packages/i18n/package.json b/packages/i18n/package.json index ccd1c1ee78a4fb..23307b7815eef9 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -26,7 +26,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "gettext-parser": "^1.3.1", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "sprintf-js": "^1.1.1", "tannin": "^1.1.0" diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 6ea01b687f2701..2548fb7d34ad24 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -31,7 +31,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "jest-matcher-utils": "^24.7.0", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "peerDependencies": { "jest": ">=24" diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 695528df254808..945e40dffb0a27 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -23,7 +23,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index 60daa21a7c288b..e14e8abdaa0c0e 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -26,7 +26,7 @@ ], "main": "index.js", "dependencies": { - "lodash": "^4.17.11", + "lodash": "^4.17.14", "webpack-sources": "^1.1.0" }, "peerDependencies": { diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 2810b989b7fd01..5fee209e114f97 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -26,7 +26,7 @@ "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 8f6ff15ae9420b..6b4d553ad48318 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -27,7 +27,7 @@ "@wordpress/blob": "file:../blob", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/notices/package.json b/packages/notices/package.json index 250ffed2398b0c..2afe1ce72c4355 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -24,7 +24,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:../a11y", "@wordpress/data": "file:../data", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/nux/package.json b/packages/nux/package.json index 9768b542bc534c..5391fd8bdd7ea0 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -27,7 +27,7 @@ "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "rememo": "^3.0.0" }, "publishConfig": { diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 86a1f40b8d5658..a7c65a4dea5f75 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -25,7 +25,7 @@ "@wordpress/compose": "file:../compose", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index d8672425a058a0..02aac8be88dd14 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -32,7 +32,7 @@ "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", "classnames": "^2.2.5", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5", "rememo": "^3.0.0" }, diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 3cb0ace0109874..3d6af1e7f21073 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -29,7 +29,7 @@ "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/url": "file:../url", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 490d8789ed1974..5718879964be8d 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -22,7 +22,7 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "memize": "^1.0.5" }, "publishConfig": { diff --git a/packages/token-list/package.json b/packages/token-list/package.json index d7ca4747b7ad69..28aa9e50364123 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -21,7 +21,7 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 334d7a834a112c..29e49449ac541c 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -25,7 +25,7 @@ "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index f3bac4a49be8ef..bb6db801438446 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -22,7 +22,7 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" From cf1da64370c209b25bb005c44083097a6137a119 Mon Sep 17 00:00:00 2001 From: Marko Savic <savicmarko1985@gmail.com> Date: Fri, 12 Jul 2019 15:10:45 -0400 Subject: [PATCH 492/664] [RNMobile] Native mobile release v1.9.0 (#16522) --- packages/block-library/src/video/edit.native.js | 1 + packages/edit-post/src/index.native.js | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 54b6a7c88d5818..913817ea2fed1c 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -238,6 +238,7 @@ class VideoEdit extends React.Component { value={ caption } placeholder={ __( 'Write caption…' ) } onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + onFocus={ this.props.onFocus } /> </View> ) } diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 1240c84ccb7e2f..1ffdcb13477335 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { Platform } from 'react-native'; - /** * WordPress dependencies */ @@ -36,11 +31,6 @@ export function initializeEditor() { // eslint-disable-next-line no-undef if ( typeof __DEV__ === 'undefined' || ! __DEV__ ) { unregisterBlockType( 'core/code' ); - - // Disable Video block except for iOS for now. - if ( Platform.OS !== 'ios' ) { - unregisterBlockType( 'core/video' ); - } } blocksRegistered = true; From c3d80db1c79f8bda8a44dcdf924bee8cc4b03b9a Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Mon, 15 Jul 2019 10:55:03 +0100 Subject: [PATCH 493/664] Hide the columns count control on empty columns block (#16476) --- packages/block-library/src/columns/edit.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index d573500cd551d9..d798f9f2115cb6 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -135,9 +135,14 @@ export function ColumnsEdit( { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); + // The template selector is shown when we first insert the columns block (count === 0). + // or if there's no template available. + // The count === 0 trick is useful when you use undo/redo. + const showTemplateSelector = ( count === 0 && ! forceUseTemplate ) || ! template; + return ( <> - { template && ( + { ! showTemplateSelector && ( <> <InspectorControls> <PanelBody> @@ -170,9 +175,7 @@ export function ColumnsEdit( { setForceUseTemplate( true ); } } __experimentalAllowTemplateOptionSkip - // setting the template to null when the inner blocks - // are empty allows to reset to the placeholder state. - template={ count === 0 && ! forceUseTemplate ? null : template } + template={ showTemplateSelector ? null : template } templateLock="all" allowedBlocks={ ALLOWED_BLOCKS } /> </div> From 7a06a10ede27fc5105bd59ca23b18ef2098a4619 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 15 Jul 2019 14:07:51 +0100 Subject: [PATCH 494/664] Fix: Block Movers disappear on middle breakpoints for full/wide blocks. (#16579) * Fix: Block Movers disappear on middle breakpoints for full/wide blocks. * Update packages/block-editor/src/components/block-list/style.scss Co-Authored-By: Kjell Reigstad <kjell.reigstad@automattic.com> --- .../src/components/block-list/style.scss | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 8ee3558ddf6398..9897ea51d63012 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -439,19 +439,6 @@ float: left; } - // Hide mover until wide breakpoints, or it might be covered by toolbar - &.is-multi-selected > .block-editor-block-mover, - > .block-editor-block-list__block-edit > .block-editor-block-mover { - display: none; - } - - @include break-wide() { - &.is-multi-selected > .block-editor-block-mover, - > .block-editor-block-list__block-edit > .block-editor-block-mover { - display: block; - } - } - // Beyond the mobile breakpoint, wide images stretch outside of the column. // To center the toolbar, we make it inline-flex so the toolbar is not full-wide. @include break-small () { @@ -464,6 +451,23 @@ .block-editor-block-mover.is-visible + .block-editor-block-list__breadcrumb { top: (-$block-padding - $block-left-border-width - ($grid-size-small / 2)); } + + // Align block toolbar to floated content. + @include break-mobile() { + .block-editor-block-toolbar { + /*!rtl:begin:ignore*/ + left: $block-side-ui-width * 3 + ($grid-size-small * 1.5); + /*!rtl:end:ignore*/ + } + } + + @include break-xlarge() { + .block-editor-block-toolbar { + /*!rtl:begin:ignore*/ + left: $block-padding; + /*!rtl:end:ignore*/ + } + } } // Wide From 6d016414e79c220c5a541cef02a26b2d44e68042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 15 Jul 2019 18:24:15 +0200 Subject: [PATCH 495/664] Codeowners: update assignments for gziolo (#16593) --- .github/CODEOWNERS | 56 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4391fa3fd2b83f..72e12a6edb9d46 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,7 +5,7 @@ # Data /packages/api-fetch @youknowriad @aduth @nerrad @mmtr -/packages/core-data @youknowriad @gziolo @aduth @nerrad +/packages/core-data @youknowriad @aduth @nerrad /packages/data @youknowriad @aduth @nerrad @coderkevin /packages/redux-routine @youknowriad @aduth @nerrad @@ -15,9 +15,9 @@ # Editor /packages/annotations @youknowriad @aduth @atimmer @ellatrix /packages/autop @youknowriad @aduth -/packages/block-editor @youknowriad @gziolo @talldan @ellatrix -/packages/block-serialization-spec-parser @youknowriad @gziolo @aduth @dmsnell -/packages/block-serialization-default-parser @youknowriad @gziolo @aduth @dmsnell +/packages/block-editor @youknowriad @talldan @ellatrix +/packages/block-serialization-spec-parser @youknowriad @aduth @dmsnell +/packages/block-serialization-default-parser @youknowriad @aduth @dmsnell /packages/blocks @youknowriad @gziolo @aduth @ellatrix /packages/edit-post @youknowriad @talldan /packages/editor @youknowriad @talldan @@ -28,14 +28,14 @@ /packages/edit-widgets @youknowriad # Tooling -/bin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/docs/tool @youknowriad @gziolo @chrisvanpatten @ajitbohra @nosolosw +/bin @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw +/docs/tool @youknowriad @chrisvanpatten @ajitbohra @nosolosw /packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/babel-plugin-makepot @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/babel-plugin-makepot @youknowriad @aduth @ntwb @nerrad @ajitbohra /packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/custom-templated-path-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/docgen @nosolosw @gziolo +/packages/custom-templated-path-webpack-plugin @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/docgen @nosolosw /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan /packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @@ -44,33 +44,33 @@ /packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/postcss-themes @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/postcss-themes @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw # UI Components /packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @chrisvanpatten /packages/compose @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan /packages/element @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/notices @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/nux @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/viewport @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/notices @youknowriad @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/nux @youknowriad @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/viewport @youknowriad @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan # Utilities -/packages/a11y @youknowriad @gziolo @aduth -/packages/blob @youknowriad @gziolo @aduth -/packages/date @youknowriad @gziolo @aduth -/packages/deprecated @youknowriad @gziolo @aduth -/packages/dom @youknowriad @gziolo @aduth @nosolosw @ellatrix -/packages/dom-ready @youknowriad @gziolo @aduth -/packages/escape-html @youknowriad @gziolo @aduth -/packages/html-entities @youknowriad @gziolo @aduth +/packages/a11y @youknowriad @aduth +/packages/blob @youknowriad @aduth +/packages/date @youknowriad @aduth +/packages/deprecated @youknowriad @aduth +/packages/dom @youknowriad @aduth @nosolosw @ellatrix +/packages/dom-ready @youknowriad @aduth +/packages/escape-html @youknowriad @aduth +/packages/html-entities @youknowriad @aduth /packages/i18n @youknowriad @aduth @swissspidy -/packages/is-shallow-equal @youknowriad @gziolo @aduth -/packages/keycodes @youknowriad @gziolo @aduth @talldan @ellatrix -/packages/priority-queue @youknowriad @gziolo @aduth -/packages/token-list @youknowriad @gziolo @aduth -/packages/url @youknowriad @gziolo @aduth @talldan -/packages/wordcount @youknowriad @gziolo @aduth +/packages/is-shallow-equal @youknowriad @aduth +/packages/keycodes @youknowriad @aduth @talldan @ellatrix +/packages/priority-queue @youknowriad @aduth +/packages/token-list @youknowriad @aduth +/packages/url @youknowriad @aduth @talldan +/packages/wordcount @youknowriad @aduth # Extensibility /packages/hooks @youknowriad @gziolo @aduth @adamsilverstein @@ -82,7 +82,7 @@ /packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom # PHP -/lib @youknowriad @gziolo @aduth @timothybjacobs +/lib @youknowriad @aduth @timothybjacobs # Native (Unowned) *.native.js @ghost From 4fa8431aaf148c9417e20804ee48874b8e271e12 Mon Sep 17 00:00:00 2001 From: Matt Chowning <matt.chowning@automattic.com> Date: Mon, 15 Jul 2019 12:36:56 -0400 Subject: [PATCH 496/664] Replace use of deprecated componentWillReceiveProps (#16577) --- packages/block-library/src/image/edit.native.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index cb0cc5a9302630..bb8878adc87322 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -98,12 +98,12 @@ class ImageEdit extends React.Component { } } - componentWillReceiveProps( nextProps ) { + static getDerivedStateFromProps( props, state ) { // Avoid a UI flicker in the toolbar by insuring that isCaptionSelected // is updated immediately any time the isSelected prop becomes false - this.setState( ( state ) => ( { - isCaptionSelected: nextProps.isSelected && state.isCaptionSelected, - } ) ); + return { + isCaptionSelected: props.isSelected && state.isCaptionSelected, + }; } onImagePressed() { From 13cab46cee20a1e3851a89999e36c979b9059595 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Tue, 16 Jul 2019 10:53:55 +0800 Subject: [PATCH 497/664] Fix typo (#16585) --- packages/block-editor/src/components/rich-text/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 48c2cf817f74ac..2c46ea36d61f73 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -41,7 +41,7 @@ import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; const wrapperClasses = 'editor-rich-text block-editor-rich-text'; const classes = 'editor-rich-text__editable block-editor-rich-text__editable'; -class RichTextWraper extends Component { +class RichTextWrapper extends Component { constructor() { super( ...arguments ); this.onEnter = this.onEnter.bind( this ); @@ -417,7 +417,7 @@ const RichTextContainer = compose( [ }; } ), withFilters( 'experimentalRichText' ), -] )( RichTextWraper ); +] )( RichTextWrapper ); RichTextContainer.Content = ( { value, tagName: Tag, multiline, ...props } ) => { let html = value; From a894b3520598eceb91ddd8bb0299024b4f970afa Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Tue, 16 Jul 2019 10:55:44 +0300 Subject: [PATCH 498/664] Try to improve triage docs (#16234) * try improve trage docs * spelling fix * Update CONTRIBUTING.md Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> * Update docs/contributors/repository-management.md Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> * Update docs/contributors/repository-management.md Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> * Update docs/contributors/repository-management.md Co-Authored-By: Chris Van Patten <hello@chrisvanpatten.com> --- CONTRIBUTING.md | 6 ++++++ docs/contributors/repository-management.md | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 001086875af3c8..8ec7046c01f84c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,12 @@ Please see the [Developer Contributions section](/docs/contributors/develop.md) If you'd like to contribute to the design or front-end, feel free to contribute to tickets labelled [Needs Design](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22Needs+Design%22) or [Needs Design Feedback](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A"Needs+Design+Feedback%22). We could use your thoughtful replies, mockups, animatics, sketches, doodles. Proposed changes are best done as minimal and specific iterations on the work that precedes it so we can compare. The [WordPress Design team](http://make.wordpress.org/design/) uses [Figma](https://www.figma.com/) to collaborate and share work. If you'd like to contribute, join the [#design channel](http://wordpress.slack.com/messages/design/) in [Slack](https://make.wordpress.org/chat/) and ask the team to set you up with a free Figma account. This will give you access to a helpful [library of components](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=0%3A1) used in WordPress. +## Triage Contributions + +*Triage* is the practice of reviewing existing issues to make sure they’re relevant, actionable, and have all the information needed to reproduce and/or solve the issue. Triaging is a very important contribution because it allows the community to focus on and prioritise issues, feature proposals, discussions, and so on. + +If you want to learn more about triage, and why it it important, please see the [repository management section](docs/contributors/repository-management.md#triaging-issues) of the Contributor Handbook. + ## Contribute to the Documentation Please see the [Documentation section](/docs/contributors/document.md) of the Contributor Handbook. diff --git a/docs/contributors/repository-management.md b/docs/contributors/repository-management.md index a9d22c8b4e59d1..8ceb01f494f9d1 100644 --- a/docs/contributors/repository-management.md +++ b/docs/contributors/repository-management.md @@ -60,12 +60,16 @@ To keep the issue list healthy, it needs to be triaged regularly. *Triage* is th Anyone can help triage, although you’ll need contributor permission on the Gutenberg repository to modify an issue’s labels or edit its title. -Here are a couple places you can start: +To start simply choose from one of these filtered lists of issues: -- [All Gutenberg issues without an assigned label](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+no%3Alabel). -- [The least recently updated Gutenberg issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc). +- [All Gutenberg issues without an assigned label](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+no%3Alabel). Triaging by simply adding labels helps people focused on certain aspects of Gutenberg find relevant issues easier and start working on them. +- [The least recently updated Gutenberg issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc). Triaging issues that are getting old and possibly out of date keeps important work from being overlooked. +- [All Gutenberg issues with no comments](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+sort%3Acomments-asc) Triaging this list helps make sure all issues are acknowledged, and can help identify issues that may need more information or discussion before they are actionable. +- [The least commented on issues](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+sort%3Acomments-asc) Triaging this list helps the community figure out things like traction for certain proposals. -When reviewing issues, here are some steps you can perform: +You can also create your own custom set of filters on GitHub. If you have a filter you think might be useful for the community, feel free to submit a PR to add it to this list. + +When triaging, either one of the lists above or issues in general, here are some steps you can perform: - First search for duplicates. If the issue is duplicate, close it by commenting with “Duplicate of #<original-id>” and add any relevant new details to the existing issue. - If the issue is missing labels, add some to better categorize it (requires proper permissions). @@ -78,6 +82,10 @@ When reviewing issues, here are some steps you can perform: - Check that the bug report is valid by debugging it to see if you can track down the technical specifics. - Check if the issue is missing some detail and see if you can fill in those details. For instance, if a bug report is missing visual detail, it’s helpful to reproduce the issue locally and upload a screenshot or GIF. +For triaging there are some labels which are very useful: +- `Needs Technical Feedback` - you can apply them when you see new features or API changes proposed +- `Needs More Info` - when it’s not clear what the issue is or it would help to provide additional details +- `Needs Testing` - it’s useful for old bugs where it seems like they are no longer relevant ## Pull Requests From 5a3c3586024fbb0b9e0276b5e31103ddf83edd52 Mon Sep 17 00:00:00 2001 From: Todor Gaidarov <gaidarov@gmail.com> Date: Tue, 16 Jul 2019 14:21:59 +0100 Subject: [PATCH 499/664] Update block-filters.md (#16610) Remove double mention of block name. --- docs/designers-developers/developers/filters/block-filters.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/filters/block-filters.md b/docs/designers-developers/developers/filters/block-filters.md index 3904d6eb3cf283..d5ddd71b3bdadf 100644 --- a/docs/designers-developers/developers/filters/block-filters.md +++ b/docs/designers-developers/developers/filters/block-filters.md @@ -59,7 +59,7 @@ Extending blocks can involve more than just providing alternative styles, in thi #### `blocks.registerBlockType` -Used to filter the block settings. It receives the block settings and the name of the block the registered block as arguments. Since v6.1.0 this filter is also applied to each of a block's deprecated settings. +Used to filter the block settings. It receives the block settings and the name of the registered block as arguments. Since v6.1.0 this filter is also applied to each of a block's deprecated settings. _Example:_ From 62439f083c5710ccc5a87187fa5c66a83c70b41f Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Tue, 16 Jul 2019 09:33:08 -0400 Subject: [PATCH 500/664] Slimmer top/bottom spacing inside notices (#16589) * Slimmer top/bottom spacing inside notices * Add slightly taller margins inside of notices in the notice list. So that they're not the same height as the sidebar's nav. --- packages/components/src/notice/style.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index 6c9b4715915639..bdb82370eaa6a0 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -28,7 +28,7 @@ } .components-notice__content { - margin: 1em #{ $icon-button-size-small + $border-width } 1em 0; + margin: $grid-size-small #{ $icon-button-size-small + $border-width } $grid-size-small 0; } .components-notice__action.components-button { @@ -63,4 +63,9 @@ // The notice should never be wider than the viewport, or the close button might be hidden. Especially relevant at high zoom levels. Related to https://core.trac.wordpress.org/ticket/47603#ticket. max-width: 100vw; z-index: z-index(".components-notice-list"); + + .components-notice__content { + margin-top: $grid-size; + margin-bottom: $grid-size; + } } From 07f19f17f09208b7918096bfaa19f43d20665df5 Mon Sep 17 00:00:00 2001 From: jodamo5 <josh@duoplus.co.nz> Date: Wed, 17 Jul 2019 15:08:34 +1200 Subject: [PATCH 501/664] Update dynamic blocks documentation (#16450) * Update dynamic blocks documentation Added in adjustments that @chrisvanpatten recommended on the last update, but they were accidentally removed before the final pull request. Also have added in a line mentioning that if using InnerBlocks these need to be saved using the save callback. * Update docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md Example added showing how to save InnerBlocks. Co-Authored-By: Marcus Kazmierczak <marcus@mkaz.com> --- .../tutorials/block-tutorial/creating-dynamic-blocks.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md index f21c3b3f09988e..f305fbfcb598d9 100644 --- a/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md +++ b/docs/designers-developers/developers/tutorials/block-tutorial/creating-dynamic-blocks.md @@ -4,11 +4,13 @@ Dynamic blocks are blocks that build their structure and content on the fly when There are two primary uses for dynamic blocks: -1. Blocks where content should change even if a post has not been updated. One example from WordPress itself is the latest posts block. This block will update everywhere it is used when a new post is published. -2. Blocks where updates to the code (HTML, CSS, JS) should be immediately shown on the front end of the website. For example, if you update the HTML structure of a block by adding a new class, adding a div, or changing the layout in any other way, if you want these changes to be applied immediately on all occurrences of that block across the site, a dynamic block should be used. (If a dynamic block is not used then when block code is updated Guterberg's [validation process](https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#validation) applies, causing users to see the validation message, "This block appears to have been modified externally"). +1. Blocks where content should change even if a post has not been updated. One example from WordPress itself is the Latest Posts block. This block will update everywhere it is used when a new post is published. +2. Blocks where updates to the code (HTML, CSS, JS) should be immediately shown on the front end of the website. For example, if you update the structure of a block by adding a new class, adding an HTML element, or changing the layout in any other way, using a dynamic block ensures those changes are applied immediately on all occurrences of that block across the site. (If a dynamic block is not used then when block code is updated Guterberg's [validation process](https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#validation) generally applies, causing users to see the validation message, "This block appears to have been modified externally"). For many dynamic blocks, the `save` callback function should be returned as `null`, which tells the editor to save only the [block attributes](https://developer.wordpress.org/block-editor/developers/block-api/block-attributes/) to the database. These attributes are then passed into the server-side rendering callback, so you can decide how to display the block on the front end of your site. When you return `null`, the editor will skip the block markup validation process, avoiding issues with frequently-changing markup. +If you are using [InnerBlocks](https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md) in a dynamic block you will need to save the `InnerBlocks` in the `save` callback function using `<InnerBlocks.Content/>` + You can also save an HTML representation of the block. If you provide a server-side rendering callback, this HTML will be replaced with the output of your callback, but will be rendered if your block is deactivated or your render callback is removed. Block attributes can be used for any content or setting you want to save for that block. In the first example above, with the latest posts block, the number of latest posts you want to show could be saved as an attribute. Or in the second example, attributes can be used for each piece of content you want to show in the front end - such as heading text, paragraph text, an image, a URL, etc. From e82b7c6a6bb50f4ecb8a9ad51432cc4f5464d214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= <nosolosw@users.noreply.github.com> Date: Wed, 17 Jul 2019 09:06:24 +0200 Subject: [PATCH 502/664] Update notifs (#16616) In the coming weeks/months I'll have less time to spend on Gutenberg build processes, so I want to be more selective of what I sign up for as a reviewer. --- .github/CODEOWNERS | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 72e12a6edb9d46..81ad7f1b51ed08 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,13 +28,14 @@ /packages/edit-widgets @youknowriad # Tooling -/bin @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw +/bin @youknowriad @aduth @ntwb @nerrad @ajitbohra +/bin/update-readmes.js @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw /docs/tool @youknowriad @chrisvanpatten @ajitbohra @nosolosw -/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/babel-plugin-makepot @youknowriad @aduth @ntwb @nerrad @ajitbohra -/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/custom-templated-path-webpack-plugin @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/custom-templated-path-webpack-plugin @youknowriad @aduth @ntwb @nerrad @ajitbohra /packages/docgen @nosolosw /packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan @@ -42,9 +43,9 @@ /packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra /packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw -/packages/postcss-themes @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra +/packages/postcss-themes @youknowriad @aduth @ntwb @nerrad @ajitbohra /packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw # UI Components @@ -60,7 +61,7 @@ /packages/blob @youknowriad @aduth /packages/date @youknowriad @aduth /packages/deprecated @youknowriad @aduth -/packages/dom @youknowriad @aduth @nosolosw @ellatrix +/packages/dom @youknowriad @aduth @ellatrix /packages/dom-ready @youknowriad @aduth /packages/escape-html @youknowriad @aduth /packages/html-entities @youknowriad @aduth From 5a9ab0a46a2febfd72fa633490958ec1bfcc5e41 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Thu, 18 Jul 2019 16:13:28 +0800 Subject: [PATCH 503/664] Fix disabled menu item button styles (#16581) * Reintroduce style for disabled menu items * Remove unused style --- packages/components/src/dropdown-menu/style.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 34f58970bf9f40..cbd41243062d3b 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -106,4 +106,12 @@ padding-left: 0.5rem; } } + + .components-dropdown-menu__menu-item, + .components-menu-item__button { + &:disabled, + &[aria-disabled="true"] { + opacity: 0.3; + } + } } From c323235c21d240c900ae95360206837a9936a4fa Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 18 Jul 2019 09:29:57 +0100 Subject: [PATCH 504/664] Fix: Converting video shortcode results in empty block (#16588) --- packages/block-library/src/video/tranforms.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/video/tranforms.js b/packages/block-library/src/video/tranforms.js index 43a662e2b15d0d..ca070a37855a75 100644 --- a/packages/block-library/src/video/tranforms.js +++ b/packages/block-library/src/video/tranforms.js @@ -28,8 +28,8 @@ const transforms = { attributes: { src: { type: 'string', - shortcode: ( { named: { src } } ) => { - return src; + shortcode: ( { named: { src, mp4, m4v, webm, ogv, flv } } ) => { + return src || mp4 || m4v || webm || ogv || flv; }, }, poster: { From 6ef0222b152408e640b8d519236f885e166c5a41 Mon Sep 17 00:00:00 2001 From: Matt Chowning <matt.chowning@automattic.com> Date: Fri, 19 Jul 2019 06:27:48 -0400 Subject: [PATCH 505/664] Mobile: Show "add block here" placeholder when adding block from title (#16539) --- .../src/components/block-list/index.native.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 117e3e5fcd4ee4..5227bce63ecc67 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -226,11 +226,12 @@ export class BlockList extends Component { return isUnmodifiedDefaultBlock( block ); } - renderItem( { item: clientId } ) { - const shouldReverseContent = this.isReplaceable( this.props.selectedBlock ); + renderItem( { item: clientId, index } ) { + const shouldShowAddBlockSeparator = this.state.blockTypePickerVisible && ( this.props.isBlockSelected( clientId ) || ( index === 0 && this.props.isPostTitleSelected ) ); + const shouldPutAddBlockSeparatorAboveBlock = this.isReplaceable( this.props.selectedBlock ) || this.props.isPostTitleSelected; return ( - <ReadableContentView reversed={ shouldReverseContent }> + <ReadableContentView reversed={ shouldPutAddBlockSeparatorAboveBlock }> <BlockListBlock key={ clientId } showTitle={ false } @@ -240,7 +241,7 @@ export class BlockList extends Component { borderStyle={ this.blockHolderBorderStyle() } focusedBorderColor={ styles.blockHolderFocused.borderColor } /> - { this.state.blockTypePickerVisible && this.props.isBlockSelected( clientId ) && this.renderAddBlockSeparator() } + { shouldShowAddBlockSeparator && this.renderAddBlockSeparator() } </ReadableContentView> ); } From b1b9c11f7951e310106112131aefc16d22a9ff44 Mon Sep 17 00:00:00 2001 From: Matt Chowning <matt.chowning@automattic.com> Date: Fri, 19 Jul 2019 17:05:17 -0400 Subject: [PATCH 506/664] [RNMobile] Blur post title any time another block is selected (#16642) * Unselect post title any time another block is selected * Remove unnecessary state check --- .../src/components/visual-editor/index.native.js | 11 +++++++++++ .../src/components/post-title/index.native.js | 15 ++------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js index 8a69b8af798831..708b96c8289aea 100644 --- a/packages/edit-post/src/components/visual-editor/index.native.js +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -26,6 +26,13 @@ class VisualEditor extends Component { }; } + static getDerivedStateFromProps( props ) { + if ( props.isAnyBlockSelected ) { + return { isPostTitleSelected: false }; + } + return null; + } + onPostTitleSelect() { this.setState( { isPostTitleSelected: true } ); this.props.clearSelectedBlock(); @@ -87,6 +94,7 @@ class VisualEditor extends Component { rootViewHeight={ rootViewHeight } safeAreaBottomInset={ safeAreaBottomInset } isPostTitleSelected={ this.state.isPostTitleSelected } + onBlockTypeSelected={ this.onPostTitleUnselect } /> </BlockEditorProvider> ); @@ -100,9 +108,12 @@ export default compose( [ getEditedPostAttribute, } = select( 'core/editor' ); + const { getSelectedBlockClientId } = select( 'core/block-editor' ); + return { blocks: getEditorBlocks(), title: getEditedPostAttribute( 'title' ), + isAnyBlockSelected: !! getSelectedBlockClientId(), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 67d58ff14e10cb..d91d0220753dfe 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -22,12 +22,6 @@ import { pasteHandler } from '@wordpress/blocks'; import styles from './style.scss'; class PostTitle extends Component { - constructor() { - super( ...arguments ); - - this.titleViewRef = null; - } - componentDidMount() { if ( this.props.innerRef ) { this.props.innerRef( this ); @@ -39,10 +33,7 @@ class PostTitle extends Component { } focus() { - if ( this.titleViewRef ) { - this.titleViewRef.focus(); - this.props.onSelect(); - } + this.props.onSelect(); } render() { @@ -91,10 +82,8 @@ class PostTitle extends Component { onSelectionChange={ () => { } } onEnter={ this.props.onEnterPress } disableEditingMenu={ true } - setRef={ ( ref ) => { - this.titleViewRef = ref; - } } __unstablePasteHandler={ pasteHandler } + __unstableIsSelected={ this.props.isSelected } > </RichText> </View> From b0884b75217cf10ea8e2707b9f3eac56bff8a5f5 Mon Sep 17 00:00:00 2001 From: Matt Chowning <matt.chowning@automattic.com> Date: Fri, 19 Jul 2019 19:38:52 -0400 Subject: [PATCH 507/664] When inserting block from title, replace block if appropriate (#16574) --- .../src/components/block-list/index.native.js | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 5227bce63ecc67..aeeb7fc815e557 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -61,15 +61,19 @@ export class BlockList extends Component { // create an empty block of the selected type const newBlock = createBlock( itemValue ); - this.finishBlockAppendingOrReplacing( newBlock ); + this.finishInsertingOrReplacingBlock( newBlock ); } - finishBlockAppendingOrReplacing( newBlock ) { - // now determine whether we need to replace the currently selected block (if it's empty) - // or just add a new block as usual + finishInsertingOrReplacingBlock( newBlock ) { if ( this.isReplaceable( this.props.selectedBlock ) ) { - // do replace here + // replace selected block this.props.replaceBlock( this.props.selectedBlockClientId, newBlock ); + } else if ( this.props.isPostTitleSelected && this.isReplaceable( this.props.firstBlock ) ) { + // replace first block in post: there is no selected block when the post title is selected, + // so replaceBlock does not select the new block and we need to manually select the new block + const { clientId: firstBlockId } = this.props.firstBlock; + this.props.replaceBlock( firstBlockId, newBlock ); + this.props.selectBlock( newBlock.clientId ); } else { this.props.insertBlock( newBlock, this.getNewBlockInsertionIndex() ); } @@ -110,7 +114,7 @@ export class BlockList extends Component { newMediaBlock.attributes.id = payload.mediaId; // finally append or replace as appropriate - this.finishBlockAppendingOrReplacing( newMediaBlock ); + this.finishInsertingOrReplacingBlock( newMediaBlock ); } ); } @@ -260,7 +264,7 @@ export class BlockList extends Component { const paragraphBlock = createBlock( 'core/paragraph' ); return ( <TouchableWithoutFeedback onPress={ () => { - this.finishBlockAppendingOrReplacing( paragraphBlock ); + this.finishInsertingOrReplacingBlock( paragraphBlock ); } } > <View style={ styles.blockListFooter } /> </TouchableWithoutFeedback> @@ -277,6 +281,7 @@ export class BlockList extends Component { export default compose( [ withSelect( ( select, { rootClientId } ) => { const { + getBlock, getBlockCount, getBlockName, getBlockIndex, @@ -287,13 +292,15 @@ export default compose( [ } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); + const blockClientIds = getBlockOrder( rootClientId ); return { - blockClientIds: getBlockOrder( rootClientId ), + blockClientIds, blockCount: getBlockCount( rootClientId ), getBlockName, isBlockSelected, selectedBlock: getSelectedBlock(), + firstBlock: getBlock( blockClientIds[ 0 ] ), selectedBlockClientId, selectedBlockOrder: getBlockIndex( selectedBlockClientId ), }; @@ -303,12 +310,14 @@ export default compose( [ insertBlock, replaceBlock, clearSelectedBlock, + selectBlock, } = dispatch( 'core/block-editor' ); return { clearSelectedBlock, insertBlock, replaceBlock, + selectBlock, }; } ), ] )( BlockList ); From ac1301377897e623b7ae31282127e1e67b7806a4 Mon Sep 17 00:00:00 2001 From: them-es <them-es@users.noreply.github.com> Date: Mon, 22 Jul 2019 11:50:29 +0200 Subject: [PATCH 508/664] Localize "Read More" link in Latest Posts block (#16665) --- packages/block-library/src/latest-posts/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index a98f9459ac7562..c2d26290746d62 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -220,7 +220,7 @@ class LatestPostsEdit extends Component { key="html" > { excerptLength < excerpt.trim().split( ' ' ).length ? - excerpt.trim().split( ' ', excerptLength ).join( ' ' ) + ' ... <a href=' + post.link + 'target="_blank" rel="noopener noreferrer">Read More</a>' : + excerpt.trim().split( ' ', excerptLength ).join( ' ' ) + ' ... <a href=' + post.link + 'target="_blank" rel="noopener noreferrer">' + __( 'Read More' ) + '</a>' : excerpt.trim().split( ' ', excerptLength ).join( ' ' ) } </RawHTML> </div> From b95e865de32ee62d722be3e3e71b334a4b639032 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 22 Jul 2019 13:43:39 +0200 Subject: [PATCH 509/664] Project Management: Update Milestone It reference date (#16702) * Project Management: Filter merge action in custom entrypoint * Project Management: Skip merge filter step in milestone workflow * Project Management: Update reference date to July 29 --- .github/actions/milestone-it/entrypoint.sh | 18 ++++++++++++++++-- .github/main.workflow | 6 ------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/actions/milestone-it/entrypoint.sh b/.github/actions/milestone-it/entrypoint.sh index c7548d65ce4dbf..3c65671104e266 100755 --- a/.github/actions/milestone-it/entrypoint.sh +++ b/.github/actions/milestone-it/entrypoint.sh @@ -1,7 +1,21 @@ #!/bin/bash set -e -# 1. Proceed only when merge occurs to `master` base branch. +# 1. Proceed only when acting on a merge action on the master branch. + +action=$(jq -r '.action' $GITHUB_EVENT_PATH) + +if [ "$action" != 'closed' ]; then + echo "Action '$action' not a close action. Aborting." + exit 78; +fi + +merged=$(jq -r '.pull_request.merged' $GITHUB_EVENT_PATH) + +if [ "$merged" != 'true' ]; then + echo "Pull request closed without merge. Aborting." + exit 78; +fi base=$(jq -r '.pull_request.base.ref' $GITHUB_EVENT_PATH) @@ -57,7 +71,7 @@ milestone="Gutenberg $major.$minor" reference_major=5 reference_minor=0 -reference_date=1549238400 +reference_date=1564358400 num_versions_elapsed=$(((major-reference_major)*10+(minor-reference_minor))) weeks=$((num_versions_elapsed*2)) due=$(date -u --iso-8601=seconds -d "$(date -d @$(echo $reference_date)) + $(echo $weeks) weeks") diff --git a/.github/main.workflow b/.github/main.workflow index a714ca269ada60..fcfd011ad3a8f0 100644 --- a/.github/main.workflow +++ b/.github/main.workflow @@ -3,13 +3,7 @@ workflow "Milestone merged pull requests" { resolves = ["Milestone It"] } -action "Filter merged" { - uses = "actions/bin/filter@3c0b4f0e63ea54ea5df2914b4fabf383368cd0da" - args = "merged true" -} - action "Milestone It" { uses = "./.github/actions/milestone-it" - needs = ["Filter merged"] secrets = ["GITHUB_TOKEN"] } From 643412ca070f187c5504683c71c393637808b42a Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Tue, 23 Jul 2019 12:02:06 +1000 Subject: [PATCH 510/664] Fix `npm run docs:build` on Windows environments (#16029) * Fix `npm run docs:build` on Windows environments Windows environments do not support shebang (#!) scripts. This caused `npm run docs:build` to error when running it on Windows. The fix is to run `node docs/tool/update-data.js` instead of running `./docs/tool/update-data.js`. Similarly, running `node path/to/directory` can cause problems, so we update the docs:build task to use `node path/to/directory/index.js`. docs:build: Use the .js extension in the filename * docgen: Correctly handle docblocks with CRLF line endings This allows `npm run docs:build` to generate docs on Windows environments. * Use explicit paths as to prevent errors in Windows machines * Remove shebang notation in update-readmes.js so it works consistently in Windows machines. * Add comment --- bin/update-readmes.js | 5 +++-- docs/tool/index.js | 4 ++-- docs/tool/update-data.js | 5 +++-- package.json | 6 +++--- packages/docgen/CHANGELOG.md | 6 ++++++ packages/docgen/src/get-jsdoc-from-token.js | 2 +- 6 files changed, 18 insertions(+), 10 deletions(-) mode change 100755 => 100644 docs/tool/update-data.js diff --git a/bin/update-readmes.js b/bin/update-readmes.js index 7d96e68adce03b..df420007de3970 100755 --- a/bin/update-readmes.js +++ b/bin/update-readmes.js @@ -1,5 +1,6 @@ -#!/usr/bin/env node - +/** + * Node dependencies. + */ const { join } = require( 'path' ); const spawnSync = require( 'child_process' ).spawnSync; diff --git a/docs/tool/index.js b/docs/tool/index.js index bb1ffdc599b104..cd0e8223f9a6b7 100644 --- a/docs/tool/index.js +++ b/docs/tool/index.js @@ -3,7 +3,7 @@ */ const fs = require( 'fs' ); const { join } = require( 'path' ); -const { execSync } = require( 'child_process' ); +const { execFileSync } = require( 'child_process' ); const path = require( 'path' ); /** @@ -15,7 +15,7 @@ const tocFileInput = path.resolve( __dirname, '../toc.json' ); const manifestOutput = path.resolve( __dirname, '../manifest-devhub.json' ); // Update data files from code -execSync( join( __dirname, 'update-data.js' ) ); +execFileSync( 'node', [ join( __dirname, 'update-data.js' ) ] ); // Process TOC file and generate manifest handbook fs.writeFileSync( manifestOutput, JSON.stringify( getRootManifest( tocFileInput ), undefined, '\t' ) ); diff --git a/docs/tool/update-data.js b/docs/tool/update-data.js old mode 100755 new mode 100644 index d689fc173fd4f2..59a45ac4d599b7 --- a/docs/tool/update-data.js +++ b/docs/tool/update-data.js @@ -1,5 +1,6 @@ -#!/usr/bin/env node - +/** + * Node dependencies. + */ const { join } = require( 'path' ); const spawnSync = require( 'child_process' ).spawnSync; diff --git a/package.json b/package.json index 75acc7ab8c39f8..3405bac2525cd6 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,7 @@ "predev": "npm run check-engines", "dev": "npm run build:packages && concurrently \"wp-scripts start\" \"npm run dev:packages\"", "dev:packages": "node ./bin/packages/watch.js", - "docs:build": "node ./docs/tool && node ./bin/update-readmes", + "docs:build": "node ./docs/tool/index.js && node ./bin/update-readmes.js", "fixtures:clean": "rimraf \"packages/e2e-tests/fixtures/blocks/*.+(json|serialized.html)\"", "fixtures:server-registered": "docker-compose run -w /var/www/html/wp-content/plugins/gutenberg --rm wordpress ./bin/get-server-blocks.php > test/integration/full-content/server-registered.json", "fixtures:generate": "npm run fixtures:server-registered && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", @@ -233,10 +233,10 @@ "wp-scripts lint-js" ], "{docs/{toc.json,tool/*.js},packages/{*/README.md,*/src/{actions,selectors}.js,components/src/*/**/README.md}}": [ - "node ./docs/tool" + "node ./docs/tool/index.js" ], "packages/**/*.js": [ - "node ./bin/update-readmes" + "node ./bin/update-readmes.js" ] } } diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index 2415b0ad876f6c..f81c562f3011c5 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.2.1 (Unreleased) + +### Bug Fixes + +- Docblocks with CRLF endings are now parsed correctly. + ## 1.2.0 (2019-05-21) ### Enhancement diff --git a/packages/docgen/src/get-jsdoc-from-token.js b/packages/docgen/src/get-jsdoc-from-token.js index e888abc53d06ca..e9cbc361c3bc52 100644 --- a/packages/docgen/src/get-jsdoc-from-token.js +++ b/packages/docgen/src/get-jsdoc-from-token.js @@ -20,7 +20,7 @@ const getTypeAsString = require( './get-type-as-string' ); module.exports = function( token ) { let jsdoc; const comments = getLeadingComments( token ); - if ( comments && comments.startsWith( '*\n' ) ) { + if ( comments && /^\*\r?\n/.test( comments ) ) { jsdoc = doctrine.parse( comments, { unwrap: true, recoverable: true, From 1ca616a456a9d9c5ab4698bf67589a99e1802fff Mon Sep 17 00:00:00 2001 From: Joen Asmussen <asmussen@gmail.com> Date: Tue, 23 Jul 2019 09:46:28 +0200 Subject: [PATCH 511/664] Fix issue with inconsistent nesting appender (#16453) Fixes #16221. This PR intends to fix a visual regression that happened as a result of some consistency work done to the trailing appender. The trailing appender is the one that sits at the end of the document to allow you to easily add new content. It usually sits after any block that isn't a paragraph. The consistency work brought this to nesting containers as well. But the side effect is that if you have a single list block inside a group block, the appender is permanently visible all the time, even if probably it shouldn't be. The net result is that the unselected editing canvas is no longer representative of the end result. This PR changes that, so the trailing appender is only visible when a parent or adjacent block is selected. It's not the be-all end-all solution, as it might make sense to look into more holistic changes to when the appender is visible. But it does fix the immediate visual regression. --- .../src/components/block-list/style.scss | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 9897ea51d63012..909d4bc7a13802 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -534,8 +534,8 @@ margin: 0 $block-padding; } - // Hide appender shortcuts in nested blocks - // This essentially duplicates the mobile styles for the appender component + // Hide appender shortcuts in nested blocks. + // This essentially duplicates the mobile styles for the appender component. // It would be nice to be able to use element queries in that component instead https://github.com/tomhodgins/element-queries-spec .block-editor-block-list__layout { .block-editor-inserter-with-shortcuts { @@ -551,6 +551,38 @@ } +/** + * Styles that affect inner-block containers (nested blocks). + */ + +// Hide trailing appender for non-empty blocks, until selected. +.block-editor-inner-blocks { + .block-editor-block-list__block + .block-list-appender { + display: none; + + // When a parent container is selected, show the appender. + .is-selected & { + display: block; + } + } + + // When a child of a parent is selected, show the adjacent appender. + .block-editor-block-list__block.is-selected + .block-list-appender { + display: block; + } + + /* @todo: + The two rules above can be simplified & combined when https://github.com/WordPress/gutenberg/pull/14961 is merged, + into the following: + + .is-selected &, + .has-child-selected & { + display: block; + } + */ +} + + /** * Left and right side UI; Unified toolbar on Mobile */ From 4ef9b7e2e2db765b5e11e37ec1024563b67574f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Wed, 24 Jul 2019 12:00:48 +0200 Subject: [PATCH 512/664] RichText: add withoutInteractiveFormatting prop (#14542) * Add withoutInteractiveFormatting * Update docs * Introduce allowedFormats * Add readme entry * Deprecate formattingControls * Oops * Address feedback --- package-lock.json | 1 + packages/block-editor/CHANGELOG.md | 4 + packages/block-editor/package.json | 1 + .../src/components/rich-text/README.md | 8 +- .../src/components/rich-text/index.js | 30 ++++- packages/block-library/src/button/edit.js | 2 +- packages/block-library/src/file/edit.js | 4 +- packages/block-library/src/search/edit.js | 4 +- .../rich-text/src/component/format-edit.js | 109 +++++++++++------- packages/rich-text/src/component/index.js | 9 +- 10 files changed, 122 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e27a7383cf08a..16b1a4037b3d61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3732,6 +3732,7 @@ "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", + "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 2d97dd70be0c25..67c7e2a9725f74 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,5 +1,9 @@ ## Master +### New Features + +- Added a new `allowedFormats` prop to `RichText` to fine tune allowed formats. Deprecated the `formattingControls` prop in favour of this. Also added a `withoutInteractiveFormatting` to specifically disable format types that would insert interactive elements, which can not be nested. + ### Breaking Changes - `BlockEditorProvider` no longer renders a wrapping `SlotFillProvider` or `DropZoneProvider` (from `@wordpress/components`). For custom block editors, you should render your own as wrapping the `BlockEditorProvider`. A future release will include a new `BlockEditor` component for simple, standard usage. `BlockEditorProvider` will serve the simple purpose of establishing its own context for block editors. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 5add09430524e1..61c49f9d9a088e 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -29,6 +29,7 @@ "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md index cca45bce3ec047..9cc0b3e93fccdd 100644 --- a/packages/block-editor/src/components/rich-text/README.md +++ b/packages/block-editor/src/components/rich-text/README.md @@ -41,9 +41,13 @@ Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs *Optional.* Called when the block can be removed. `forward` is true when the selection is expected to move to the next block, false to the previous block. -### `formattingControls: Array` +### `allowedFormats: Array` -*Optional.* By default, all formatting controls are present. This setting can be used to fine-tune formatting controls. Possible items: `[ 'bold', 'italic', 'strikethrough', 'link' ]`. +*Optional.* By default, all registered formats are allowed. This setting can be used to fine-tune the allowed formats. Example: `[ 'core/bold', 'core/link' ]`. + +### `withoutInteractiveFormatting: Boolean` + +*Optional.* By default, all formatting controls are present. This setting can be used to remove formatting controls that would make content [interactive](https://html.spec.whatwg.org/multipage/dom.html#interactive-content). This is useful if you want to make content that is already interactive editable. ### `isSelected: Boolean` diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 2c46ea36d61f73..8a3ef0550045bc 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -27,6 +27,7 @@ import { } from '@wordpress/rich-text'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; import { createBlobURL } from '@wordpress/blob'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -253,6 +254,24 @@ class RichTextWrapper extends Component { onReplace( [ block ] ); } + getAllowedFormats() { + const { allowedFormats, formattingControls } = this.props; + + if ( ! allowedFormats && ! formattingControls ) { + return; + } + + if ( allowedFormats ) { + return allowedFormats; + } + + deprecated( 'wp.blockEditor.RichText formattingControls prop', { + alternative: 'allowedFormats', + } ); + + return formattingControls.map( ( name ) => `core/${ name }` ); + } + render() { const { tagName, @@ -276,6 +295,9 @@ class RichTextWrapper extends Component { placeholder, keepPlaceholderOnFocus, // eslint-disable-next-line no-unused-vars + allowedFormats, + withoutInteractiveFormatting, + // eslint-disable-next-line no-unused-vars onRemove, // eslint-disable-next-line no-unused-vars onMerge, @@ -293,6 +315,8 @@ class RichTextWrapper extends Component { ...experimentalProps } = this.props; + const adjustedAllowedFormats = this.getAllowedFormats(); + const hasFormats = ! adjustedAllowedFormats || adjustedAllowedFormats.length > 0; let adjustedValue = originalValue; let adjustedOnChange = originalOnChange; @@ -317,6 +341,8 @@ class RichTextWrapper extends Component { className={ classnames( classes, className ) } placeholder={ placeholder } keepPlaceholderOnFocus={ keepPlaceholderOnFocus } + allowedFormats={ adjustedAllowedFormats } + withoutInteractiveFormatting={ withoutInteractiveFormatting } onEnter={ this.onEnter } onDelete={ this.onDelete } onPaste={ this.onPaste } @@ -341,12 +367,12 @@ class RichTextWrapper extends Component { onChange={ onChange } /> ) } - { isSelected && ! inlineToolbar && ( + { isSelected && ! inlineToolbar && hasFormats && ( <BlockFormatControls> <FormatToolbar /> </BlockFormatControls> ) } - { isSelected && inlineToolbar && ( + { isSelected && inlineToolbar && hasFormats && ( <IsolatedEventContainer className="editor-rich-text__inline-toolbar block-editor-rich-text__inline-toolbar" > diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 4e8f0dfedc3265..a5f70204d6557c 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -114,7 +114,7 @@ class ButtonEdit extends Component { placeholder={ __( 'Add text…' ) } value={ text } onChange={ ( value ) => setAttributes( { text: value } ) } - formattingControls={ [ 'bold', 'italic', 'strikethrough' ] } + withoutInteractiveFormatting className={ classnames( 'wp-block-button__link', { 'has-background': backgroundColor.color, diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 2ab0baaa08f8bf..616092ac82203f 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -213,7 +213,7 @@ class FileEdit extends Component { value={ fileName } placeholder={ __( 'Write file name…' ) } keepPlaceholderOnFocus - formattingControls={ [] } // disable controls + withoutInteractiveFormatting onChange={ ( text ) => setAttributes( { fileName: text } ) } /> { showDownloadButton && @@ -223,7 +223,7 @@ class FileEdit extends Component { tagName="div" // must be block-level or else cursor disappears className={ 'wp-block-file__button' } value={ downloadButtonText } - formattingControls={ [] } // disable controls + withoutInteractiveFormatting placeholder={ __( 'Add text…' ) } keepPlaceholderOnFocus onChange={ ( text ) => setAttributes( { downloadButtonText: text } ) } diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 6525f5a82408f9..40834ab63f3ab3 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -14,7 +14,7 @@ export default function SearchEdit( { className, attributes, setAttributes } ) { aria-label={ __( 'Label text' ) } placeholder={ __( 'Add label…' ) } keepPlaceholderOnFocus - formattingControls={ [] } + withoutInteractiveFormatting value={ label } onChange={ ( html ) => setAttributes( { label: html } ) } /> @@ -34,7 +34,7 @@ export default function SearchEdit( { className, attributes, setAttributes } ) { aria-label={ __( 'Button text' ) } placeholder={ __( 'Add button text…' ) } keepPlaceholderOnFocus - formattingControls={ [] } + withoutInteractiveFormatting value={ buttonText } onChange={ ( html ) => setAttributes( { buttonText: html } ) } /> diff --git a/packages/rich-text/src/component/format-edit.js b/packages/rich-text/src/component/format-edit.js index 9a9d1945ba8844..2bee3de1237823 100644 --- a/packages/rich-text/src/component/format-edit.js +++ b/packages/rich-text/src/component/format-edit.js @@ -9,45 +9,74 @@ import { withSelect } from '@wordpress/data'; import { getActiveFormat } from '../get-active-format'; import { getActiveObject } from '../get-active-object'; -const FormatEdit = ( { formatTypes, onChange, value } ) => { - return ( - <> - { formatTypes.map( ( { name, edit: Edit } ) => { - if ( ! Edit ) { - return null; +/** + * Set of all interactive content tags. + * + * @see https://html.spec.whatwg.org/multipage/dom.html#interactive-content + */ +const interactiveContentTags = new Set( [ + 'a', + 'audio', + 'button', + 'details', + 'embed', + 'iframe', + 'input', + 'label', + 'select', + 'textarea', + 'video', +] ); + +const FormatEdit = ( { + formatTypes, + onChange, + value, + allowedFormats, + withoutInteractiveFormatting, +} ) => + formatTypes.map( ( { + name, + edit: Edit, + tagName, + } ) => { + if ( ! Edit ) { + return null; + } + + if ( allowedFormats && allowedFormats.indexOf( name ) === -1 ) { + return null; + } + + if ( + withoutInteractiveFormatting && + interactiveContentTags.has( tagName ) + ) { + return null; + } + + const activeFormat = getActiveFormat( value, name ); + const isActive = activeFormat !== undefined; + const activeObject = getActiveObject( value ); + const isObjectActive = activeObject !== undefined; + + return ( + <Edit + key={ name } + isActive={ isActive } + activeAttributes={ + isActive ? activeFormat.attributes || {} : {} + } + isObjectActive={ isObjectActive } + activeObjectAttributes={ + isObjectActive ? activeObject.attributes || {} : {} } + value={ value } + onChange={ onChange } + /> + ); + } ); - const activeFormat = getActiveFormat( value, name ); - const isActive = activeFormat !== undefined; - const activeObject = getActiveObject( value ); - const isObjectActive = activeObject !== undefined; - - return ( - <Edit - key={ name } - isActive={ isActive } - activeAttributes={ - isActive ? activeFormat.attributes || {} : {} - } - isObjectActive={ isObjectActive } - activeObjectAttributes={ - isObjectActive ? activeObject.attributes || {} : {} - } - value={ value } - onChange={ onChange } - /> - ); - } ) } - </> - ); -}; - -export default withSelect( - ( select ) => { - const { getFormatTypes } = select( 'core/rich-text' ); - - return { - formatTypes: getFormatTypes(), - }; - } -)( FormatEdit ); +export default withSelect( ( select ) => ( { + formatTypes: select( 'core/rich-text' ).getFormatTypes(), +} ) )( FormatEdit ); diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index c2f76c4a22c296..34792ad1d2b3aa 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -864,6 +864,8 @@ class RichText extends Component { __unstableAutocompleters: autocompleters, __unstableAutocomplete: Autocomplete = ( { children: ch } ) => ch( {} ), __unstableOnReplace: onReplace, + allowedFormats, + withoutInteractiveFormatting, } = this.props; // Generating a key that includes `tagName` ensures that if the tag @@ -908,7 +910,12 @@ class RichText extends Component { { MultilineTag ? <MultilineTag>{ placeholder }</MultilineTag> : placeholder } </Tagname> } - { isSelected && <FormatEdit value={ record } onChange={ this.onChange } /> } + { isSelected && <FormatEdit + allowedFormats={ allowedFormats } + withoutInteractiveFormatting={ withoutInteractiveFormatting } + value={ record } + onChange={ this.onChange } + /> } </> ); From 70a11492d2e66b06e0a1dc3019ce5a0e34948296 Mon Sep 17 00:00:00 2001 From: Matt Chowning <matt.chowning@automattic.com> Date: Tue, 23 Jul 2019 10:48:42 -0400 Subject: [PATCH 513/664] Mobile: ignore default tagName in image blocks Using a `div` tag on Android causes the text's alignment to be reset to normal alignment unless the div has a text-align style attached to it. This is a problem on image captions which need to be center aligned. --- packages/block-library/src/image/edit.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index bb8878adc87322..299b595ed896b3 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -368,6 +368,7 @@ class ImageEdit extends React.Component { fontSize={ 14 } underlineColorAndroid="transparent" textAlign={ 'center' } + tagName={ '' } /> </View> ) } From 28c7f3a6ca159627d9f3ddc5bd89834b314ca9ae Mon Sep 17 00:00:00 2001 From: Chris Van Patten <chris@vanpattenmedia.com> Date: Wed, 24 Jul 2019 09:29:46 -0400 Subject: [PATCH 514/664] Apply inline-flex to all buttons within a ButtonGroup (fixes 16664) (#16686) --- packages/components/src/button-group/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/src/button-group/style.scss b/packages/components/src/button-group/style.scss index 08f4ba8ecf4596..b256895f0a47ae 100644 --- a/packages/components/src/button-group/style.scss +++ b/packages/components/src/button-group/style.scss @@ -3,6 +3,7 @@ .components-button.is-button { border-radius: 0; + display: inline-flex; & + .components-button.is-button { margin-left: -1px; From 95bef4637104e20aaa50f06ec8793adc7c93358d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 24 Jul 2019 10:47:36 -0400 Subject: [PATCH 515/664] Project Management: Update aduth's code ownership (#16734) --- .github/CODEOWNERS | 84 +++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 81ad7f1b51ed08..1a509c9cebff71 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,86 +4,86 @@ /docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra # Data -/packages/api-fetch @youknowriad @aduth @nerrad @mmtr -/packages/core-data @youknowriad @aduth @nerrad -/packages/data @youknowriad @aduth @nerrad @coderkevin -/packages/redux-routine @youknowriad @aduth @nerrad +/packages/api-fetch @youknowriad @nerrad @mmtr +/packages/core-data @youknowriad @nerrad +/packages/data @youknowriad @nerrad @coderkevin +/packages/redux-routine @youknowriad @nerrad # Blocks /packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan # Editor -/packages/annotations @youknowriad @aduth @atimmer @ellatrix +/packages/annotations @youknowriad @atimmer @ellatrix /packages/autop @youknowriad @aduth /packages/block-editor @youknowriad @talldan @ellatrix -/packages/block-serialization-spec-parser @youknowriad @aduth @dmsnell -/packages/block-serialization-default-parser @youknowriad @aduth @dmsnell -/packages/blocks @youknowriad @gziolo @aduth @ellatrix +/packages/block-serialization-spec-parser @youknowriad @dmsnell +/packages/block-serialization-default-parser @youknowriad @dmsnell +/packages/blocks @youknowriad @gziolo @ellatrix /packages/edit-post @youknowriad @talldan /packages/editor @youknowriad @talldan -/packages/list-reusable-blocks @youknowriad @aduth @noisysocks +/packages/list-reusable-blocks @youknowriad @noisysocks /packages/shortcode @youknowriad @aduth # Widgets /packages/edit-widgets @youknowriad # Tooling -/bin @youknowriad @aduth @ntwb @nerrad @ajitbohra -/bin/update-readmes.js @youknowriad @aduth @ntwb @nerrad @ajitbohra @nosolosw +/bin @youknowriad @ntwb @nerrad @ajitbohra +/bin/update-readmes.js @youknowriad @ntwb @nerrad @ajitbohra @nosolosw /docs/tool @youknowriad @chrisvanpatten @ajitbohra @nosolosw -/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/babel-plugin-makepot @youknowriad @aduth @ntwb @nerrad @ajitbohra -/packages/babel-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/browserslist-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/custom-templated-path-webpack-plugin @youknowriad @aduth @ntwb @nerrad @ajitbohra +/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/babel-plugin-makepot @youknowriad @ntwb @nerrad @ajitbohra +/packages/babel-preset-default @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/browserslist-config @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/custom-templated-path-webpack-plugin @youknowriad @ntwb @nerrad @ajitbohra /packages/docgen @nosolosw -/packages/e2e-test-utils @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/e2e-tests @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @talldan -/packages/eslint-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/jest-console @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/jest-preset-default @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/jest-puppeteer-axe @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/npm-package-json-lint-config @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra -/packages/postcss-themes @youknowriad @aduth @ntwb @nerrad @ajitbohra -/packages/scripts @youknowriad @gziolo @aduth @ntwb @nerrad @ajitbohra @nosolosw +/packages/e2e-test-utils @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/e2e-tests @youknowriad @gziolo @ntwb @nerrad @ajitbohra @talldan +/packages/eslint-plugin @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/jest-console @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/jest-preset-default @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/jest-puppeteer-axe @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/library-export-default-webpack-plugin @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/npm-package-json-lint-config @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/postcss-themes @youknowriad @ntwb @nerrad @ajitbohra +/packages/scripts @youknowriad @gziolo @ntwb @nerrad @ajitbohra @nosolosw # UI Components -/packages/components @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @chrisvanpatten -/packages/compose @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/element @youknowriad @gziolo @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/notices @youknowriad @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/nux @youknowriad @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks -/packages/viewport @youknowriad @aduth @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/components @youknowriad @gziolo @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @chrisvanpatten +/packages/compose @youknowriad @gziolo @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/element @youknowriad @gziolo @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/notices @youknowriad @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/nux @youknowriad @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/viewport @youknowriad @ajitbohra @jaymanpandya @jorgefilipecosta @talldan # Utilities /packages/a11y @youknowriad @aduth /packages/blob @youknowriad @aduth /packages/date @youknowriad @aduth /packages/deprecated @youknowriad @aduth -/packages/dom @youknowriad @aduth @ellatrix +/packages/dom @youknowriad @ellatrix /packages/dom-ready @youknowriad @aduth /packages/escape-html @youknowriad @aduth /packages/html-entities @youknowriad @aduth -/packages/i18n @youknowriad @aduth @swissspidy +/packages/i18n @youknowriad @swissspidy /packages/is-shallow-equal @youknowriad @aduth -/packages/keycodes @youknowriad @aduth @talldan @ellatrix +/packages/keycodes @youknowriad @talldan @ellatrix /packages/priority-queue @youknowriad @aduth /packages/token-list @youknowriad @aduth -/packages/url @youknowriad @aduth @talldan +/packages/url @youknowriad @talldan /packages/wordcount @youknowriad @aduth # Extensibility -/packages/hooks @youknowriad @gziolo @aduth @adamsilverstein -/packages/plugins @youknowriad @gziolo @aduth @adamsilverstein +/packages/hooks @youknowriad @gziolo @adamsilverstein +/packages/plugins @youknowriad @gziolo @adamsilverstein # Rich Text -/packages/format-library @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom -/packages/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom -/packages/block-editor/src/components/rich-text @youknowriad @aduth @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/format-library @youknowriad @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/rich-text @youknowriad @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/block-editor/src/components/rich-text @youknowriad @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom # PHP -/lib @youknowriad @aduth @timothybjacobs +/lib @youknowriad @timothybjacobs # Native (Unowned) *.native.js @ghost From 174c0982fed8a2e81d0a2e70fd474009e7e85b4d Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Wed, 24 Jul 2019 11:59:48 -0400 Subject: [PATCH 516/664] remove inappropriate executable permissions from a handful of files in core-data (#16687) --- packages/core-data/src/queried-data/actions.js | 0 packages/core-data/src/queried-data/get-query-parts.js | 0 packages/core-data/src/queried-data/index.js | 0 packages/core-data/src/queried-data/reducer.js | 0 packages/core-data/src/queried-data/selectors.js | 0 packages/core-data/src/queried-data/test/get-query-parts.js | 0 packages/core-data/src/queried-data/test/reducer.js | 0 packages/core-data/src/queried-data/test/selectors.js | 0 packages/core-data/src/utils/if-matching-action.js | 0 packages/core-data/src/utils/index.js | 0 packages/core-data/src/utils/on-sub-key.js | 0 packages/core-data/src/utils/replace-action.js | 0 packages/core-data/src/utils/test/if-matching-action.js | 0 packages/core-data/src/utils/test/on-sub-key.js | 0 packages/core-data/src/utils/test/replace-action.js | 0 packages/core-data/src/utils/test/with-weak-map-cache.js | 0 packages/core-data/src/utils/with-weak-map-cache.js | 0 17 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 packages/core-data/src/queried-data/actions.js mode change 100755 => 100644 packages/core-data/src/queried-data/get-query-parts.js mode change 100755 => 100644 packages/core-data/src/queried-data/index.js mode change 100755 => 100644 packages/core-data/src/queried-data/reducer.js mode change 100755 => 100644 packages/core-data/src/queried-data/selectors.js mode change 100755 => 100644 packages/core-data/src/queried-data/test/get-query-parts.js mode change 100755 => 100644 packages/core-data/src/queried-data/test/reducer.js mode change 100755 => 100644 packages/core-data/src/queried-data/test/selectors.js mode change 100755 => 100644 packages/core-data/src/utils/if-matching-action.js mode change 100755 => 100644 packages/core-data/src/utils/index.js mode change 100755 => 100644 packages/core-data/src/utils/on-sub-key.js mode change 100755 => 100644 packages/core-data/src/utils/replace-action.js mode change 100755 => 100644 packages/core-data/src/utils/test/if-matching-action.js mode change 100755 => 100644 packages/core-data/src/utils/test/on-sub-key.js mode change 100755 => 100644 packages/core-data/src/utils/test/replace-action.js mode change 100755 => 100644 packages/core-data/src/utils/test/with-weak-map-cache.js mode change 100755 => 100644 packages/core-data/src/utils/with-weak-map-cache.js diff --git a/packages/core-data/src/queried-data/actions.js b/packages/core-data/src/queried-data/actions.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/get-query-parts.js b/packages/core-data/src/queried-data/get-query-parts.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/index.js b/packages/core-data/src/queried-data/index.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/selectors.js b/packages/core-data/src/queried-data/selectors.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/test/get-query-parts.js b/packages/core-data/src/queried-data/test/get-query-parts.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/test/reducer.js b/packages/core-data/src/queried-data/test/reducer.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/queried-data/test/selectors.js b/packages/core-data/src/queried-data/test/selectors.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/if-matching-action.js b/packages/core-data/src/utils/if-matching-action.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/on-sub-key.js b/packages/core-data/src/utils/on-sub-key.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/replace-action.js b/packages/core-data/src/utils/replace-action.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/test/if-matching-action.js b/packages/core-data/src/utils/test/if-matching-action.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/test/on-sub-key.js b/packages/core-data/src/utils/test/on-sub-key.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/test/replace-action.js b/packages/core-data/src/utils/test/replace-action.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/test/with-weak-map-cache.js b/packages/core-data/src/utils/test/with-weak-map-cache.js old mode 100755 new mode 100644 diff --git a/packages/core-data/src/utils/with-weak-map-cache.js b/packages/core-data/src/utils/with-weak-map-cache.js old mode 100755 new mode 100644 From c3a39f4a0bacdcf148e877573e51a2fa954ffcc4 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 25 Jul 2019 09:04:11 +0100 Subject: [PATCH 517/664] Remove "Change Permalink" button when permalink is not editable. (#16395) --- .../editor/src/components/post-permalink/index.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/editor/src/components/post-permalink/index.js b/packages/editor/src/components/post-permalink/index.js index aa713b4bf82628..59d4df1600895e 100644 --- a/packages/editor/src/components/post-permalink/index.js +++ b/packages/editor/src/components/post-permalink/index.js @@ -18,7 +18,7 @@ import { safeDecodeURI, safeDecodeURIComponent } from '@wordpress/url'; * Internal dependencies */ import PostPermalinkEditor from './editor.js'; -import { getWPAdminURL, cleanForSlug } from '../../utils/url'; +import { cleanForSlug } from '../../utils/url'; class PostPermalink extends Component { constructor() { @@ -122,18 +122,6 @@ class PostPermalink extends Component { { __( 'Edit' ) } </Button> } - - { ! isEditable && - <Button - className="editor-post-permalink__change" - isLarge - href={ getWPAdminURL( 'options-permalink.php' ) } - onClick={ this.addVisibilityCheck } - target="_blank" - > - { __( 'Change Permalinks' ) } - </Button> - } </div> ); } From 8f542ba831a053689c76cf9d3168c9f9091d6706 Mon Sep 17 00:00:00 2001 From: Maxime Biais <maxime@bia.is> Date: Thu, 25 Jul 2019 12:13:24 +0200 Subject: [PATCH 518/664] [RNMobile] Update Video caption placeholder color to match other placeholder text styles (#16716) --- packages/block-library/src/video/edit.native.js | 1 + packages/block-library/src/video/style.native.scss | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 913817ea2fed1c..4d7ce426d55c05 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -237,6 +237,7 @@ class VideoEdit extends React.Component { underlineColorAndroid="transparent" value={ caption } placeholder={ __( 'Write caption…' ) } + placeholderTextColor={ style.captionPlaceholder.color } onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) } onFocus={ this.props.onFocus } /> diff --git a/packages/block-library/src/video/style.native.scss b/packages/block-library/src/video/style.native.scss index e7b2a3014b579e..a7b77efce2c4bc 100644 --- a/packages/block-library/src/video/style.native.scss +++ b/packages/block-library/src/video/style.native.scss @@ -57,6 +57,10 @@ font-family: $default-regular-font; } +.captionPlaceholder { + color: $gray; +} + .icon { fill: $gray-dark; width: 100%; From e65c55d4c90ae862236bda5fcf192b486496acc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 25 Jul 2019 13:51:51 +0200 Subject: [PATCH 519/664] Remove gziolo from the list of code owners for the block library --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1a509c9cebff71..8ee088faf9d2f0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,7 +10,7 @@ /packages/redux-routine @youknowriad @nerrad # Blocks -/packages/block-library @youknowriad @gziolo @Soean @ajitbohra @jorgefilipecosta @talldan +/packages/block-library @youknowriad @Soean @ajitbohra @jorgefilipecosta @talldan # Editor /packages/annotations @youknowriad @atimmer @ellatrix From 9fcccb3c374238a161fc92dc414f0e11d135606e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 25 Jul 2019 09:00:52 -0400 Subject: [PATCH 520/664] ESLint Plugin: Exempt React hooks from no-unused-vars-before-return (#16737) * ESLint Plugin: No Unused Vars Before Return: Add option excludePattern * ESLint Plugin: Exempt hooks from recommended react configuration * Components: Remove unneccessary ESLint disabling --- .../components/src/font-size-picker/index.js | 1 - packages/eslint-plugin/CHANGELOG.md | 10 ++++++++ packages/eslint-plugin/configs/react.js | 3 +++ packages/eslint-plugin/configs/recommended.js | 2 +- .../rules/no-unused-vars-before-return.md | 6 +++++ .../__tests__/no-unused-vars-before-return.js | 25 +++++++++++++++++++ .../rules/no-unused-vars-before-return.js | 22 +++++++++++++++- 7 files changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index 0143c04fa098ca..a52f6ccd841d2f 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -35,7 +35,6 @@ function FontSizePicker( { value, withSlider = false, } ) { - // eslint-disable-next-line @wordpress/no-unused-vars-before-return const [ currentSelectValue, setCurrentSelectValue ] = useState( getSelectValueFromFontSize( fontSizes, value ) ); if ( disableCustomFontSizes && ! fontSizes.length ) { diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index abe9728d926313..b209286ab3fd3b 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.4.0 (Unreleased) + +### New Features + +- [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) now supports an `excludePattern` option to exempt function calls by name. + +### Improvements + +- The recommended `react` configuration specifies an option to [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/react-unused-vars-before-return.md) to exempt React hooks usage, by convention of hooks beginning with "use" prefix. + ## 2.3.0 (2019-06-12) ### Bug Fix diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js index 8f88b208df2e36..20d0ccfc797b84 100644 --- a/packages/eslint-plugin/configs/react.js +++ b/packages/eslint-plugin/configs/react.js @@ -12,6 +12,9 @@ module.exports = { 'react-hooks', ], rules: { + '@wordpress/no-unused-vars-before-return': [ 'error', { + excludePattern: '^use', + } ], 'react/display-name': 'off', 'react/jsx-curly-spacing': [ 'error', { when: 'always', diff --git a/packages/eslint-plugin/configs/recommended.js b/packages/eslint-plugin/configs/recommended.js index 370355bd1d6ad3..f7c264684ecaf9 100644 --- a/packages/eslint-plugin/configs/recommended.js +++ b/packages/eslint-plugin/configs/recommended.js @@ -2,8 +2,8 @@ module.exports = { parser: 'babel-eslint', extends: [ require.resolve( './jsx-a11y.js' ), - require.resolve( './react.js' ), require.resolve( './custom.js' ), + require.resolve( './react.js' ), require.resolve( './esnext.js' ), ], env: { diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md b/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md index 357d0d948ff16f..b8d4ca33efdd8e 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md +++ b/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md @@ -29,3 +29,9 @@ function example( number ) { return number + foo; } ``` + +## Options + +This rule accepts a single options argument: + +- Set the `excludePattern` option to a regular expression string to exempt specific function calls by name. diff --git a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js index 394b88728954df..ae00f9cffe8140 100644 --- a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js +++ b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js @@ -27,6 +27,18 @@ function example( number ) { return number + foo; }`, }, + { + code: ` +function example() { + const foo = doSomeCostlyOperation(); + if ( number > 10 ) { + return number + 1; + } + + return number + foo; +}`, + options: [ { excludePattern: '^do' } ], + }, ], invalid: [ { @@ -41,5 +53,18 @@ function example( number ) { }`, errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], }, + { + code: ` +function example() { + const foo = doSomeCostlyOperation(); + if ( number > 10 ) { + return number + 1; + } + + return number + foo; +}`, + options: [ { excludePattern: '^run' } ], + errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], + }, ], } ); diff --git a/packages/eslint-plugin/rules/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/no-unused-vars-before-return.js index 012b7172bdbb0e..b2951c10f3bfa6 100644 --- a/packages/eslint-plugin/rules/no-unused-vars-before-return.js +++ b/packages/eslint-plugin/rules/no-unused-vars-before-return.js @@ -1,9 +1,22 @@ module.exports = { meta: { type: 'problem', - schema: [], + schema: [ + { + type: 'object', + properties: { + excludePattern: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], }, create( context ) { + const options = context.options[ 0 ] || {}; + const { excludePattern } = options; + return { ReturnStatement( node ) { let functionScope = context.getScope(); @@ -34,6 +47,13 @@ module.exports = { continue; } + if ( + excludePattern !== undefined && + new RegExp( excludePattern ).test( declaratorCandidate.node.init.callee.name ) + ) { + return; + } + // The first entry in `references` is the declaration // itself, which can be ignored. const isUsedBeforeReturn = variable.references.slice( 1 ).some( ( reference ) => { From bed7e0c6a6dff04409c883a0cf7f00fa6560b2db Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Thu, 25 Jul 2019 16:25:41 +0100 Subject: [PATCH 521/664] Use Portal-based slots for the block toolbar (#16421) --- .../src/components/block-list/block.js | 5 +- .../src/components/block-toolbar/index.js | 4 +- .../src/components/block-toolbar/style.scss | 4 ++ .../block-vertical-alignment-toolbar/index.js | 2 +- .../src/components/navigable-toolbar/index.js | 12 +++- .../src/navigable-container/container.js | 22 +++++-- .../src/navigable-container/test/menu.js | 13 ++--- .../src/navigable-container/test/tabbable.js | 13 ++--- packages/components/src/slot-fill/README.md | 2 + packages/components/src/slot-fill/context.js | 57 +++++++++++++++---- packages/components/src/slot-fill/fill.js | 17 ++---- packages/components/src/slot-fill/slot.js | 4 +- .../specs/block-hierarchy-navigation.test.js | 8 +-- 13 files changed, 107 insertions(+), 56 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 320f63555946c8..c7632b0bc7168d 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -322,7 +322,10 @@ function BlockListBlock( { onShiftSelection(); event.preventDefault(); } - } else { + + // Avoid triggering multi-selection if we click toolbars/inspectors + // and all elements that are outside the Block Edit DOM tree. + } else if ( blockNodeRef.current.contains( event.target ) ) { onSelectionStart( clientId ); // Allow user to escape out of a multi-selection to a singular diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 4eb26c2d50f86b..83edd7814a0ed2 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -31,8 +31,8 @@ function BlockToolbar( { blockClientIds, isValid, mode } ) { { mode === 'visual' && isValid && ( <> <BlockSwitcher clientIds={ blockClientIds } /> - <BlockControls.Slot /> - <BlockFormatControls.Slot /> + <BlockControls.Slot bubblesVirtually className="block-editor-block-toolbar__slot" /> + <BlockFormatControls.Slot bubblesVirtually className="block-editor-block-toolbar__slot" /> </> ) } <BlockSettingsMenu clientIds={ blockClientIds } /> diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 3d741214e98799..7d9f29c956e624 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -42,3 +42,7 @@ } } } + +.block-editor-block-toolbar__slot { + display: inline-flex; +} diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js index d3d94a0218b098..86414d5ddf07f5 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js @@ -72,7 +72,7 @@ export default compose( return { isCollapsed: isCollapsed || ! isLargeViewport || ( ! getSettings().hasFixedToolbar && - getBlockRootClientId( clientId ) + !! getBlockRootClientId( clientId ) ), }; } ), diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index b4fbbf7f3f241f..21c6b5dd841e44 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -65,6 +65,17 @@ class NavigableToolbar extends Component { if ( this.props.focusOnMount ) { this.focusToolbar(); } + + // We use DOM event listeners instead of React event listeners + // because we want to catch events from the underlying DOM tree + // The React Tree can be different from the DOM tree when using + // portals. Block Toolbars for instance are rendered in a separate + // React Tree. + this.toolbar.current.addEventListener( 'keydown', this.switchOnKeyDown ); + } + + componentwillUnmount() { + this.toolbar.current.removeEventListener( 'keydown', this.switchOnKeyDown ); } render() { @@ -74,7 +85,6 @@ class NavigableToolbar extends Component { orientation="horizontal" role="toolbar" ref={ this.toolbar } - onKeyDown={ this.switchOnKeyDown } { ...omit( props, [ 'focusOnMount', ] ) } diff --git a/packages/components/src/navigable-container/container.js b/packages/components/src/navigable-container/container.js index 244d646de026fc..be3a9d73be402c 100644 --- a/packages/components/src/navigable-container/container.js +++ b/packages/components/src/navigable-container/container.js @@ -30,6 +30,21 @@ class NavigableContainer extends Component { this.getFocusableIndex = this.getFocusableIndex.bind( this ); } + componentDidMount() { + // We use DOM event listeners instead of React event listeners + // because we want to catch events from the underlying DOM tree + // The React Tree can be different from the DOM tree when using + // portals. Block Toolbars for instance are rendered in a separate + // React Trees. + this.container.addEventListener( 'keydown', this.onKeyDown ); + this.container.addEventListener( 'focus', this.onFocus ); + } + + componentWillUnmount() { + this.container.removeEventListener( 'keydown', this.onKeyDown ); + this.container.removeEventListener( 'focus', this.onFocus ); + } + bindContainer( ref ) { const { forwardedRef } = this.props; this.container = ref; @@ -73,15 +88,13 @@ class NavigableContainer extends Component { // eventToOffset returns undefined if the event is not handled by the component if ( offset !== undefined && stopNavigationEvents ) { // Prevents arrow key handlers bound to the document directly interfering - event.nativeEvent.stopImmediatePropagation(); + event.stopImmediatePropagation(); // When navigating a collection of items, prevent scroll containers // from scrolling. if ( event.target.getAttribute( 'role' ) === 'menuitem' ) { event.preventDefault(); } - - event.stopPropagation(); } if ( ! offset ) { @@ -115,8 +128,7 @@ class NavigableContainer extends Component { 'onlyBrowserTabstops', 'forwardedRef', ] ) } - onKeyDown={ this.onKeyDown } - onFocus={ this.onFocus }> + > { children } </div> ); diff --git a/packages/components/src/navigable-container/test/menu.js b/packages/components/src/navigable-container/test/menu.js index 7dff49f321a134..8f19afe270cb64 100644 --- a/packages/components/src/navigable-container/test/menu.js +++ b/packages/components/src/navigable-container/test/menu.js @@ -26,17 +26,14 @@ function fireKeyDown( container, keyCode, shiftKey ) { stopped: false, }; - container.simulate( 'keydown', { - stopPropagation: () => { - interaction.stopped = true; - }, - preventDefault: () => {}, - nativeEvent: { - stopImmediatePropagation: () => {}, - }, + const event = new window.KeyboardEvent( 'keydown', { keyCode, shiftKey, } ); + event.stopImmediatePropagation = () => { + interaction.stopped = true; + }; + container.getDOMNode().dispatchEvent( event ); return interaction; } diff --git a/packages/components/src/navigable-container/test/tabbable.js b/packages/components/src/navigable-container/test/tabbable.js index b56421df41bb3c..65bfbb387c2f3b 100644 --- a/packages/components/src/navigable-container/test/tabbable.js +++ b/packages/components/src/navigable-container/test/tabbable.js @@ -26,17 +26,14 @@ function fireKeyDown( container, keyCode, shiftKey ) { stopped: false, }; - container.simulate( 'keydown', { - stopPropagation: () => { - interaction.stopped = true; - }, - preventDefault: () => {}, - nativeEvent: { - stopImmediatePropagation: () => {}, - }, + const event = new window.KeyboardEvent( 'keydown', { keyCode, shiftKey, } ); + event.stopImmediatePropagation = () => { + interaction.stopped = true; + }; + container.getDOMNode().dispatchEvent( event ); return interaction; } diff --git a/packages/components/src/slot-fill/README.md b/packages/components/src/slot-fill/README.md index 23bbd7b77f9411..20cd6d708bea6d 100644 --- a/packages/components/src/slot-fill/README.md +++ b/packages/components/src/slot-fill/README.md @@ -70,6 +70,8 @@ Both `Slot` and `Fill` accept a `name` string prop, where a `Slot` with a given - By default, events will bubble to their parents on the DOM hierarchy (native event bubbling) - If `bubblesVirtually` is set to true, events will bubble to their virtual parent in the React elements hierarchy instead. + `Slot` with `bubblesVirtually` set to true also accept an optional `className` to add to the slot container. + `Slot` also accepts optional `children` function prop, which takes `fills` as a param. It allows to perform additional processing and wrap `fills` conditionally. _Example_: diff --git a/packages/components/src/slot-fill/context.js b/packages/components/src/slot-fill/context.js index 7ee0d9806c4003..587c1ccab5ccb8 100644 --- a/packages/components/src/slot-fill/context.js +++ b/packages/components/src/slot-fill/context.js @@ -6,16 +6,18 @@ import { sortBy, forEach, without } from 'lodash'; /** * WordPress dependencies */ -import { Component, createContext } from '@wordpress/element'; +import { Component, createContext, useContext, useState, useEffect } from '@wordpress/element'; -const { Provider, Consumer } = createContext( { +const SlotFillContext = createContext( { registerSlot: () => {}, unregisterSlot: () => {}, registerFill: () => {}, unregisterFill: () => {}, getSlot: () => {}, getFills: () => {}, + subscribe: () => {}, } ); +const { Provider, Consumer } = SlotFillContext; class SlotFillProvider extends Component { constructor() { @@ -27,23 +29,26 @@ class SlotFillProvider extends Component { this.unregisterFill = this.unregisterFill.bind( this ); this.getSlot = this.getSlot.bind( this ); this.getFills = this.getFills.bind( this ); + this.subscribe = this.subscribe.bind( this ); this.slots = {}; this.fills = {}; - this.state = { + this.listeners = []; + this.contextValue = { registerSlot: this.registerSlot, unregisterSlot: this.unregisterSlot, registerFill: this.registerFill, unregisterFill: this.unregisterFill, getSlot: this.getSlot, getFills: this.getFills, + subscribe: this.subscribe, }; } registerSlot( name, slot ) { const previousSlot = this.slots[ name ]; this.slots[ name ] = slot; - this.forceUpdateFills( name ); + this.triggerListeners(); // Sometimes the fills are registered after the initial render of slot // But before the registerSlot call, we need to rerender the slot @@ -75,7 +80,7 @@ class SlotFillProvider extends Component { } delete this.slots[ name ]; - this.forceUpdateFills( name ); + this.triggerListeners(); } unregisterFill( name, instance ) { @@ -106,12 +111,6 @@ class SlotFillProvider extends Component { } ); } - forceUpdateFills( name ) { - forEach( this.fills[ name ], ( instance ) => { - instance.forceUpdate(); - } ); - } - forceUpdateSlot( name ) { const slot = this.getSlot( name ); @@ -120,14 +119,48 @@ class SlotFillProvider extends Component { } } + triggerListeners() { + this.listeners.forEach( ( listener ) => listener() ); + } + + subscribe( listener ) { + this.listeners.push( listener ); + + return () => { + this.listeners = without( this.listeners, listener ); + }; + } + render() { return ( - <Provider value={ this.state }> + <Provider value={ this.contextValue }> { this.props.children } </Provider> ); } } +/** + * React hook returning the active slot given a name. + * + * @param {string} name Slot name. + * @return {Object} Slot object. + */ +export const useSlot = ( name ) => { + const { getSlot, subscribe } = useContext( SlotFillContext ); + const [ slot, setSlot ] = useState( getSlot( name ) ); + + useEffect( () => { + setSlot( getSlot( name ) ); + const unsubscribe = subscribe( () => { + setSlot( getSlot( name ) ); + } ); + + return unsubscribe; + }, [ name ] ); + + return slot; +}; + export default SlotFillProvider; export { Consumer }; diff --git a/packages/components/src/slot-fill/fill.js b/packages/components/src/slot-fill/fill.js index a0c2876643f048..3600e177e5434f 100644 --- a/packages/components/src/slot-fill/fill.js +++ b/packages/components/src/slot-fill/fill.js @@ -6,19 +6,17 @@ import { isFunction } from 'lodash'; /** * WordPress dependencies */ -import { createPortal, useLayoutEffect, useRef, useState } from '@wordpress/element'; +import { createPortal, useLayoutEffect, useRef } from '@wordpress/element'; /** * Internal dependencies */ -import { Consumer } from './context'; +import { Consumer, useSlot } from './context'; let occurrences = 0; -function FillComponent( { name, getSlot, children, registerFill, unregisterFill } ) { - // Random state used to rerender the component if needed, ideally we don't need this - const [ , updateRerenderState ] = useState( {} ); - const rerender = () => updateRerenderState( {} ); +function FillComponent( { name, children, registerFill, unregisterFill } ) { + const slot = useSlot( name ); const ref = useRef( { name, @@ -30,14 +28,12 @@ function FillComponent( { name, getSlot, children, registerFill, unregisterFill } useLayoutEffect( () => { - ref.current.forceUpdate = rerender; registerFill( name, ref.current ); return () => unregisterFill( name, ref.current ); }, [] ); useLayoutEffect( () => { ref.current.children = children; - const slot = getSlot( name ); if ( slot && ! slot.props.bubblesVirtually ) { slot.forceUpdate(); } @@ -53,8 +49,6 @@ function FillComponent( { name, getSlot, children, registerFill, unregisterFill registerFill( name, ref.current ); }, [ name ] ); - const slot = getSlot( name ); - if ( ! slot || ! slot.node || ! slot.props.bubblesVirtually ) { return null; } @@ -69,10 +63,9 @@ function FillComponent( { name, getSlot, children, registerFill, unregisterFill const Fill = ( props ) => ( <Consumer> - { ( { getSlot, registerFill, unregisterFill } ) => ( + { ( { registerFill, unregisterFill } ) => ( <FillComponent { ...props } - getSlot={ getSlot } registerFill={ registerFill } unregisterFill={ unregisterFill } /> diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 6de69f74f865a3..4578656b5efaa9 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -56,10 +56,10 @@ class SlotComponent extends Component { } render() { - const { children, name, bubblesVirtually = false, fillProps = {}, getFills } = this.props; + const { children, name, bubblesVirtually = false, fillProps = {}, getFills, className } = this.props; if ( bubblesVirtually ) { - return <div ref={ this.bindNode } />; + return <div ref={ this.bindNode } className={ className } />; } const fills = map( getFills( name, this ), ( fill ) => { diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 3f0e1c8c3f55c7..378180b58cd824 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -23,7 +23,7 @@ describe( 'Navigating the block hierarchy', () => { await page.click( '[aria-label="Two columns; equal split"]' ); // Add a paragraph in the first column. - await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await pressKeyTimes( 'Tab', 3 ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. @@ -50,7 +50,7 @@ describe( 'Navigating the block hierarchy', () => { await lastColumnsBlockMenuItem.click(); // Insert text in the last column block. - await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await pressKeyTimes( 'Tab', 3 ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. @@ -65,7 +65,7 @@ describe( 'Navigating the block hierarchy', () => { await page.click( '[aria-label="Two columns; equal split"]' ); // Add a paragraph in the first column. - await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await pressKeyTimes( 'Tab', 3 ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. @@ -92,7 +92,7 @@ describe( 'Navigating the block hierarchy', () => { await page.keyboard.press( 'Enter' ); // Insert text in the last column block - await pressKeyTimes( 'Tab', 5 ); // Tab to inserter. + await pressKeyTimes( 'Tab', 3 ); // Tab to inserter. await page.keyboard.press( 'Enter' ); // Activate inserter. await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. From bd4c495bdf38b176c8a8e7b94c9215d738ce9f32 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 25 Jul 2019 11:41:48 -0400 Subject: [PATCH 522/664] Framework: Restrict combineReducers usage to data module (#16752) * Core Data: Use combineReducers from data module * Framework: Restrict combineReducers usage to data module --- .eslintrc.js | 4 ++++ packages/core-data/src/queried-data/reducer.js | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 64ce20d480d398..2a6ad63b1eafc7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,6 +57,10 @@ module.exports = { selector: 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] Literal[value=/\\.{3}/]', message: 'Use ellipsis character (…) in place of three dots', }, + { + selector: 'ImportDeclaration[source.value="redux"] Identifier.imported[name="combineReducers"]', + message: 'Use `combineReducers` from `@wordpress/data`', + }, { selector: 'ImportDeclaration[source.value="lodash"] Identifier.imported[name="memoize"]', message: 'Use memize instead of Lodash’s memoize', diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 2ddf82f9045f46..8e78544dcdbe2f 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -1,9 +1,13 @@ /** * External dependencies */ -import { combineReducers } from 'redux'; import { keyBy, map, flowRight } from 'lodash'; +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; + /** * Internal dependencies */ From dd34d327c2fca10658ace8e97884b8a11193664c Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 25 Jul 2019 17:45:44 +0200 Subject: [PATCH 523/664] Move the post title selection state to the store and update PostTitle (#16704) * Move the post title selection state to the store and update PostTitle * Fix jsdoc type returned for postTitle reducer * Update native version of VisualEditor to use the store to know if the post title is selected * Update doc * Remove unused clearSelectedBlock props in PostTitle web and bring back focus in native version * Fork core/editor store for native instead of modifying it * Move tests to native * Update docs * Revert changes to web PostTitle * Fix post title blur on block selection * Update the isPostTitleSelected state if any other block is selected --- .../components/visual-editor/index.native.js | 37 +---------- .../src/components/post-title/index.native.js | 64 +++++++++++++------ packages/editor/src/store/actions.native.js | 16 +++++ packages/editor/src/store/reducer.native.js | 64 +++++++++++++++++++ packages/editor/src/store/selectors.native.js | 13 ++++ .../editor/src/store/test/actions.native.js | 17 +++++ .../editor/src/store/test/reducer.native.js | 34 ++++++++++ .../editor/src/store/test/selectors.native.js | 28 ++++++++ 8 files changed, 220 insertions(+), 53 deletions(-) create mode 100644 packages/editor/src/store/actions.native.js create mode 100644 packages/editor/src/store/reducer.native.js create mode 100644 packages/editor/src/store/selectors.native.js create mode 100644 packages/editor/src/store/test/actions.native.js create mode 100644 packages/editor/src/store/test/reducer.native.js create mode 100644 packages/editor/src/store/test/selectors.native.js diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js index 708b96c8289aea..092ea638f68026 100644 --- a/packages/edit-post/src/components/visual-editor/index.native.js +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -15,33 +15,6 @@ import { ReadableContentView } from '@wordpress/components'; import styles from './style.scss'; class VisualEditor extends Component { - constructor() { - super( ...arguments ); - - this.onPostTitleSelect = this.onPostTitleSelect.bind( this ); - this.onPostTitleUnselect = this.onPostTitleUnselect.bind( this ); - - this.state = { - isPostTitleSelected: false, - }; - } - - static getDerivedStateFromProps( props ) { - if ( props.isAnyBlockSelected ) { - return { isPostTitleSelected: false }; - } - return null; - } - - onPostTitleSelect() { - this.setState( { isPostTitleSelected: true } ); - this.props.clearSelectedBlock(); - } - - onPostTitleUnselect() { - this.setState( { isPostTitleSelected: false } ); - } - renderHeader() { const { editTitle, @@ -55,9 +28,6 @@ class VisualEditor extends Component { innerRef={ setTitleRef } title={ title } onUpdate={ editTitle } - onSelect={ this.onPostTitleSelect } - onUnselect={ this.onPostTitleUnselect } - isSelected={ this.state.isPostTitleSelected } placeholder={ __( 'Add title' ) } borderStyle={ this.props.isFullyBordered ? @@ -93,7 +63,7 @@ class VisualEditor extends Component { isFullyBordered={ isFullyBordered } rootViewHeight={ rootViewHeight } safeAreaBottomInset={ safeAreaBottomInset } - isPostTitleSelected={ this.state.isPostTitleSelected } + isPostTitleSelected={ this.props.isPostTitleSelected } onBlockTypeSelected={ this.onPostTitleUnselect } /> </BlockEditorProvider> @@ -106,14 +76,13 @@ export default compose( [ const { getEditorBlocks, getEditedPostAttribute, + isPostTitleSelected, } = select( 'core/editor' ); - const { getSelectedBlockClientId } = select( 'core/block-editor' ); - return { blocks: getEditorBlocks(), title: getEditedPostAttribute( 'title' ), - isAnyBlockSelected: !! getSelectedBlockClientId(), + isPostTitleSelected: isPostTitleSelected(), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index d91d0220753dfe..ce845b8fb630f6 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import { Component } from '@wordpress/element'; import { RichText } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; -import { withDispatch } from '@wordpress/data'; +import { withDispatch, withSelect } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; import { __, sprintf } from '@wordpress/i18n'; @@ -22,6 +22,13 @@ import { pasteHandler } from '@wordpress/blocks'; import styles from './style.scss'; class PostTitle extends Component { + componentDidUpdate( prevProps ) { + // Unselect if any other block is selected + if ( this.props.isSelected && ! prevProps.isAnyBlockSelected && this.props.isAnyBlockSelected ) { + this.props.onUnselect(); + } + } + componentDidMount() { if ( this.props.innerRef ) { this.props.innerRef( this ); @@ -91,27 +98,46 @@ class PostTitle extends Component { } } -const applyWithDispatch = withDispatch( ( dispatch ) => { - const { - undo, - redo, - } = dispatch( 'core/editor' ); +export default compose( + withSelect( ( select ) => { + const { + isPostTitleSelected, + } = select( 'core/editor' ); - const { - insertDefaultBlock, - } = dispatch( 'core/block-editor' ); + const { getSelectedBlockClientId } = select( 'core/block-editor' ); - return { - onEnterPress() { - insertDefaultBlock( undefined, undefined, 0 ); - }, - onUndo: undo, - onRedo: redo, - }; -} ); + return { + isAnyBlockSelected: !! getSelectedBlockClientId(), + isSelected: isPostTitleSelected(), + }; + } ), + withDispatch( ( dispatch ) => { + const { + undo, + redo, + togglePostTitleSelection, + } = dispatch( 'core/editor' ); -export default compose( - applyWithDispatch, + const { + clearSelectedBlock, + insertDefaultBlock, + } = dispatch( 'core/block-editor' ); + + return { + onEnterPress() { + insertDefaultBlock( undefined, undefined, 0 ); + }, + onUndo: undo, + onRedo: redo, + onSelect() { + togglePostTitleSelection( true ); + clearSelectedBlock(); + }, + onUnselect() { + togglePostTitleSelection( false ); + }, + }; + } ), withInstanceId, withFocusOutside )( PostTitle ); diff --git a/packages/editor/src/store/actions.native.js b/packages/editor/src/store/actions.native.js new file mode 100644 index 00000000000000..3d638cbc2be2cf --- /dev/null +++ b/packages/editor/src/store/actions.native.js @@ -0,0 +1,16 @@ + +export * from './actions.js'; + +/** + * Returns an action object that enables or disables post title selection. + * + * @param {boolean} [isSelected=true] Whether post title is currently selected. + + * @return {Object} Action object. + */ +export function togglePostTitleSelection( isSelected = true ) { + return { + type: 'TOGGLE_POST_TITLE_SELECTION', + isSelected, + }; +} diff --git a/packages/editor/src/store/reducer.native.js b/packages/editor/src/store/reducer.native.js new file mode 100644 index 00000000000000..82b3689a98ead0 --- /dev/null +++ b/packages/editor/src/store/reducer.native.js @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import optimist from 'redux-optimist'; + +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + editor, + initialEdits, + currentPost, + preferences, + saving, + postLock, + reusableBlocks, + template, + previewLink, + postSavingLock, + isReady, + editorSettings, +} from './reducer.js'; + +export * from './reducer.js'; + +/** + * Reducer returning the post title state. + * + * @param {PostTitleState} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export const postTitle = combineReducers( { + isSelected( state = false, action ) { + switch ( action.type ) { + case 'TOGGLE_POST_TITLE_SELECTION': + return action.isSelected; + } + + return state; + }, +} ); + +export default optimist( combineReducers( { + editor, + initialEdits, + currentPost, + preferences, + saving, + postLock, + reusableBlocks, + template, + previewLink, + postSavingLock, + isReady, + editorSettings, + postTitle, +} ) ); diff --git a/packages/editor/src/store/selectors.native.js b/packages/editor/src/store/selectors.native.js new file mode 100644 index 00000000000000..8c6ead8e97ba2f --- /dev/null +++ b/packages/editor/src/store/selectors.native.js @@ -0,0 +1,13 @@ + +export * from './selectors.js'; + +/** + * Returns true if post title is selected. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether current post title is selected. + */ +export function isPostTitleSelected( state ) { + return state.postTitle.isSelected; +} diff --git a/packages/editor/src/store/test/actions.native.js b/packages/editor/src/store/test/actions.native.js new file mode 100644 index 00000000000000..b39086244d98de --- /dev/null +++ b/packages/editor/src/store/test/actions.native.js @@ -0,0 +1,17 @@ + +/** + * Internal dependencies + */ +import { togglePostTitleSelection } from '../actions'; + +describe( 'Editor actions', () => { + describe( 'togglePostTitleSelection', () => { + it( 'should return the TOGGLE_POST_TITLE_SELECTION action', () => { + const result = togglePostTitleSelection( true ); + expect( result ).toEqual( { + type: 'TOGGLE_POST_TITLE_SELECTION', + isSelected: true, + } ); + } ); + } ); +} ); diff --git a/packages/editor/src/store/test/reducer.native.js b/packages/editor/src/store/test/reducer.native.js new file mode 100644 index 00000000000000..d97ca0d9e5c9df --- /dev/null +++ b/packages/editor/src/store/test/reducer.native.js @@ -0,0 +1,34 @@ +/** + * Internal dependencies + */ +import { + postTitle, +} from '../reducer'; + +describe( 'state native', () => { + describe( 'postTitle', () => { + describe( 'isSelected()', () => { + it( 'should not be selected by default', () => { + expect( postTitle( undefined, {} ).isSelected ).toBe( false ); + } ); + + it( 'should return false if not selecting the post title', () => { + const action = { + type: 'TOGGLE_POST_TITLE_SELECTION', + isSelected: false, + }; + + expect( postTitle( { isSelected: true }, action ).isSelected ).toBe( false ); + } ); + + it( 'should return true if selecting the post title', () => { + const action = { + type: 'TOGGLE_POST_TITLE_SELECTION', + isSelected: true, + }; + + expect( postTitle( { isSelected: false }, action ).isSelected ).toBe( true ); + } ); + } ); + } ); +} ); diff --git a/packages/editor/src/store/test/selectors.native.js b/packages/editor/src/store/test/selectors.native.js new file mode 100644 index 00000000000000..958a68e651f743 --- /dev/null +++ b/packages/editor/src/store/test/selectors.native.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import { isPostTitleSelected } from '../selectors'; + +describe( 'selectors native', () => { + describe( 'isPostTitleSelected', () => { + it( 'should return true if the post title is selected', () => { + const state = { + postTitle: { + isSelected: true, + }, + }; + + expect( isPostTitleSelected( state ) ).toBe( true ); + } ); + + it( 'should return false if the post title is not selected', () => { + const state = { + postTitle: { + isSelected: false, + }, + }; + + expect( isPostTitleSelected( state ) ).toBe( false ); + } ); + } ); +} ); From e54043aa1cd2893c7ec3e1244413777d263f82fe Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Fri, 26 Jul 2019 03:41:37 -0400 Subject: [PATCH 524/664] fix(editor): remove nonexistent `getBlockDependantsCacheBust` selector reference (#16756) --- .../developers/data/data-core-editor.md | 6 ------ packages/editor/src/store/selectors.js | 5 ----- 2 files changed, 11 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index e3456d65168dbd..b38e4ede0bf678 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -118,12 +118,6 @@ _Related_ - getBlockCount in core/block-editor store. -<a name="getBlockDependantsCacheBust" href="#getBlockDependantsCacheBust">#</a> **getBlockDependantsCacheBust** - -_Related_ - -- getBlockDependantsCacheBust in core/block-editor store. - <a name="getBlockHierarchyRootClientId" href="#getBlockHierarchyRootClientId">#</a> **getBlockHierarchyRootClientId** _Related_ diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 7570e8b7848de6..fa6843010ab704 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1168,11 +1168,6 @@ function getBlockEditorSelector( name ) { } ); } -/** - * @see getBlockDependantsCacheBust in core/block-editor store. - */ -export const getBlockDependantsCacheBust = getBlockEditorSelector( 'getBlockDependantsCacheBust' ); - /** * @see getBlockName in core/block-editor store. */ From 297396bd5197ffb6e3a3b74e6c101f468a9f1a57 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Fri, 26 Jul 2019 15:42:33 +0800 Subject: [PATCH 525/664] Fix pasting content into inner blocks (#16717) --- packages/block-editor/src/store/reducer.js | 18 +++++++++++++++--- .../block-editor/src/store/test/reducer.js | 8 ++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 97a366e8884d2f..df81bd5df22f0b 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -225,6 +225,17 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { } newState.cache = state.cache ? state.cache : {}; + /** + * For each clientId provided, traverses up parents, adding the provided clientIds + * and each parent's clientId to the returned array. + * + * When calling this function consider that it uses the old state, so any state + * modifications made by the `reducer` will not be present. + * + * @param {Array} clientIds an Array of block clientIds. + * + * @return {Array} The provided clientIds and all of their parent clientIds. + */ const getBlocksWithParentsClientIds = ( clientIds ) => { return clientIds.reduce( ( result, clientId ) => { let current = clientId; @@ -264,11 +275,12 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { }; break; case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': + const parentClientIds = fillKeysWithEmptyObject( getBlocksWithParentsClientIds( action.replacedClientIds ) ); + newState.cache = { ...omit( newState.cache, action.replacedClientIds ), - ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( keys( flattenBlocks( action.blocks ) ) ), - ), + ...omit( parentClientIds, action.replacedClientIds ), + ...fillKeysWithEmptyObject( keys( flattenBlocks( action.blocks ) ) ), }; break; case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 0626f57fb41291..7eb698be778650 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -759,21 +759,29 @@ describe( 'state', () => { blocks: [ wrapperBlock ], } ); + const originalWrapperBlockCacheKey = original.cache[ wrapperBlock.clientId ]; + const state = blocks( original, { type: 'REPLACE_BLOCKS', clientIds: [ nestedBlock.clientId ], blocks: [ replacementBlock ], } ); + const newWrapperBlockCacheKey = state.cache[ wrapperBlock.clientId ]; + + expect( newWrapperBlockCacheKey ).not.toBe( originalWrapperBlockCacheKey ); + expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], [ wrapperBlock.clientId ]: [ replacementBlock.clientId ], [ replacementBlock.clientId ]: [], } ); + expect( state.parents ).toEqual( { [ wrapperBlock.clientId ]: '', [ replacementBlock.clientId ]: wrapperBlock.clientId, } ); + expect( state.cache ).toEqual( { [ wrapperBlock.clientId ]: {}, [ replacementBlock.clientId ]: {}, From b8df976f963b8923c27e1f40db16de509ed3fabb Mon Sep 17 00:00:00 2001 From: Dominik Schilling <dominikschilling+git@gmail.com> Date: Fri, 26 Jul 2019 09:43:09 +0200 Subject: [PATCH 526/664] Support `hideLabelFromVision` prop for components using `BaseControl` (#16701) --- packages/components/CHANGELOG.md | 1 + packages/components/src/base-control/README.md | 7 +++++++ packages/components/src/select-control/README.md | 6 ++++++ packages/components/src/text-control/README.md | 6 ++++++ packages/components/src/text-control/index.js | 4 ++-- packages/components/src/textarea-control/README.md | 7 +++++++ packages/components/src/textarea-control/index.js | 4 ++-- 7 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index a2ce1b818505e4..26c1c2ecdaed68 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -3,6 +3,7 @@ ### New Features - Added a new `popoverProps` prop to the `Dropdown` component which allows users of the `Dropdown` component to pass props directly to the `PopOver` component. +- Added and documented `hideLabelFromVision` prop to `BaseControl` used by `SelectControl`, `TextControl`, and `TextareaControl`. ### Bug Fixes diff --git a/packages/components/src/base-control/README.md b/packages/components/src/base-control/README.md index f451a75b6ee4fd..7b20ced34734d9 100644 --- a/packages/components/src/base-control/README.md +++ b/packages/components/src/base-control/README.md @@ -41,6 +41,13 @@ If this property is added, a label will be generated using label property as the - Type: `String` - Required: No +### hideLabelFromVision + +If true, the label will only be visible to screen readers. + +- Type: `Boolean` +- Required: No + ### help If this property is added, a help text will be generated using help property as the content. diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index 30fcb148ba8cc6..ac58106dfd0f7b 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -127,6 +127,12 @@ If this property is added, a label will be generated using label property as the - Type: `String` - Required: No +#### hideLabelFromVision + +If true, the label will only be visible to screen readers. +- Type: `Boolean` +- Required: No + #### help If this property is added, a help text will be generated using help property as the content. diff --git a/packages/components/src/text-control/README.md b/packages/components/src/text-control/README.md index 2fa725b3c3e88b..91bd724923456e 100644 --- a/packages/components/src/text-control/README.md +++ b/packages/components/src/text-control/README.md @@ -80,6 +80,12 @@ If this property is added, a label will be generated using label property as the - Type: `String` - Required: No +#### hideLabelFromVision +If true, the label will only be visible to screen readers. + +- Type: `Boolean` +- Required: No + #### help If this property is added, a help text will be generated using help property as the content. diff --git a/packages/components/src/text-control/index.js b/packages/components/src/text-control/index.js index 1a215882d2c801..61504cc7da5cfb 100644 --- a/packages/components/src/text-control/index.js +++ b/packages/components/src/text-control/index.js @@ -8,12 +8,12 @@ import { withInstanceId } from '@wordpress/compose'; */ import BaseControl from '../base-control'; -function TextControl( { label, value, help, className, instanceId, onChange, type = 'text', ...props } ) { +function TextControl( { label, hideLabelFromVision, value, help, className, instanceId, onChange, type = 'text', ...props } ) { const id = `inspector-text-control-${ instanceId }`; const onChangeValue = ( event ) => onChange( event.target.value ); return ( - <BaseControl label={ label } id={ id } help={ help } className={ className }> + <BaseControl label={ label } hideLabelFromVision={ hideLabelFromVision } id={ id } help={ help } className={ className }> <input className="components-text-control__input" type={ type } id={ id } diff --git a/packages/components/src/textarea-control/README.md b/packages/components/src/textarea-control/README.md index bf5ff5164300e0..2adfffdaf7584f 100644 --- a/packages/components/src/textarea-control/README.md +++ b/packages/components/src/textarea-control/README.md @@ -104,6 +104,13 @@ If this property is added, a label will be generated using label property as the - Type: `String` - Required: No +#### hideLabelFromVision + +If true, the label will only be visible to screen readers. + +- Type: `Boolean` +- Required: No + #### help If this property is added, a help text will be generated using help property as the content. diff --git a/packages/components/src/textarea-control/index.js b/packages/components/src/textarea-control/index.js index e0f3ce6de429da..45158ca2ec7474 100644 --- a/packages/components/src/textarea-control/index.js +++ b/packages/components/src/textarea-control/index.js @@ -8,12 +8,12 @@ import { withInstanceId } from '@wordpress/compose'; */ import BaseControl from '../base-control'; -function TextareaControl( { label, value, help, instanceId, onChange, rows = 4, className, ...props } ) { +function TextareaControl( { label, hideLabelFromVision, value, help, instanceId, onChange, rows = 4, className, ...props } ) { const id = `inspector-textarea-control-${ instanceId }`; const onChangeValue = ( event ) => onChange( event.target.value ); return ( - <BaseControl label={ label } id={ id } help={ help } className={ className }> + <BaseControl label={ label } hideLabelFromVision={ hideLabelFromVision } id={ id } help={ help } className={ className }> <textarea className="components-textarea-control__input" id={ id } From fc0b94bf565b689247164da49a134d4dbfd2b484 Mon Sep 17 00:00:00 2001 From: Ryan Welcher <ryan.welcher@10up.com> Date: Fri, 26 Jul 2019 03:44:30 -0400 Subject: [PATCH 527/664] Fix/plugin document setting panel docs (#16620) --- .../developers/slotfills/README.md | 1 + .../plugin-document-setting-panel.md | 26 +++++++++++++++++++ docs/manifest-devhub.json | 6 +++++ docs/toc.json | 1 + 4 files changed, 34 insertions(+) create mode 100644 docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md diff --git a/docs/designers-developers/developers/slotfills/README.md b/docs/designers-developers/developers/slotfills/README.md index 61e642658a0350..991118bdcbff60 100644 --- a/docs/designers-developers/developers/slotfills/README.md +++ b/docs/designers-developers/developers/slotfills/README.md @@ -100,6 +100,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { There are currently seven available SlotFills in the `edit-post` package. Please refer to the individual items below for usage and example details: * [PluginBlockSettingsMenuItem](./plugin-block-settings-menu-item.md) +* [PluginDocumentSettingPanel](./plugin-document-setting-panel.md) * [PluginMoreMenuItem](./plugin-more-menu-item.md) * [PluginPostPublishPanel](./plugin-post-publish-panel.md) * [PluginPostStatusInfo](./plugin-post-status-info.md) diff --git a/docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md b/docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md new file mode 100644 index 00000000000000..75f0a3638f1b4c --- /dev/null +++ b/docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md @@ -0,0 +1,26 @@ +# PluginDocumentSettingPanel + +This SlotFill allows registering a UI to edit Document settings. + +## Available Props +* __name__ `string`: A string identifying the panel. +* __className__ `string`: An optional class name added to the sidebar body. +* __title__ `string`: Title displayed at the top of the sidebar. +* __icon__ `(string|Element)`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. + +## Example +```js +const { registerPlugin } = wp.plugins; +const { PluginDocumentSettingPanel } = wp.editPost; + +const PluginDocumentSettingPanelDemo = () => ( + <PluginDocumentSettingPanel + name="custom-panel" + title="Custom Panel" + className="custom-panel" + > + Custom Panel Contents + </PluginDocumentSettingPanel> +); +registerPlugin( 'plugin-document-setting-panel-demo', { render: PluginDocumentSettingPanelDemo, icon: 'palmtree' } ); +``` \ No newline at end of file diff --git a/docs/manifest-devhub.json b/docs/manifest-devhub.json index 1a5dc5fd60f139..8c96482e340a30 100644 --- a/docs/manifest-devhub.json +++ b/docs/manifest-devhub.json @@ -101,6 +101,12 @@ "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md", "parent": "slotfills" }, + { + "title": "PluginDocumentSettingPanel", + "slug": "plugin-document-setting-panel", + "markdown_source": "../docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md", + "parent": "slotfills" + }, { "title": "PluginMoreMenuItem", "slug": "plugin-more-menu-item", diff --git a/docs/toc.json b/docs/toc.json index 8b3f3b4d8a4fe3..8f42aa7d2fa717 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -18,6 +18,7 @@ ] }, {"docs/designers-developers/developers/slotfills/README.md": [ { "docs/designers-developers/developers/slotfills/plugin-block-settings-menu-item.md": [] }, + { "docs/designers-developers/developers/slotfills/plugin-document-setting-panel.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-more-menu-item.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-post-publish-panel.md": [] }, { "docs/designers-developers/developers/slotfills/plugin-post-status-info.md": [] }, From 2e43198d1adfc90126fef5586bb9c37c7a4124f3 Mon Sep 17 00:00:00 2001 From: Greg Sullivan <greg@swivelbase.com> Date: Fri, 26 Jul 2019 00:45:31 -0700 Subject: [PATCH 528/664] Fix aspect ratio typo and recalculate padding in embed block CSS (#16573) --- packages/block-library/src/embed/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/embed/style.scss b/packages/block-library/src/embed/style.scss index 6f3478f3bb1089..5409085faf0206 100644 --- a/packages/block-library/src/embed/style.scss +++ b/packages/block-library/src/embed/style.scss @@ -63,8 +63,8 @@ padding-top: 100%; // 1 / 1 * 100 } - &.wp-embed-aspect-9-6 .wp-block-embed__wrapper::before { - padding-top: 66.66%; // 6 / 9 * 100 + &.wp-embed-aspect-9-16 .wp-block-embed__wrapper::before { + padding-top: 177.78%; // 16 / 9 * 100 } &.wp-embed-aspect-1-2 .wp-block-embed__wrapper::before { From 45c31692909bdc59ea85c18167cd04be7c45bd3e Mon Sep 17 00:00:00 2001 From: Adam Silverstein <adam@10up.com> Date: Fri, 26 Jul 2019 01:55:49 -0600 Subject: [PATCH 529/664] Date/Time picker: ensure hour/minute fields are always shown left to right (#16375) --- packages/components/src/date-time/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index d25d923dd24d94..71c44cbcfd3709 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -135,6 +135,12 @@ .components-datetime__time-field { + &-time { + /*rtl:ignore*/ + direction: ltr; + } + + &.am-pm button { font-size: 11px; font-weight: 600; From 944392db2be77c7da179af6e5c22fb5a19bed032 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 26 Jul 2019 09:04:21 +0100 Subject: [PATCH 530/664] Add: Simple way to register block styles (#16356) Co-Authored-By: Pascal Birchler <pascalb@google.com> --- lib/blocks.php | 75 +++++++++++- lib/class-wp-block-styles-registry.php | 161 +++++++++++++++++++++++++ lib/load.php | 1 + 3 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 lib/class-wp-block-styles-registry.php diff --git a/lib/blocks.php b/lib/blocks.php index fc3adb8a09efa3..cf66e1d4044a89 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -1,6 +1,6 @@ <?php /** - * Block registration functions. + * Block and style registration functions. * * @package gutenberg */ @@ -45,3 +45,76 @@ function gutenberg_reregister_core_block_types() { } } add_action( 'init', 'gutenberg_reregister_core_block_types' ); + +/** + * Registers a new block style. + * + * @param string $block_name Block type name including namespace. + * @param array $style_properties Array containing the properties of the style name, label, style (name of the stylesheet to be enqueued), inline_style (string containing the CSS to be added). + * + * @return boolean True if the block style was registered with success and false otherwise. + */ +function register_block_style( $block_name, $style_properties ) { + return WP_Block_Styles_Registry::get_instance()->register( $block_name, $style_properties ); +} + +/** + * Unregisters a block style. + * + * @param string $block_name Block type name including namespace. + * @param array $block_style_name Block style name. + * + * @return boolean True if the block style was unregistered with success and false otherwise. + */ +function unregister_block_style( $block_name, $block_style_name ) { + return WP_Block_Styles_Registry::get_instance()->unregister( $block_name, $block_style_name ); +} + +/** + * Function responsible for enqueuing the styles required for block styles functionality on the editor and on the frontend. + */ +function enqueue_block_styles_assets() { + $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); + + foreach ( $block_styles as $styles ) { + foreach ( $styles as $style_properties ) { + if ( isset( $style_properties['style_handle'] ) ) { + wp_enqueue_style( $style_properties['style_handle'] ); + } + if ( isset( $style_properties['inline_style'] ) ) { + wp_add_inline_style( 'wp-block-library', $style_properties['inline_style'] ); + } + } + } +} +add_action( 'enqueue_block_assets', 'enqueue_block_styles_assets', 30 ); + +/** + * Function responsible for enqueuing the assets required for block styles functionality on the editor. + */ +function enqueue_editor_block_styles_assets() { + $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); + + $register_script_lines = array( '( function() {' ); + foreach ( $block_styles as $block_name => $styles ) { + foreach ( $styles as $style_properties ) { + $register_script_lines[] = sprintf( + ' wp.blocks.registerBlockStyle( \'%s\', %s );', + $block_name, + wp_json_encode( + array( + 'name' => $style_properties['name'], + 'label' => $style_properties['label'], + ) + ) + ); + } + } + $register_script_lines[] = '} )();'; + $inline_script = implode( "\n", $register_script_lines ); + + wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, true ); + wp_add_inline_script( 'wp-block-styles', $inline_script ); + wp_enqueue_script( 'wp-block-styles' ); +} +add_action( 'enqueue_block_editor_assets', 'enqueue_editor_block_styles_assets' ); diff --git a/lib/class-wp-block-styles-registry.php b/lib/class-wp-block-styles-registry.php new file mode 100644 index 00000000000000..a5c2d9edae8b3b --- /dev/null +++ b/lib/class-wp-block-styles-registry.php @@ -0,0 +1,161 @@ +<?php +/** + * Blocks API: WP_Block_Styles_Registry class + * + * @package Gutenberg + * @since 6.2.0 + */ + +/** + * Class used for interacting with block styles. + * + * @since 6.2.0 + */ +final class WP_Block_Styles_Registry { + /** + * Registered block styles, as `$block_name => $block_style_name => $block_style_properties` multidimensional arrays. + * + * @since 6.2.0 + * @var array + */ + private $registered_block_styles = array(); + + /** + * Container for the main instance of the class. + * + * @since 6.2.0 + * @var WP_Block_Styles_Registry|null + */ + private static $instance = null; + + /** + * Registers a block style. + * + * @since 6.2.0 + * + * @param string $block_name Block type name including namespace. + * @param array $style_properties Array containing the properties of the style name, label, style (name of the stylesheet to be enqueued), inline_style (string containing the CSS to be added). + * + * @return boolean True if the block style was registered with success and false otherwise. + */ + public function register( $block_name, $style_properties ) { + + if ( ! isset( $block_name ) || ! is_string( $block_name ) ) { + $message = __( 'Block name name must be a string.', 'gutenberg' ); + _doing_it_wrong( __METHOD__, $message, '6.2.0' ); + return false; + } + + if ( ! isset( $style_properties['name'] ) || ! is_string( $style_properties['name'] ) ) { + $message = __( 'Block style name must be a string.', 'gutenberg' ); + _doing_it_wrong( __METHOD__, $message, '6.2.0' ); + return false; + } + + $block_style_name = $style_properties['name']; + + if ( ! isset( $this->registered_block_styles[ $block_name ] ) ) { + $this->registered_block_styles[ $block_name ] = array(); + } + $this->registered_block_styles[ $block_name ][ $block_style_name ] = $style_properties; + + return true; + } + + /** + * Unregisters a block style. + * + * @param string $block_name Block type name including namespace. + * @param array $block_style_name Block style name. + * + * @return boolean True if the block style was unregistered with success and false otherwise. + */ + public function unregister( $block_name, $block_style_name ) { + if ( ! $this->is_registered( $block_name, $block_style_name ) ) { + /* translators: 1: block name, 2: block style name */ + $message = sprintf( __( 'Block "%1$s" does not contain a style named "%2$s.".', 'gutenberg' ), $block_name, $block_style_name ); + _doing_it_wrong( __METHOD__, $message, '6.2.0' ); + return false; + } + + unset( $this->registered_block_styles[ $block_name ][ $block_style_name ] ); + + return true; + } + + /** + * Retrieves an array containing the properties of a registered block style. + * + * @since 6.2.0 + * + * @param string $block_name Block type name including namespace. + * @param array $block_style_name Block style name. + * + * @return array Registered block style properties. + */ + public function get_registered( $block_name, $block_style_name ) { + if ( ! $this->is_registered( $block_name, $block_style_name ) ) { + return null; + } + + return $this->registered_block_styles[ $block_name ][ $block_style_name ]; + } + + /** + * Retrieves all registered block styles. + * + * @since 6.2.0 + * + * @return array Array of arrays containing the registered block styles properties grouped per block, and per style. + */ + public function get_all_registered() { + return $this->registered_block_styles; + } + + /** + * Retrieves registered block styles for a specific block. + * + * @since 6.2.0 + * + * @param string $block_name Block type name including namespace. + * + * @return array Array whose keys are block style names and whose value are block style properties. + */ + public function get_registered_styles_for_block( $block_name ) { + if ( isset( $this->registered_block_styles[ $block_name ] ) ) { + return $this->registered_block_styles[ $block_name ]; + } + return array(); + } + + /** + * Checks if a block style is registered. + * + * @since 6.2.0 + * + * @param string $block_name Block type name including namespace. + * @param array $block_style_name Block style name. + * + * @return bool True if the block style is registered, false otherwise. + */ + public function is_registered( $block_name, $block_style_name ) { + return isset( $this->registered_block_styles[ $block_name ][ $block_style_name ] ); + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.2.0 + * + * @return WP_Block_Styles_Registry The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } +} diff --git a/lib/load.php b/lib/load.php index 7a3c97fb65553b..c34a8d4fc4d408 100644 --- a/lib/load.php +++ b/lib/load.php @@ -29,6 +29,7 @@ } require dirname( __FILE__ ) . '/compat.php'; +require dirname( __FILE__ ) . '/class-wp-block-styles-registry.php'; require dirname( __FILE__ ) . '/blocks.php'; require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/demo.php'; From d0ea5bffd1665e4e1dbf42775fa3326a474248f4 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 26 Jul 2019 09:22:29 +0100 Subject: [PATCH 531/664] Fix race condition in block moving animation. (#16750) --- .../src/components/block-list/block.js | 11 ++++++-- .../components/block-list/moving-animation.js | 28 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index c7632b0bc7168d..890bfa4ff7aea5 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -252,7 +252,7 @@ function BlockListBlock( { }, [ isFirstMultiSelected ] ); // Block Reordering animation - const style = useMovingAnimation( wrapper, isSelected || isPartOfMultiSelection, enableAnimation, animateOnChange ); + const animationStyle = useMovingAnimation( wrapper, isSelected || isPartOfMultiSelection, enableAnimation, animateOnChange ); // Other event handlers @@ -469,8 +469,15 @@ function BlockListBlock( { aria-label={ blockLabel } childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } tagName={ animated.div } - style={ style } { ...blockWrapperProps } + style={ + wrapperProps && wrapperProps.style ? + { + ...wrapperProps.style, + ...animationStyle, + } : + animationStyle + } > { shouldShowInsertionPoint && ( <BlockInsertionPoint diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js index 393bfa33e2a7ca..4cb9b0ea9fa296 100644 --- a/packages/block-editor/src/components/block-list/moving-animation.js +++ b/packages/block-editor/src/components/block-list/moving-animation.js @@ -6,9 +6,17 @@ import { useSpring, interpolate } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { useState, useLayoutEffect } from '@wordpress/element'; +import { useState, useLayoutEffect, useReducer } from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; +/** + * Simple reducer used to increment a counter. + * + * @param {number} state Previous counter value. + * @return {number} New state value. + */ +const counterReducer = ( state ) => state + 1; + /** * Hook used to compute the styles required to move a div into a new position. * @@ -29,10 +37,17 @@ import { useReducedMotion } from '@wordpress/compose'; */ function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationOnChange ) { const prefersReducedMotion = useReducedMotion() || ! enableAnimation; - const [ resetAnimation, setResetAnimation ] = useState( false ); + const [ triggeredAnimation, triggerAnimation ] = useReducer( counterReducer, 0 ); + const [ finishedAnimation, endAnimation ] = useReducer( counterReducer, 0 ); const [ transform, setTransform ] = useState( { x: 0, y: 0 } ); const previous = ref.current ? ref.current.getBoundingClientRect() : null; + + useLayoutEffect( () => { + if ( triggeredAnimation ) { + endAnimation(); + } + }, [ triggeredAnimation ] ); useLayoutEffect( () => { if ( prefersReducedMotion ) { return; @@ -46,21 +61,16 @@ function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationO ref.current.style.transform = newTransform.x === 0 && newTransform.y === 0 ? undefined : `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; - setResetAnimation( true ); + triggerAnimation(); setTransform( newTransform ); }, [ triggerAnimationOnChange ] ); - useLayoutEffect( () => { - if ( resetAnimation ) { - setResetAnimation( false ); - } - }, [ resetAnimation ] ); const animationProps = useSpring( { from: transform, to: { x: 0, y: 0, }, - reset: resetAnimation, + reset: triggeredAnimation !== finishedAnimation, config: { mass: 5, tension: 2000, friction: 200 }, immediate: prefersReducedMotion, } ); From bf8e942a93045ed76bc3ecc09e063652d88a6cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 26 Jul 2019 11:58:51 +0200 Subject: [PATCH 532/664] RichText/Input Interaction: use ZWNBSP as padding (#14846) * RichText: try ZWNBSP as padding * Add e2e test * Fix e2e test * Add list e2e test * Try removing some mysterious code * Add docs for special characters --- packages/dom/src/dom.js | 16 +----- .../__snapshots__/writing-flow.test.js.snap | 14 +++++ .../blocks/__snapshots__/list.test.js.snap | 6 +++ packages/e2e-tests/specs/blocks/list.test.js | 13 +++++ .../__snapshots__/hooks-api.test.js.snap | 3 ++ .../e2e-tests/specs/plugins/hooks-api.test.js | 4 +- packages/e2e-tests/specs/writing-flow.test.js | 14 +++++ packages/rich-text/src/create.js | 12 +++-- packages/rich-text/src/special-characters.js | 12 ++++- .../src/test/__snapshots__/to-dom.js.snap | 54 +++++-------------- packages/rich-text/src/test/helpers/index.js | 11 ++-- packages/rich-text/src/to-tree.js | 14 ++--- 12 files changed, 97 insertions(+), 76 deletions(-) create mode 100644 packages/e2e-tests/specs/plugins/__snapshots__/hooks-api.test.js.snap diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 64fb97534f60a2..ba005d6d3c4013 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -390,7 +390,7 @@ export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScro const x = rect.left; const y = isReverse ? ( editableRect.bottom - buffer ) : ( editableRect.top + buffer ); - let range = hiddenCaretRangeFromPoint( document, x, y, container ); + const range = hiddenCaretRangeFromPoint( document, x, y, container ); if ( ! range || ! container.contains( range.startContainer ) ) { if ( mayUseScroll && ( @@ -407,20 +407,6 @@ export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScro return; } - // Check if the closest text node is actually further away. - // If so, attempt to get the range again with the y position adjusted to get the right offset. - if ( range.startContainer.nodeType === TEXT_NODE ) { - const parentNode = range.startContainer.parentNode; - const parentRect = parentNode.getBoundingClientRect(); - const side = isReverse ? 'bottom' : 'top'; - const padding = parseInt( getComputedStyle( parentNode ).getPropertyValue( `padding-${ side }` ), 10 ) || 0; - const actualY = isReverse ? ( parentRect.bottom - padding - buffer ) : ( parentRect.top + padding + buffer ); - - if ( y !== actualY ) { - range = hiddenCaretRangeFromPoint( document, x, actualY, container ); - } - } - const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange( range ); diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index e693daea38401b..f5e1b7e1afd791 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -142,6 +142,20 @@ exports[`adding blocks should navigate contenteditable with padding 1`] = ` <!-- /wp:paragraph -->" `; +exports[`adding blocks should navigate contenteditable with side padding 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph -->" +`; + exports[`adding blocks should navigate empty paragraph 1`] = ` "<!-- wp:paragraph --> <p>1</p> diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap index a7212afc153e6f..9ce876ee4aebec 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap @@ -212,6 +212,12 @@ exports[`List should outdent with children 2`] = ` <!-- /wp:list -->" `; +exports[`List should place the caret in the right place with nested list 1`] = ` +"<!-- wp:list --> +<ul><li>1</li><li>2<ul><li>a</li></ul></li></ul> +<!-- /wp:list -->" +`; + exports[`List should split indented list item 1`] = ` "<!-- wp:list --> <ul><li>one<ul><li>two</li><li>three</li></ul></li></ul> diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/blocks/list.test.js index 776483ae4eb6a2..54d726aca6ae84 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/blocks/list.test.js @@ -358,4 +358,17 @@ describe( 'List', () => { // That's 9 key presses to create the list, and 9 key presses to remove // the list. ;) } ); + + it( 'should place the caret in the right place with nested list', async () => { + await clickBlockAppender(); + await page.keyboard.type( '* 1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( ' a' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'Enter' ); + // The caret should land in the second item. + await page.keyboard.type( '2' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/hooks-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/hooks-api.test.js.snap new file mode 100644 index 00000000000000..67fa4a4a04d0f2 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/__snapshots__/hooks-api.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Using Hooks API Pressing reset block button resets the block 1`] = `""`; diff --git a/packages/e2e-tests/specs/plugins/hooks-api.test.js b/packages/e2e-tests/specs/plugins/hooks-api.test.js index 7ad6f518c1e699..4641c534493b3f 100644 --- a/packages/e2e-tests/specs/plugins/hooks-api.test.js +++ b/packages/e2e-tests/specs/plugins/hooks-api.test.js @@ -6,6 +6,7 @@ import { clickBlockAppender, createNewPost, deactivatePlugin, + getEditedPostContent, } from '@wordpress/e2e-test-utils'; describe( 'Using Hooks API', () => { @@ -33,7 +34,6 @@ describe( 'Using Hooks API', () => { const paragraphContent = await page.$eval( 'div[data-type="core/paragraph"] p', ( element ) => element.textContent ); expect( paragraphContent ).toEqual( 'First paragraph' ); await page.click( '.edit-post-sidebar .e2e-reset-block-button' ); - const newParagraphContent = await page.$eval( 'div[data-type="core/paragraph"] p', ( element ) => element.textContent ); - expect( newParagraphContent ).toEqual( '' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index b9ac58f46fd405..a9923f1d7b6e8f 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -395,4 +395,18 @@ describe( 'adding blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should navigate contenteditable with side padding', async () => { + await clickBlockAppender(); + await page.keyboard.press( 'Enter' ); + await page.evaluate( () => { + document.activeElement.style.paddingLeft = '100px'; + } ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.type( '1' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 37bd1189193f2c..a07ae9aebd663c 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -13,6 +13,7 @@ import { mergePair } from './concat'; import { LINE_SEPARATOR, OBJECT_REPLACEMENT_CHARACTER, + ZWNBSP, } from './special-characters'; /** @@ -267,10 +268,14 @@ function filterRange( node, range, filter ) { return { startContainer, startOffset, endContainer, endOffset }; } +const ZWNBSPRegExp = new RegExp( ZWNBSP, 'g' ); + function filterString( string ) { // Reduce any whitespace used for HTML formatting to one space // character, because it will also be displayed as such by the browser. - return string.replace( /[\n\r\t]+/g, ' ' ); + return string.replace( /[\n\r\t]+/g, ' ' ) + // Remove padding added by `toTree`. + .replace( ZWNBSPRegExp, '' ); } /** @@ -329,8 +334,9 @@ function createFromElement( { } if ( - node.getAttribute( 'data-rich-text-padding' ) || - ( isEditableTree && type === 'br' && ! node.getAttribute( 'data-rich-text-line-break' ) ) + isEditableTree && + type === 'br' && + ! node.getAttribute( 'data-rich-text-line-break' ) ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; diff --git a/packages/rich-text/src/special-characters.js b/packages/rich-text/src/special-characters.js index 5695c40c745b96..078525ec1777e6 100644 --- a/packages/rich-text/src/special-characters.js +++ b/packages/rich-text/src/special-characters.js @@ -1,5 +1,15 @@ /** - * Line separator character. + * Line separator character, used for multiline text. */ export const LINE_SEPARATOR = '\u2028'; + +/** + * Object replacement character, used as a placeholder for objects. + */ export const OBJECT_REPLACEMENT_CHARACTER = '\ufffc'; + +/** + * Zero width non-breaking space, used as padding in the editable DOM tree when + * it is empty otherwise. + */ +export const ZWNBSP = '\ufeff'; diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index ae6c0db2d55750..755cfb068aaddd 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -110,18 +110,14 @@ exports[`recordToDom should create a value without formatting 1`] = ` exports[`recordToDom should create an empty value 1`] = ` <body> - <br - data-rich-text-padding="true" - /> +  </body> `; exports[`recordToDom should create an empty value from empty tags 1`] = ` <body> - <br - data-rich-text-padding="true" - /> +  </body> `; @@ -143,9 +139,7 @@ exports[`recordToDom should handle br 1`] = ` data-rich-text-line-break="true" /> - <br - data-rich-text-padding="true" - /> +  </body> `; @@ -161,9 +155,7 @@ exports[`recordToDom should handle br with formatting 1`] = ` </em> - <br - data-rich-text-padding="true" - /> +  </body> `; @@ -195,9 +187,7 @@ exports[`recordToDom should handle empty list value 1`] = ` <body> <li> - <br - data-rich-text-padding="true" - /> +  </li> </body> `; @@ -206,9 +196,7 @@ exports[`recordToDom should handle empty multiline value 1`] = ` <body> <p> - <br - data-rich-text-padding="true" - /> +  </p> </body> `; @@ -217,23 +205,15 @@ exports[`recordToDom should handle middle empty list value 1`] = ` <body> <li> - <br - data-rich-text-padding="true" - /> - +  </li> <li> - <br - data-rich-text-padding="true" - /> - +  </li> <li> - <br - data-rich-text-padding="true" - /> +  </li> </body> `; @@ -291,9 +271,7 @@ exports[`recordToDom should handle multiline value with empty 1`] = ` </p> <p> - <br - data-rich-text-padding="true" - /> +  </p> </body> `; @@ -302,15 +280,11 @@ exports[`recordToDom should handle nested empty list value 1`] = ` <body> <li> - <br - data-rich-text-padding="true" - /> +  <ul> <li> - <br - data-rich-text-padding="true" - /> +  </li> </ul> </li> @@ -373,9 +347,7 @@ exports[`recordToDom should preserve non breaking space 1`] = ` exports[`recordToDom should remove padding 1`] = ` <body> - <br - data-rich-text-padding="true" - /> +  </body> `; diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 16b09bb19b8326..fe97f00d245f53 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { ZWNBSP } from '../../special-characters'; + export function getSparseArrayLength( array ) { return array.reduce( ( i ) => i + 1, 0 ); } @@ -504,8 +509,8 @@ export const spec = [ endOffset: 0, endContainer: element.firstChild.nextSibling, } ), - startPath: [ 1, 2, 0 ], - endPath: [ 1, 2, 0 ], + startPath: [ 1, 1, 1 ], + endPath: [ 1, 1, 1 ], record: { start: 1, end: 1, @@ -568,7 +573,7 @@ export const spec = [ }, { description: 'should remove padding', - html: '<br data-rich-text-padding="true">', + html: ZWNBSP, createRange: ( element ) => ( { startOffset: 0, startContainer: element, diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index c76eafc803b92f..df68bd5413d0b0 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -7,6 +7,7 @@ import { getFormatType } from './get-format-type'; import { LINE_SEPARATOR, OBJECT_REPLACEMENT_CHARACTER, + ZWNBSP, } from './special-characters'; /** @@ -69,14 +70,6 @@ function fromFormat( { type, attributes, unregisteredAttributes, object, boundar }; } -const padding = { - type: 'br', - attributes: { - 'data-rich-text-padding': 'true', - }, - object: true, -}; - export function toTree( { value, multilineTag, @@ -145,8 +138,7 @@ export function toTree( { node = getLastChild( node ); } - append( getParent( node ), padding ); - append( getParent( node ), '' ); + append( getParent( node ), ZWNBSP ); } // Set selection for the start of line. @@ -255,7 +247,7 @@ export function toTree( { } if ( shouldInsertPadding && i === text.length ) { - append( getParent( pointer ), padding ); + append( getParent( pointer ), ZWNBSP ); } lastCharacterFormats = characterFormats; From 0779690cf4345a2468e294cbab0c8f6b805073fb Mon Sep 17 00:00:00 2001 From: Seghir Nadir <nadir.seghir@gmail.com> Date: Fri, 26 Jul 2019 11:28:56 +0100 Subject: [PATCH 533/664] Relax Cover and Media+Text inner blocks restrictions (#16751) --- packages/block-library/src/cover/edit.js | 2 -- packages/block-library/src/media-text/edit.js | 2 -- .../__snapshots__/media-text.test.js.snap | 10 -------- .../e2e-tests/specs/blocks/media-text.test.js | 24 ------------------- 4 files changed, 38 deletions(-) delete mode 100644 packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap delete mode 100644 packages/e2e-tests/specs/blocks/media-text.test.js diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 7de37e53f3fc39..6ba81ed2d96886 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -54,7 +54,6 @@ const INNER_BLOCKS_TEMPLATE = [ placeholder: __( 'Write title…' ), } ], ]; -const INNER_BLOCKS_ALLOWED_BLOCKS = [ 'core/button', 'core/heading', 'core/paragraph' ]; function retrieveFastAverageColor() { if ( ! retrieveFastAverageColor.fastAverageColor ) { @@ -299,7 +298,6 @@ class CoverEdit extends Component { <div className="wp-block-cover__inner-container"> <InnerBlocks template={ INNER_BLOCKS_TEMPLATE } - allowedBlocks={ INNER_BLOCKS_ALLOWED_BLOCKS } /> </div> </div> diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index f6594b7a71a591..782aee3bacf4d3 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -33,7 +33,6 @@ import MediaContainer from './media-container'; /** * Constants */ -const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ]; const TEMPLATE = [ [ 'core/paragraph', { fontSize: 'large', placeholder: _x( 'Content…', 'content placeholder' ) } ], ]; @@ -233,7 +232,6 @@ class MediaTextEdit extends Component { <div className={ classNames } style={ style } > { this.renderMediaArea() } <InnerBlocks - allowedBlocks={ ALLOWED_BLOCKS } template={ TEMPLATE } templateInsertUpdatesSelection={ false } /> diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap deleted file mode 100644 index 7ce74edf9b7060..00000000000000 --- a/packages/e2e-tests/specs/blocks/__snapshots__/media-text.test.js.snap +++ /dev/null @@ -1,10 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Media Text restricts blocks that can be inserted 1`] = ` -Array [ - "Button", - "Heading", - "List", - "Paragraph", -] -`; diff --git a/packages/e2e-tests/specs/blocks/media-text.test.js b/packages/e2e-tests/specs/blocks/media-text.test.js deleted file mode 100644 index 2e59b776703271..00000000000000 --- a/packages/e2e-tests/specs/blocks/media-text.test.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * WordPress dependencies - */ -import { - createNewPost, - getAllBlockInserterItemTitles, - insertBlock, - openAllBlockInserterCategories, - openGlobalBlockInserter, -} from '@wordpress/e2e-test-utils'; - -describe( 'Media Text', () => { - beforeEach( async () => { - await createNewPost(); - } ); - - it( 'restricts blocks that can be inserted', async () => { - await insertBlock( 'Media & Text' ); - await page.click( '.wp-block-media-text .block-editor-rich-text' ); - await openGlobalBlockInserter(); - await openAllBlockInserterCategories(); - expect( await getAllBlockInserterItemTitles() ).toMatchSnapshot(); - } ); -} ); From 79d78477d42aef8228b4cd27b745739ce8d9d5cd Mon Sep 17 00:00:00 2001 From: Joen Asmussen <asmussen@gmail.com> Date: Fri, 26 Jul 2019 14:46:11 +0200 Subject: [PATCH 534/664] Try: Always collapse block alignments. (#16557) * Try: Always collapse block alignments. This PR makes the block alignments always be collapsed. This group would already collapse at mobile responsive breakpoints. In addition, this PR also adds a dropdown arrow. This comes with a couple of benefit: - It ensures that the block toolbar always fits even when the item is deeply nested inside columns. - It affords a scalable method to show additional alignment options, such as those suggested in #16385. - It scales to future ideas of allowing a theme to create CSS grid-based layouts, which could allow theme authors to create their own custom alignments such as "pull right" or others. - It has labels, to be descriptive of such new alignments. Noting that 3 is just an idea at this point, but the other items on the list can potentially benefit us today. * Refactor alignment toolbars to be collapsed by default * Add arrow indicator to the Edit tabel dropdown menu * Update snapshot tests to work with updated components * Update failing e2e tests to work after UI changes introduced --- .../src/components/alignment-toolbar/index.js | 36 ++---------- .../test/__snapshots__/index.js.snap | 12 ++-- .../block-alignment-toolbar/index.js | 30 +++++----- .../test/__snapshots__/index.js.snap | 9 +-- .../README.md | 5 ++ .../block-vertical-alignment-toolbar/index.js | 26 +-------- .../test/__snapshots__/index.js.snap | 3 +- packages/block-library/src/heading/edit.js | 1 + packages/block-library/src/table/edit.js | 1 + .../components/src/dropdown-menu/README.md | 12 +++- .../components/src/dropdown-menu/index.js | 3 +- packages/components/src/toolbar/index.js | 1 + .../e2e-tests/specs/block-deletion.test.js | 2 +- .../e2e-tests/specs/block-grouping.test.js | 10 ++-- .../blocks/__snapshots__/table.test.js.snap | 4 +- packages/e2e-tests/specs/blocks/table.test.js | 2 + .../specs/keyboard-navigable-blocks.test.js | 22 ++----- .../specs/plugins/align-hook.test.js | 57 +++++++++++++------ 18 files changed, 111 insertions(+), 125 deletions(-) diff --git a/packages/block-editor/src/components/alignment-toolbar/index.js b/packages/block-editor/src/components/alignment-toolbar/index.js index 1124f44f033e1f..981e8d6f9914d3 100644 --- a/packages/block-editor/src/components/alignment-toolbar/index.js +++ b/packages/block-editor/src/components/alignment-toolbar/index.js @@ -8,34 +8,26 @@ import { find } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { Toolbar } from '@wordpress/components'; -import { withViewportMatch } from '@wordpress/viewport'; -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import { withBlockEditContext } from '../block-edit/context'; const DEFAULT_ALIGNMENT_CONTROLS = [ { icon: 'editor-alignleft', - title: __( 'Align text left' ), + title: __( 'Align Text Left' ), align: 'left', }, { icon: 'editor-aligncenter', - title: __( 'Align text center' ), + title: __( 'Align Text Center' ), align: 'center', }, { icon: 'editor-alignright', - title: __( 'Align text right' ), + title: __( 'Align Text Right' ), align: 'right', }, ]; -export function AlignmentToolbar( { isCollapsed, value, onChange, alignmentControls = DEFAULT_ALIGNMENT_CONTROLS } ) { +export function AlignmentToolbar( { value, onChange, alignmentControls = DEFAULT_ALIGNMENT_CONTROLS, isCollapsed = true } ) { function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } @@ -46,7 +38,7 @@ export function AlignmentToolbar( { isCollapsed, value, onChange, alignmentContr <Toolbar isCollapsed={ isCollapsed } icon={ activeAlignment ? activeAlignment.icon : 'editor-alignleft' } - label={ __( 'Change Text Alignment' ) } + label={ __( 'Change text alignment' ) } controls={ alignmentControls.map( ( control ) => { const { align } = control; const isActive = ( value === align ); @@ -61,20 +53,4 @@ export function AlignmentToolbar( { isCollapsed, value, onChange, alignmentContr ); } -export default compose( - withBlockEditContext( ( { clientId } ) => { - return { - clientId, - }; - } ), - withViewportMatch( { isLargeViewport: 'medium' } ), - withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getSettings } = select( 'core/block-editor' ); - return { - isCollapsed: isCollapsed || ! isLargeViewport || ( - ! getSettings().hasFixedToolbar && - getBlockRootClientId( clientId ) - ), - }; - } ), -)( AlignmentToolbar ); +export default AlignmentToolbar; diff --git a/packages/block-editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap index 34e440f5d76fef..5ea348074add60 100644 --- a/packages/block-editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/alignment-toolbar/test/__snapshots__/index.js.snap @@ -21,7 +21,8 @@ exports[`AlignmentToolbar should allow custom alignment controls to be specified ] } icon="editor-aligncenter" - label="Change Text Alignment" + isCollapsed={true} + label="Change text alignment" /> `; @@ -34,25 +35,26 @@ exports[`AlignmentToolbar should match snapshot 1`] = ` "icon": "editor-alignleft", "isActive": true, "onClick": [Function], - "title": "Align text left", + "title": "Align Text Left", }, Object { "align": "center", "icon": "editor-aligncenter", "isActive": false, "onClick": [Function], - "title": "Align text center", + "title": "Align Text Center", }, Object { "align": "right", "icon": "editor-alignright", "isActive": false, "onClick": [Function], - "title": "Align text right", + "title": "Align Text Right", }, ] } icon="editor-alignleft" - label="Change Text Alignment" + isCollapsed={true} + label="Change text alignment" /> `; diff --git a/packages/block-editor/src/components/block-alignment-toolbar/index.js b/packages/block-editor/src/components/block-alignment-toolbar/index.js index adf02cd90b08b2..e766504f0d9f4d 100644 --- a/packages/block-editor/src/components/block-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-alignment-toolbar/index.js @@ -3,7 +3,6 @@ */ import { __ } from '@wordpress/i18n'; import { Toolbar } from '@wordpress/components'; -import { withViewportMatch } from '@wordpress/viewport'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -15,30 +14,31 @@ import { withBlockEditContext } from '../block-edit/context'; const BLOCK_ALIGNMENTS_CONTROLS = { left: { icon: 'align-left', - title: __( 'Align left' ), + title: __( 'Align Left' ), }, center: { icon: 'align-center', - title: __( 'Align center' ), + title: __( 'Align Center' ), }, right: { icon: 'align-right', - title: __( 'Align right' ), + title: __( 'Align Right' ), }, wide: { icon: 'align-wide', - title: __( 'Wide width' ), + title: __( 'Wide Width' ), }, full: { icon: 'align-full-width', - title: __( 'Full width' ), + title: __( 'Full Width' ), }, }; const DEFAULT_CONTROLS = [ 'left', 'center', 'right', 'wide', 'full' ]; +const DEFAULT_CONTROL = 'center'; const WIDE_CONTROLS = [ 'wide', 'full' ]; -export function BlockAlignmentToolbar( { isCollapsed, value, onChange, controls = DEFAULT_CONTROLS, wideControlsEnabled = false } ) { +export function BlockAlignmentToolbar( { value, onChange, controls = DEFAULT_CONTROLS, isCollapsed = true, wideControlsEnabled = false } ) { function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } @@ -47,13 +47,14 @@ export function BlockAlignmentToolbar( { isCollapsed, value, onChange, controls controls : controls.filter( ( control ) => WIDE_CONTROLS.indexOf( control ) === -1 ); - const activeAlignment = BLOCK_ALIGNMENTS_CONTROLS[ value ]; + const activeAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ value ]; + const defaultAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ DEFAULT_CONTROL ]; return ( <Toolbar isCollapsed={ isCollapsed } - icon={ activeAlignment ? activeAlignment.icon : 'align-left' } - label={ __( 'Change Alignment' ) } + icon={ activeAlignmentControl ? activeAlignmentControl.icon : defaultAlignmentControl.icon } + label={ __( 'Change alignment' ) } controls={ enabledControls.map( ( control ) => { return { @@ -73,16 +74,11 @@ export default compose( clientId, }; } ), - withViewportMatch( { isLargeViewport: 'medium' } ), - withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getSettings } = select( 'core/block-editor' ); + withSelect( ( select ) => { + const { getSettings } = select( 'core/block-editor' ); const settings = getSettings(); return { wideControlsEnabled: settings.alignWide, - isCollapsed: isCollapsed || ! isLargeViewport || ( - ! settings.hasFixedToolbar && - getBlockRootClientId( clientId ) - ), }; } ), )( BlockAlignmentToolbar ); diff --git a/packages/block-editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap index 27fbae699c3bdb..e35af5e597210a 100644 --- a/packages/block-editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-alignment-toolbar/test/__snapshots__/index.js.snap @@ -8,23 +8,24 @@ exports[`BlockAlignmentToolbar should match snapshot 1`] = ` "icon": "align-left", "isActive": true, "onClick": [Function], - "title": "Align left", + "title": "Align Left", }, Object { "icon": "align-center", "isActive": false, "onClick": [Function], - "title": "Align center", + "title": "Align Center", }, Object { "icon": "align-right", "isActive": false, "onClick": [Function], - "title": "Align right", + "title": "Align Right", }, ] } icon="align-left" - label="Change Alignment" + isCollapsed={true} + label="Change alignment" /> `; diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md b/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md index 8eca9ffe0861a6..3e11032e85fa50 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md @@ -66,6 +66,11 @@ _Note:_ the user can repeatedly click on the toolbar buttons to toggle the align The current value of the alignment setting. You may only choose from the `Options` listed above. +### `isCollapsed` +* **Type:** `Boolean` +* **Default:** `true` + +Whether to display toolbar options in the dropdown menu. This toolbar is collapsed by default. ### `onChange` * **Type:** `Function` diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js index 86414d5ddf07f5..22206984dc2706 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js @@ -3,14 +3,10 @@ */ import { _x } from '@wordpress/i18n'; import { Toolbar } from '@wordpress/components'; -import { withViewportMatch } from '@wordpress/viewport'; -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; /** * Internal dependencies */ -import { withBlockEditContext } from '../block-edit/context'; import { alignTop, alignCenter, alignBottom } from './icons'; const BLOCK_ALIGNMENTS_CONTROLS = { @@ -31,7 +27,7 @@ const BLOCK_ALIGNMENTS_CONTROLS = { const DEFAULT_CONTROLS = [ 'top', 'center', 'bottom' ]; const DEFAULT_CONTROL = 'top'; -export function BlockVerticalAlignmentToolbar( { isCollapsed, value, onChange, controls = DEFAULT_CONTROLS } ) { +export function BlockVerticalAlignmentToolbar( { value, onChange, controls = DEFAULT_CONTROLS, isCollapsed = true } ) { function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } @@ -43,7 +39,7 @@ export function BlockVerticalAlignmentToolbar( { isCollapsed, value, onChange, c <Toolbar isCollapsed={ isCollapsed } icon={ activeAlignment ? activeAlignment.icon : defaultAlignmentControl.icon } - label={ _x( 'Change Alignment', 'Block vertical alignment setting label' ) } + label={ _x( 'Change vertical alignment', 'Block vertical alignment setting label' ) } controls={ controls.map( ( control ) => { return { @@ -60,20 +56,4 @@ export function BlockVerticalAlignmentToolbar( { isCollapsed, value, onChange, c /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/block-vertical-alignment-toolbar/README.md */ -export default compose( - withBlockEditContext( ( { clientId } ) => { - return { - clientId, - }; - } ), - withViewportMatch( { isLargeViewport: 'medium' } ), - withSelect( ( select, { clientId, isLargeViewport, isCollapsed } ) => { - const { getBlockRootClientId, getSettings } = select( 'core/block-editor' ); - return { - isCollapsed: isCollapsed || ! isLargeViewport || ( - ! getSettings().hasFixedToolbar && - !! getBlockRootClientId( clientId ) - ), - }; - } ), -)( BlockVerticalAlignmentToolbar ); +export default BlockVerticalAlignmentToolbar; diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/__snapshots__/index.js.snap index 5af12d1e1a1c7d..605e697786030d 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/__snapshots__/index.js.snap @@ -79,6 +79,7 @@ exports[`BlockVerticalAlignmentToolbar should match snapshot 1`] = ` /> </SVG> } - label="Change Alignment" + isCollapsed={true} + label="Change vertical alignment" /> `; diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 7fc4721c9169ab..fd280fd2714fed 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -69,6 +69,7 @@ function HeadingEdit( { <HeadingToolbar minLevel={ 1 } maxLevel={ 7 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> <p>{ __( 'Text Alignment' ) }</p> <AlignmentToolbar + isCollapsed={ false } value={ align } onChange={ ( nextAlign ) => { setAttributes( { align: nextAlign } ); diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 90a4682e2223ad..3c5f6c11fe2776 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -461,6 +461,7 @@ export class TableEdit extends Component { <BlockControls> <Toolbar> <DropdownMenu + hasArrowIndicator icon="editor-table" label={ __( 'Edit table' ) } controls={ this.getTableControls() } diff --git a/packages/components/src/dropdown-menu/README.md b/packages/components/src/dropdown-menu/README.md index 4e2243c653b2f4..f118a1bc4c241f 100644 --- a/packages/components/src/dropdown-menu/README.md +++ b/packages/components/src/dropdown-menu/README.md @@ -140,12 +140,22 @@ The component accepts the following props: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug to be shown in the collapsed menu button. -- Type: `String` +- Type: `String|null` - Required: No - Default: `"menu"` See also: [https://developer.wordpress.org/resource/dashicons/](https://developer.wordpress.org/resource/dashicons/) +#### hasArrowIndicator + +Whether to display an arrow indicator next to the icon. + +- Type: `Boolean` +- Required: No +- Default: `false` + +For backward compatibility, when `icon` is explicitly set to `null` then the arrow indicator will be displayed even when this flag is set to `false`. + #### label A human-readable label to present as accessibility text on the focused collapsed menu button. diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 8195a309ec0241..8212deaeda5956 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -20,6 +20,7 @@ function DropdownMenu( { children, className, controls, + hasArrowIndicator = false, icon = 'menu', label, menuLabel, @@ -70,7 +71,7 @@ function DropdownMenu( { labelPosition={ __unstableLabelPosition } tooltip={ label } > - { ! icon && <span className="components-dropdown-menu__indicator" /> } + { ( ! icon || hasArrowIndicator ) && <span className="components-dropdown-menu__indicator" /> } </IconButton> ); } } diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.js index 64e4f8359919de..3dc1f3c1d4451a 100644 --- a/packages/components/src/toolbar/index.js +++ b/packages/components/src/toolbar/index.js @@ -58,6 +58,7 @@ function Toolbar( { controls = [], children, className, isCollapsed, icon, label if ( isCollapsed ) { return ( <DropdownMenu + hasArrowIndicator icon={ icon } label={ label } controls={ controlSets } diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index f5cfe7aec0da4d..4466c03783f290 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -35,7 +35,7 @@ const clickOnBlockSettingsMenuRemoveBlockButton = async () => { let isRemoveButton = false; - let numButtons = await page.$$eval( '.block-editor-block-toolbar button', ( btns ) => btns.length ); + let numButtons = await page.$$eval( '.block-editor-block-settings-menu__content button', ( btns ) => btns.length ); // Limit by the number of buttons available while ( --numButtons ) { diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/block-grouping.test.js index 3a42f3f6e43f6a..64bba812f4f278 100644 --- a/packages/e2e-tests/specs/block-grouping.test.js +++ b/packages/e2e-tests/specs/block-grouping.test.js @@ -157,13 +157,15 @@ describe( 'Block Grouping', () => { await insertBlock( 'Heading' ); await page.keyboard.type( 'Group Heading' ); - // Full width image + // Full width image. await insertBlock( 'Image' ); - await clickBlockToolbarButton( 'Full width' ); + await clickBlockToolbarButton( 'Change alignment' ); + await page.click( '.components-dropdown-menu__menu button svg.dashicons-align-full-width' ); - // Wide width image) + // Wide width image. await insertBlock( 'Image' ); - await clickBlockToolbarButton( 'Wide width' ); + await clickBlockToolbarButton( 'Change alignment' ); + await page.click( '.components-dropdown-menu__menu button svg.dashicons-align-wide' ); await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Some paragraph' ); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap index f2c4ee232d0785..1065dc2615e2e5 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -2,13 +2,13 @@ exports[`Table allows adding and deleting columns across the table header, body and footer 1`] = ` "<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table></figure> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td><td></td></tr></tfoot></table></figure> <!-- /wp:table -->" `; exports[`Table allows adding and deleting columns across the table header, body and footer 2`] = ` "<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr><tr><td></td></tr></tbody><tfoot><tr><td></td></tr></tfoot></table></figure> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table></figure> <!-- /wp:table -->" `; diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js index dd24f06b24adaf..8da1506c1c51ec 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -134,6 +134,8 @@ describe( 'Table', () => { await headerSwitch[ 0 ].click(); await footerSwitch[ 0 ].click(); + await page.click( '.wp-block-table__cell-content' ); + // Add a column. await clickBlockToolbarButton( 'Edit table' ); const addColumnAfterButton = await page.$x( "//button[text()='Add Column After']" ); diff --git a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js index 64d1afcda1a61e..a8ebe96c34fcd2 100644 --- a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js @@ -76,26 +76,12 @@ const tabThroughBlockToolbar = async () => { ); await expect( isFocusedBlockSwitcherControl ).toBe( true ); - // Tab to focus on the 'left paragraph alignment' dropdown control + // Tab to focus on the 'Change text alignment' dropdown control await page.keyboard.press( 'Tab' ); - const isFocusedLeftAlignmentControl = await page.evaluate( () => - document.activeElement.classList.contains( 'components-toolbar__control' ) - ); - await expect( isFocusedLeftAlignmentControl ).toBe( true ); - - // Tab to focus on the 'center paragraph alignment' dropdown control - await page.keyboard.press( 'Tab' ); - const isFocusedCenterAlignmentControl = await page.evaluate( () => - document.activeElement.classList.contains( 'components-toolbar__control' ) - ); - await expect( isFocusedCenterAlignmentControl ).toBe( true ); - - // Tab to focus on the 'right paragraph alignment' dropdown control - await page.keyboard.press( 'Tab' ); - const isFocusedRightAlignmentControl = await page.evaluate( () => - document.activeElement.classList.contains( 'components-toolbar__control' ) + const isFocusedChangeTextAlignmentControl = await page.evaluate( () => + document.activeElement.classList.contains( 'components-dropdown-menu__toggle' ) ); - await expect( isFocusedRightAlignmentControl ).toBe( true ); + await expect( isFocusedChangeTextAlignmentControl ).toBe( true ); // Tab to focus on the 'More formatting' dropdown toggle await page.keyboard.press( 'Tab' ); diff --git a/packages/e2e-tests/specs/plugins/align-hook.test.js b/packages/e2e-tests/specs/plugins/align-hook.test.js index aacacbe657e462..4735560b8fb81e 100644 --- a/packages/e2e-tests/specs/plugins/align-hook.test.js +++ b/packages/e2e-tests/specs/plugins/align-hook.test.js @@ -13,6 +13,8 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'Align Hook Works As Expected', () => { + const CHANGE_ALIGNMENT_BUTTON_SELECTOR = '.block-editor-block-toolbar .components-dropdown-menu__toggle[aria-label="Change alignment"]'; + beforeAll( async () => { await activatePlugin( 'gutenberg-test-align-hook' ); } ); @@ -26,14 +28,16 @@ describe( 'Align Hook Works As Expected', () => { } ); const getAlignmentToolbarLabels = async () => { + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + const buttonLabels = await page.evaluate( () => { return Array.from( document.querySelectorAll( - '.block-editor-block-toolbar button[aria-label^="Align"]' + '.components-dropdown-menu__menu button' ) ).map( ( button ) => { - return button.getAttribute( 'aria-label' ); + return button.innerText; } ); } ); @@ -44,6 +48,7 @@ describe( 'Align Hook Works As Expected', () => { it( 'Shows the expected buttons on the alignment toolbar', async () => { await insertBlock( blockName ); + expect( await getAlignmentToolbarLabels() ).toEqual( buttonLabels ); @@ -53,8 +58,10 @@ describe( 'Align Hook Works As Expected', () => { const createDoesNotApplyAlignmentByDefaultTest = ( blockName ) => { it( 'Does not apply any alignment by default', async () => { await insertBlock( blockName ); + // verify no alignment button is in pressed state - const pressedButtons = await page.$$( '.block-editor-block-toolbar button[aria-label^="Align"][aria-pressed="true"]' ); + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + const pressedButtons = await page.$$( '.components-dropdown-menu__menu button.is-active' ); expect( pressedButtons ).toHaveLength( 0 ); } ); }; @@ -69,13 +76,15 @@ describe( 'Align Hook Works As Expected', () => { const createCorrectlyAppliesAndRemovesAlignmentTest = ( blockName, alignment ) => { it( 'Correctly applies the selected alignment and correctly removes the alignment', async () => { - const BUTTON_SELECTOR = `.block-editor-block-toolbar button[aria-label="Align ${ alignment }"]`; - const BUTTON_PRESSED_SELECTOR = `${ BUTTON_SELECTOR }[aria-pressed="true"]`; + const BUTTON_SELECTOR = `.components-dropdown-menu__menu button svg.dashicons-align-${ alignment }`; + const BUTTON_PRESSED_SELECTOR = '.components-dropdown-menu__menu button.is-active'; // set the specified alignment. await insertBlock( blockName ); + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); await page.click( BUTTON_SELECTOR ); // verify the button of the specified alignment is pressed. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); let pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); expect( pressedButtons ).toHaveLength( 1 ); @@ -92,9 +101,11 @@ describe( 'Align Hook Works As Expected', () => { ); // remove the alignment. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); await page.click( BUTTON_SELECTOR ); // verify no alignment button is in pressed state. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); expect( pressedButtons ).toHaveLength( 0 ); @@ -110,6 +121,7 @@ describe( 'Align Hook Works As Expected', () => { ); // verify no alignment button is in pressed state after parsing the block. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); expect( pressedButtons ).toHaveLength( 0 ); } @@ -120,7 +132,9 @@ describe( 'Align Hook Works As Expected', () => { const BLOCK_NAME = 'Test No Alignment Set'; it( 'Shows no alignment buttons on the alignment toolbar', async () => { - expect( await getAlignmentToolbarLabels() ).toHaveLength( 0 ); + await insertBlock( BLOCK_NAME ); + const changeAlignmentButton = await page.$( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + expect( changeAlignmentButton ).toBe( null ); } ); @@ -136,9 +150,11 @@ describe( 'Align Hook Works As Expected', () => { const BLOCK_NAME = 'Test Align True'; createShowsTheExpectedButtonsTest( BLOCK_NAME, [ - 'Align left', - 'Align center', - 'Align right', + 'Align Left', + 'Align Center', + 'Align Right', + 'Wide Width', + 'Full Width', ] ); createDoesNotApplyAlignmentByDefaultTest( BLOCK_NAME ); @@ -150,8 +166,8 @@ describe( 'Align Hook Works As Expected', () => { const BLOCK_NAME = 'Test Align Array'; createShowsTheExpectedButtonsTest( BLOCK_NAME, [ - 'Align left', - 'Align center', + 'Align Left', + 'Align Center', ] ); createDoesNotApplyAlignmentByDefaultTest( BLOCK_NAME ); @@ -161,18 +177,21 @@ describe( 'Align Hook Works As Expected', () => { describe( 'Block with default align', () => { const BLOCK_NAME = 'Test Default Align'; - const PRESSED_BUTTON_SELECTOR = '.block-editor-block-toolbar button[aria-label="Align right"][aria-pressed="true"]'; + const SELECTED_ALIGNMENT_CONTROL_SELECTOR = '//div[contains(@class, "components-dropdown-menu__menu")]//button[contains(@class, "is-active")][text()="Align Right"]'; createShowsTheExpectedButtonsTest( BLOCK_NAME, [ - 'Align left', - 'Align center', - 'Align right', + 'Align Left', + 'Align Center', + 'Align Right', + 'Wide Width', + 'Full Width', ] ); it( 'Applies the selected alignment by default', async () => { await insertBlock( BLOCK_NAME ); // verify the correct alignment button is pressed - const pressedButtons = await page.$$( PRESSED_BUTTON_SELECTOR ); - expect( pressedButtons ).toHaveLength( 1 ); + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + const selectedAlignmentControls = await page.$x( SELECTED_ALIGNMENT_CONTROL_SELECTOR ); + expect( selectedAlignmentControls ).toHaveLength( 1 ); } ); it( 'The default markup does not contain the alignment attribute but contains the alignment class', @@ -187,7 +206,9 @@ describe( 'Align Hook Works As Expected', () => { it( 'Can remove the default alignment and the align attribute equals none but alignnone class is not applied', async () => { await insertBlock( BLOCK_NAME ); // remove the alignment. - await page.click( PRESSED_BUTTON_SELECTOR ); + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + const [ selectedAlignmentControl ] = await page.$x( SELECTED_ALIGNMENT_CONTROL_SELECTOR ); + await selectedAlignmentControl.click(); const markup = await getEditedPostContent(); expect( markup ).toContain( '"align":""' ); } ); From c40201439411f66adc092b072afd618095361f60 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 26 Jul 2019 08:54:09 -0400 Subject: [PATCH 535/664] Framework: Ignore more transient directories for lint (#16753) --- .eslintignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 9cd32291e32608..aab87ed140ccd6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,8 +1,11 @@ +.cache build build-module node_modules +packages/block-serialization-spec-parser/parser.js packages/e2e-tests/plugins +playground/dist vendor -packages/block-serialization-spec-parser/parser.js +wordpress !.eslintrc.js From 32b7b341885c6e5d9b710bfe4ed64123d3e23a9d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 26 Jul 2019 10:55:54 -0400 Subject: [PATCH 536/664] Data: Document WPDataRegistry properties (#16693) * Data: Document WPDataRegistry properties * Data: Use WPDataRegistry type where applicable * Data: Fix WPDataRegistry typedef syntax * Data: Bring WPDataRegistry into scope for typedef --- packages/data/src/namespace-store/index.js | 36 +++++++++++-------- packages/data/src/registry.js | 22 ++++++++---- .../data/src/resolvers-cache-middleware.js | 10 +++--- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/packages/data/src/namespace-store/index.js b/packages/data/src/namespace-store/index.js index 6c8d7d94094eba..8d2b009cc4d7d5 100644 --- a/packages/data/src/namespace-store/index.js +++ b/packages/data/src/namespace-store/index.js @@ -23,12 +23,18 @@ import metadataReducer from './metadata/reducer'; import * as metadataSelectors from './metadata/selectors'; import * as metadataActions from './metadata/actions'; +/** + * @typedef {import('../registry').WPDataRegistry} WPDataRegistry + */ + /** * Creates a namespace object with a store derived from the reducer given. * - * @param {string} key Identifying string used for namespace and redex dev tools. - * @param {Object} options Contains reducer, actions, selectors, and resolvers. - * @param {Object} registry Registry reference. + * @param {string} key Unique namespace identifier. + * @param {Object} options Registered store options, with properties + * describing reducer, actions, selectors, and + * resolvers. + * @param {WPDataRegistry} registry Registry reference. * * @return {Object} Store Object. */ @@ -102,10 +108,11 @@ export default function createNamespace( key, options, registry ) { /** * Creates a redux store for a namespace. * - * @param {string} key Part of the state shape to register the - * selectors for. - * @param {Object} options Registered store options. - * @param {Object} registry Registry reference, for resolver enhancer support. + * @param {string} key Unique namespace identifier. + * @param {Object} options Registered store options, with properties + * describing reducer, actions, selectors, and + * resolvers. + * @param {WPDataRegistry} registry Registry reference. * * @return {Object} Newly created redux store. */ @@ -143,15 +150,16 @@ function createReduxStore( key, options, registry ) { } /** - * Maps selectors to a redux store. + * Maps selectors to a store. * - * @param {Object} selectors Selectors to register. Keys will be used as the - * public facing API. Selectors will get passed the - * state as first argument. - * @param {Object} store The redux store to which the selectors should be mapped. - * @param {Object} registry Registry reference. + * @param {Object} selectors Selectors to register. Keys will be used as + * the public facing API. Selectors will get + * passed the state as first argument. + * @param {Object} store The store to which the selectors should be + * mapped. + * @param {WPDataRegistry} registry Registry reference. * - * @return {Object} Selectors mapped to the redux store provided. + * @return {Object} Selectors mapped to the provided store. */ function mapSelectors( selectors, store, registry ) { const createStateSelector = ( registeredSelector ) => { diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index 15f02180c7b859..97e5212f2a24ab 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -15,13 +15,23 @@ import createCoreDataStore from './store'; /** * An isolated orchestrator of store registrations. * - * @typedef {WPDataRegistry} + * @typedef {Object} WPDataRegistry * - * @property {Function} registerGenericStore - * @property {Function} registerStore - * @property {Function} subscribe - * @property {Function} select - * @property {Function} dispatch + * @property {Function} registerGenericStore Given a namespace key and settings + * object, registers a new generic + * store. + * @property {Function} registerStore Given a namespace key and settings + * object, registers a new namespace + * store. + * @property {Function} subscribe Given a function callback, invokes + * the callback on any change to state + * within any registered store. + * @property {Function} select Given a namespace key, returns an + * object of the store's registered + * selectors. + * @property {Function} dispatch Given a namespace key, returns an + * object of the store's registered + * action dispatchers. */ /** diff --git a/packages/data/src/resolvers-cache-middleware.js b/packages/data/src/resolvers-cache-middleware.js index 0e661442e180d6..e7e0a2ae7c6201 100644 --- a/packages/data/src/resolvers-cache-middleware.js +++ b/packages/data/src/resolvers-cache-middleware.js @@ -4,12 +4,14 @@ import { get } from 'lodash'; /** - * creates a middleware handling resolvers cache invalidation. + * Creates a middleware handling resolvers cache invalidation. * - * @param {Object} registry - * @param {string} reducerKey + * @param {WPDataRegistry} registry The registry reference for which to create + * the middleware. + * @param {string} reducerKey The namespace for which to create the + * middleware. * - * @return {function} middleware + * @return {Function} Middleware function. */ const createResolversCacheMiddleware = ( registry, reducerKey ) => () => ( next ) => ( action ) => { const resolvers = registry.select( 'core/data' ).getCachedResolvers( reducerKey ); From b51e44feb73d0d5b1d4bfbd9be665fb57ca46fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 26 Jul 2019 19:02:22 +0200 Subject: [PATCH 537/664] Improved rich text placeholder (#16733) * Improved rich text placeholder * Rerender if placeholder changes * Adjust placeholder padding for paragraph * Use is-selected instead of :focus --- .../developers/richtext.md | 2 - .../src/components/rich-text/README.md | 4 -- .../src/components/rich-text/index.js | 4 +- .../src/components/rich-text/index.native.js | 2 - .../src/components/rich-text/style.scss | 37 ++++++------------- packages/block-library/src/button/edit.js | 1 - packages/block-library/src/button/editor.scss | 24 +----------- packages/block-library/src/file/edit.js | 2 - .../block-library/src/gallery/editor.scss | 8 ++-- .../block-library/src/paragraph/editor.scss | 3 +- packages/block-library/src/search/edit.js | 2 - .../src/components/visual-editor/style.scss | 2 +- .../style.scss | 4 -- packages/rich-text/src/component/editable.js | 13 ------- packages/rich-text/src/component/index.js | 19 ++++------ packages/rich-text/src/create.js | 11 +++--- packages/rich-text/src/to-dom.js | 4 ++ packages/rich-text/src/to-tree.js | 10 +++++ 18 files changed, 48 insertions(+), 104 deletions(-) diff --git a/docs/designers-developers/developers/richtext.md b/docs/designers-developers/developers/richtext.md index 6e0752f64f8752..e662ce41b7cf94 100644 --- a/docs/designers-developers/developers/richtext.md +++ b/docs/designers-developers/developers/richtext.md @@ -49,7 +49,6 @@ wp.blocks.registerBlockType( /* ... */, { props.setAttributes( { content: content } ); // Store updated content as a block attribute }, placeholder: __( 'Heading...' ), // Display this text before any content has been added by the user - keepPlaceholderOnFocus: true // Keep the placeholder text showing even when the field is focused (leave this property off to remove placeholder content on focus) } ); }, @@ -85,7 +84,6 @@ registerBlockType( /* ... */, { formattingControls={ [ 'bold', 'italic' ] } // Allow the content to be made bold or italic, but do not allow other formatting options onChange={ ( content ) => setAttributes( { content } ) } // Store updated content as a block attribute placeholder={ __( 'Heading...' ) } // Display this text before any content has been added by the user - keepPlaceholderOnFocus // Keep the placeholder text showing even when the field is focused (leave this property off to remove placeholder content on focus) /> ); }, diff --git a/packages/block-editor/src/components/rich-text/README.md b/packages/block-editor/src/components/rich-text/README.md index 9cc0b3e93fccdd..e271bc5200e658 100644 --- a/packages/block-editor/src/components/rich-text/README.md +++ b/packages/block-editor/src/components/rich-text/README.md @@ -53,10 +53,6 @@ Render a rich [`contenteditable` input](https://developer.mozilla.org/en-US/docs *Optional.* Whether to show the input is selected or not in order to show the formatting controls. By default it renders the controls when the block is selected. -### `keepPlaceholderOnFocus: Boolean` - -*Optional.* By default, the placeholder will hide as soon as the editable field receives focus. With this setting it can be be kept while the field is focussed and empty. - ### `autocompleters: Array<Completer>` *Optional.* A list of autocompleters to use instead of the default. diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 8a3ef0550045bc..1cf2c6225afaa0 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -293,7 +293,6 @@ class RichTextWrapper extends Component { isSelected: originalIsSelected, onCreateUndoLevel, placeholder, - keepPlaceholderOnFocus, // eslint-disable-next-line no-unused-vars allowedFormats, withoutInteractiveFormatting, @@ -338,9 +337,8 @@ class RichTextWrapper extends Component { onSelectionChange={ onSelectionChange } tagName={ tagName } wrapperClassName={ classnames( wrapperClasses, wrapperClassName ) } - className={ classnames( classes, className ) } + className={ classnames( classes, className, { 'is-selected': originalIsSelected } ) } placeholder={ placeholder } - keepPlaceholderOnFocus={ keepPlaceholderOnFocus } allowedFormats={ adjustedAllowedFormats } withoutInteractiveFormatting={ withoutInteractiveFormatting } onEnter={ this.onEnter } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 61cbf2551751a1..ccb5e0d15bf7ec 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -49,7 +49,6 @@ function RichTextWraper( { isSelected: originalIsSelected, onCreateUndoLevel, placeholder, - keepPlaceholderOnFocus, // From experimental filter. ...experimentalProps } ) { @@ -68,7 +67,6 @@ function RichTextWraper( { wrapperClassName={ classnames( wrapperClasses, wrapperClassName ) } className={ classnames( classes, className ) } placeholder={ placeholder } - keepPlaceholderOnFocus={ keepPlaceholderOnFocus } __unstableIsSelected={ originalIsSelected } //__unstablePatterns={ getPatterns() } //__unstableEnterPatterns={ getEnterPatterns() } diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index d4a7e39978a4aa..e5498676c60159 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -34,39 +34,26 @@ } } - &[data-is-placeholder-visible="true"] { - position: absolute; - top: 0; - width: 100%; - margin-top: 0; - - & > p { - margin-top: 0; - } - - // Ensure that if placeholder wraps (mobile/nested contexts) the clickable area is full-height. - height: 100%; - } - - // Placeholder text. - & + .block-editor-rich-text__editable { + [data-rich-text-placeholder]::after { + content: attr(data-rich-text-placeholder); pointer-events: none; - // Use opacity to work in various editor styles. // We don't specify the color here, because blocks or editor styles might provide their own. - &, - p { - opacity: 0.62; - } + opacity: 0.62; } - // Captions may have lighter (gray) text, or be shown on a range of different background luminosites. - // To ensure legibility, we increase the default placeholder opacity to ensure contrast. - &[data-is-placeholder-visible="true"] + figcaption.block-editor-rich-text__editable { - opacity: 0.8; + // Could be unset for individual rich text instances. + &.is-selected [data-rich-text-placeholder]::after { + display: none; } } +// Captions may have lighter (gray) text, or be shown on a range of different background luminosites. +// To ensure legibility, we increase the default placeholder opacity to ensure contrast. +figcaption.block-editor-rich-text__editable [data-rich-text-placeholder]::before { + opacity: 0.8; +} + .block-editor-rich-text__inline-toolbar { display: flex; justify-content: center; diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index a5f70204d6557c..967dc29c9e44cb 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -127,7 +127,6 @@ class ButtonEdit extends Component { backgroundColor: backgroundColor.color, color: textColor.color, } } - keepPlaceholderOnFocus /> <BaseControl label={ __( 'Link' ) } diff --git a/packages/block-library/src/button/editor.scss b/packages/block-library/src/button/editor.scss index 4b0cc2381fb7d3..9fe1a9afabd1bd 100644 --- a/packages/block-library/src/button/editor.scss +++ b/packages/block-library/src/button/editor.scss @@ -22,7 +22,7 @@ } // Make placeholder text white unless custom colors or outline versions are chosen. - &:not(.has-text-color):not(.is-style-outline) .block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable { + &:not(.has-text-color):not(.is-style-outline) [data-rich-text-placeholder]::after { color: $white; } @@ -36,29 +36,14 @@ } // Increase placeholder opacity to meet contrast ratios. - .block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable { + [data-rich-text-placeholder]::after { opacity: 0.8; } - // Make placeholder disappear on focus to make focus-state visible. - .block-editor-rich-text__editable[data-is-placeholder-visible="true"]:focus + .block-editor-rich-text__editable { - opacity: 0; - } - - // Align cursor to left when placeholder is active. - .block-editor-rich-text__editable[data-is-placeholder-visible="true"] { - text-align: left; - } - // Don't let the placeholder text wrap in the variation preview. .block-editor-block-preview__content & { max-width: 100%; - // Polish the empty placeholder text for the button in variation previews. - .block-editor-rich-text__editable[data-is-placeholder-visible="true"] { - height: auto; - } - .wp-block-button__link { max-width: 100%; overflow: hidden; @@ -70,11 +55,6 @@ text-overflow: ellipsis; } } - - // Limit width of the text field if empty - .wp-block-button__link[data-is-placeholder-visible="true"] { - max-width: 150px; - } } .wp-block-button__inline-link { diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 616092ac82203f..3b7f1fa0e32a3e 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -212,7 +212,6 @@ class FileEdit extends Component { tagName="div" // must be block-level or else cursor disappears value={ fileName } placeholder={ __( 'Write file name…' ) } - keepPlaceholderOnFocus withoutInteractiveFormatting onChange={ ( text ) => setAttributes( { fileName: text } ) } /> @@ -225,7 +224,6 @@ class FileEdit extends Component { value={ downloadButtonText } withoutInteractiveFormatting placeholder={ __( 'Add text…' ) } - keepPlaceholderOnFocus onChange={ ( text ) => setAttributes( { downloadButtonText: text } ) } /> </div> diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 21623fad95abc9..910bed783e67f4 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -35,11 +35,6 @@ ul.wp-block-gallery { overflow-y: auto; } - .block-editor-rich-text figcaption:not([data-is-placeholder-visible="true"]) { - position: relative; - overflow: hidden; - } - .is-selected .block-editor-rich-text { // IE calculates this incorrectly, so leave it to modern browsers. @supports (position: sticky) { @@ -74,6 +69,9 @@ ul.wp-block-gallery { } .block-editor-rich-text figcaption { + position: relative; + overflow: hidden; + a { color: $white; } diff --git a/packages/block-library/src/paragraph/editor.scss b/packages/block-library/src/paragraph/editor.scss index 8efa8e47f73e0c..1ea726dcbb552a 100644 --- a/packages/block-library/src/paragraph/editor.scss +++ b/packages/block-library/src/paragraph/editor.scss @@ -1,7 +1,8 @@ // Specific to the empty paragraph placeholder: // when shown on mobile and in nested contexts, one or more icons show up on the right. // This padding makes sure it doesn't overlap text. -.block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable.wp-block-paragraph { +.block-editor-rich-text__editable.wp-block-paragraph:not(.is-selected) [data-rich-text-placeholder]::after { + display: inline-block; padding-right: $icon-button-size * 3; // In nested contexts only one icon shows up. diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 40834ab63f3ab3..81c2ec63d8acb4 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -13,7 +13,6 @@ export default function SearchEdit( { className, attributes, setAttributes } ) { wrapperClassName="wp-block-search__label" aria-label={ __( 'Label text' ) } placeholder={ __( 'Add label…' ) } - keepPlaceholderOnFocus withoutInteractiveFormatting value={ label } onChange={ ( html ) => setAttributes( { label: html } ) } @@ -33,7 +32,6 @@ export default function SearchEdit( { className, attributes, setAttributes } ) { className="wp-block-search__button-rich-text" aria-label={ __( 'Button text' ) } placeholder={ __( 'Add button text…' ) } - keepPlaceholderOnFocus withoutInteractiveFormatting value={ buttonText } onChange={ ( html ) => setAttributes( { buttonText: html } ) } diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index 8b46a1f9dfc8a4..c02a1c7dbcacc8 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -105,7 +105,7 @@ } // Ensure that the height of the first appender, and the one between blocks, is the same as text. - .block-editor-block-list__block[data-type="core/paragraph"] p[data-is-placeholder-visible="true"] + p, + .block-editor-block-list__block[data-type="core/paragraph"] p, .block-editor-default-block-appender__content { min-height: $empty-paragraph-height / 2; line-height: $editor-line-height; diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss index f7ac0e338ce99c..fda5b9c84944ed 100644 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss @@ -11,8 +11,4 @@ .block-editor-block-list__empty-block-inserter { left: -18px; } - - .block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable.wp-block-paragraph { - padding: 0; - } } diff --git a/packages/rich-text/src/component/editable.js b/packages/rich-text/src/component/editable.js index b4054a883c0d27..c5407fc308582c 100644 --- a/packages/rich-text/src/component/editable.js +++ b/packages/rich-text/src/component/editable.js @@ -81,8 +81,6 @@ function applyInternetExplorerInputFix( editorNode ) { }; } -const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; - /** * Whether or not the user agent is Internet Explorer. * @@ -106,8 +104,6 @@ export default class Editable extends Component { // update the attributes on the wrapper nodes here. `componentDidUpdate` // will never be called. shouldComponentUpdate( nextProps ) { - this.configureIsPlaceholderVisible( nextProps.isPlaceholderVisible ); - if ( ! isEqual( this.props.style, nextProps.style ) ) { this.editorNode.setAttribute( 'style', '' ); Object.assign( this.editorNode.style, { @@ -129,13 +125,6 @@ export default class Editable extends Component { return false; } - configureIsPlaceholderVisible( isPlaceholderVisible ) { - const isPlaceholderVisibleString = String( !! isPlaceholderVisible ); - if ( this.editorNode.getAttribute( IS_PLACEHOLDER_VISIBLE_ATTR_NAME ) !== isPlaceholderVisibleString ) { - this.editorNode.setAttribute( IS_PLACEHOLDER_VISIBLE_ATTR_NAME, isPlaceholderVisibleString ); - } - } - bindEditorNode( editorNode ) { this.editorNode = editorNode; this.props.setRef( editorNode ); @@ -158,7 +147,6 @@ export default class Editable extends Component { record, valueToEditableHTML, className, - isPlaceholderVisible, ...remainingProps } = this.props; @@ -190,7 +178,6 @@ export default class Editable extends Component { 'aria-multiline': true, className, contentEditable: true, - [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, ref: this.bindEditorNode, style: { ...style, diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 34792ad1d2b3aa..d16598417e2f78 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -181,6 +181,7 @@ class RichText extends Component { multilineWrapperTags: this.multilineWrapperTags, prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), __unstableDomOnly: domOnly, + placeholder: this.props.placeholder, } ); } @@ -725,6 +726,7 @@ class RichText extends Component { value, selectionStart, selectionEnd, + placeholder, __unstableIsSelected: isSelected, } = this.props; @@ -752,6 +754,10 @@ class RichText extends Component { shouldReapply = shouldReapply || ! isShallowEqual( prepareProps, prevPrepareProps ); + // Rerender if the placeholder changed. + shouldReapply = shouldReapply || + placeholder !== prevProps.placeholder; + const { activeFormats = [] } = this.record; if ( shouldReapply ) { @@ -808,6 +814,7 @@ class RichText extends Component { value, multilineTag: this.multilineTag, prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), + placeholder: this.props.placeholder, } ).body.innerHTML; } @@ -857,7 +864,6 @@ class RichText extends Component { wrapperClassName, className, placeholder, - keepPlaceholderOnFocus = false, __unstableIsSelected: isSelected, children, // To do: move autocompletion logic to rich-text. @@ -872,10 +878,8 @@ class RichText extends Component { // changes, we replace the relevant element. This is needed because we // prevent Editable component updates. const key = Tagname; - const MultilineTag = this.multilineTag; const ariaProps = pickAriaProps( this.props ); const record = this.getRecord(); - const isPlaceholderVisible = placeholder && ( ! isSelected || keepPlaceholderOnFocus ) && isEmpty( record ); const autoCompleteContent = ( { listBoxId, activeId } ) => ( <> @@ -884,7 +888,6 @@ class RichText extends Component { style={ style } record={ record } valueToEditableHTML={ this.valueToEditableHTML } - isPlaceholderVisible={ isPlaceholderVisible } aria-label={ placeholder } aria-autocomplete={ listBoxId ? 'list' : undefined } aria-owns={ listBoxId } @@ -902,14 +905,6 @@ class RichText extends Component { onTouchStart={ this.onPointerDown } setRef={ this.setRef } /> - { isPlaceholderVisible && - <Tagname - className={ classnames( 'rich-text', className ) } - style={ style } - > - { MultilineTag ? <MultilineTag>{ placeholder }</MultilineTag> : placeholder } - </Tagname> - } { isSelected && <FormatEdit allowedFormats={ allowedFormats } withoutInteractiveFormatting={ withoutInteractiveFormatting } diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index a07ae9aebd663c..92251fc547b8e6 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -333,11 +333,12 @@ function createFromElement( { continue; } - if ( - isEditableTree && - type === 'br' && - ! node.getAttribute( 'data-rich-text-line-break' ) - ) { + if ( isEditableTree && ( + // Ignore any placeholders. + node.getAttribute( 'data-rich-text-placeholder' ) || + // Ignore any line breaks that are not inserted by us. + ( type === 'br' && ! node.getAttribute( 'data-rich-text-line-break' ) ) + ) ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; } diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index d99cdd055feda0..9d7aed3cfdfd00 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -118,6 +118,7 @@ export function toDom( { multilineTag, prepareEditableTree, isEditableTree = true, + placeholder, } ) { let startPath = []; let endPath = []; @@ -147,6 +148,7 @@ export function toDom( { endPath = createPathToNode( pointer, body, [ pointer.nodeValue.length ] ); }, isEditableTree, + placeholder, } ); return { @@ -172,12 +174,14 @@ export function apply( { multilineTag, prepareEditableTree, __unstableDomOnly, + placeholder, } ) { // Construct a new element tree in memory. const { body, selection } = toDom( { value, multilineTag, prepareEditableTree, + placeholder, } ); applyValue( body, current ); diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index df68bd5413d0b0..f1047228507603 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -84,6 +84,7 @@ export function toTree( { onStartIndex, onEndIndex, isEditableTree, + placeholder, } ) { const { formats, replacements, text, start, end } = value; const formatsLength = formats.length + 1; @@ -248,6 +249,15 @@ export function toTree( { if ( shouldInsertPadding && i === text.length ) { append( getParent( pointer ), ZWNBSP ); + + if ( placeholder && text.length === 0 ) { + append( getParent( pointer ), { + type: 'span', + attributes: { + 'data-rich-text-placeholder': placeholder, + }, + } ); + } } lastCharacterFormats = characterFormats; From 567214a807b62123b98f9d70339886364d7e2919 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 26 Jul 2019 13:19:26 -0400 Subject: [PATCH 538/664] Compat: Add removal criteria for allowed CSS filter --- lib/compat.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/compat.php b/lib/compat.php index fb09a6e49ef97f..000279832a7839 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -12,6 +12,11 @@ * Filters allowed CSS attributes to include `flex-basis`, included in saved * markup of the Column block. * + * This can be removed when plugin support requires WordPress 5.3.0+. + * + * @see https://core.trac.wordpress.org/ticket/47281 + * @see https://core.trac.wordpress.org/changeset/45363 + * * @since 5.7.0 * * @param string[] $attr Array of allowed CSS attributes. From 50880f6e82aff5008abaa0dd0429cab920e24092 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Fri, 26 Jul 2019 13:23:53 -0400 Subject: [PATCH 539/664] Components: Restore default opacity to buttons (#16769) * Components: Restore default opacity to buttons * Components: Remove redundant DropdownMenu disabled styling --- packages/components/src/button/style.scss | 3 +++ packages/components/src/dropdown-menu/style.scss | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 27f3de079631a0..db27c0cd0e4f06 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -60,6 +60,7 @@ box-shadow: none; text-shadow: 0 1px 0 #fff; transform: none; + opacity: 1; } } @@ -109,6 +110,7 @@ border-color: color(theme(button) shade(7%)); box-shadow: none; text-shadow: none; + opacity: 1; // This specificity is needed to override alternate color schemes in WP-Admin. &.is-button, @@ -189,6 +191,7 @@ &:disabled, &[aria-disabled="true"] { cursor: default; + opacity: 0.3; } &:focus:not(:disabled) { diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index cbd41243062d3b..34f58970bf9f40 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -106,12 +106,4 @@ padding-left: 0.5rem; } } - - .components-dropdown-menu__menu-item, - .components-menu-item__button { - &:disabled, - &[aria-disabled="true"] { - opacity: 0.3; - } - } } From a5dc5aed45b2865a4cd90ce05644e8e4b2125ce4 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Fri, 26 Jul 2019 21:53:17 +0200 Subject: [PATCH 540/664] [RNMobile] Native mobile release v1.10.0 --- .../src/components/block-list/index.native.js | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index aeeb7fc815e557..5227bce63ecc67 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -61,19 +61,15 @@ export class BlockList extends Component { // create an empty block of the selected type const newBlock = createBlock( itemValue ); - this.finishInsertingOrReplacingBlock( newBlock ); + this.finishBlockAppendingOrReplacing( newBlock ); } - finishInsertingOrReplacingBlock( newBlock ) { + finishBlockAppendingOrReplacing( newBlock ) { + // now determine whether we need to replace the currently selected block (if it's empty) + // or just add a new block as usual if ( this.isReplaceable( this.props.selectedBlock ) ) { - // replace selected block + // do replace here this.props.replaceBlock( this.props.selectedBlockClientId, newBlock ); - } else if ( this.props.isPostTitleSelected && this.isReplaceable( this.props.firstBlock ) ) { - // replace first block in post: there is no selected block when the post title is selected, - // so replaceBlock does not select the new block and we need to manually select the new block - const { clientId: firstBlockId } = this.props.firstBlock; - this.props.replaceBlock( firstBlockId, newBlock ); - this.props.selectBlock( newBlock.clientId ); } else { this.props.insertBlock( newBlock, this.getNewBlockInsertionIndex() ); } @@ -114,7 +110,7 @@ export class BlockList extends Component { newMediaBlock.attributes.id = payload.mediaId; // finally append or replace as appropriate - this.finishInsertingOrReplacingBlock( newMediaBlock ); + this.finishBlockAppendingOrReplacing( newMediaBlock ); } ); } @@ -264,7 +260,7 @@ export class BlockList extends Component { const paragraphBlock = createBlock( 'core/paragraph' ); return ( <TouchableWithoutFeedback onPress={ () => { - this.finishInsertingOrReplacingBlock( paragraphBlock ); + this.finishBlockAppendingOrReplacing( paragraphBlock ); } } > <View style={ styles.blockListFooter } /> </TouchableWithoutFeedback> @@ -281,7 +277,6 @@ export class BlockList extends Component { export default compose( [ withSelect( ( select, { rootClientId } ) => { const { - getBlock, getBlockCount, getBlockName, getBlockIndex, @@ -292,15 +287,13 @@ export default compose( [ } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); - const blockClientIds = getBlockOrder( rootClientId ); return { - blockClientIds, + blockClientIds: getBlockOrder( rootClientId ), blockCount: getBlockCount( rootClientId ), getBlockName, isBlockSelected, selectedBlock: getSelectedBlock(), - firstBlock: getBlock( blockClientIds[ 0 ] ), selectedBlockClientId, selectedBlockOrder: getBlockIndex( selectedBlockClientId ), }; @@ -310,14 +303,12 @@ export default compose( [ insertBlock, replaceBlock, clearSelectedBlock, - selectBlock, } = dispatch( 'core/block-editor' ); return { clearSelectedBlock, insertBlock, replaceBlock, - selectBlock, }; } ), ] )( BlockList ); From dd4d2058618dbb4c61aeb8097c17bc0eb3a1d687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 29 Jul 2019 11:55:22 +0200 Subject: [PATCH 541/664] Configure Babel to target your current version of Node with Jest (#16259) --- package-lock.json | 58 +++++++--------------- package.json | 1 - packages/babel-preset-default/CHANGELOG.md | 7 +++ packages/babel-preset-default/index.js | 5 +- packages/babel-preset-default/package.json | 3 +- packages/scripts/CHANGELOG.md | 4 ++ packages/scripts/package.json | 1 + 7 files changed, 35 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16b1a4037b3d61..1908b09d6c3491 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3713,7 +3713,8 @@ "@babel/preset-env": "^7.4.4", "@babel/runtime": "^7.4.4", "@wordpress/babel-plugin-import-jsx-pragma": "file:packages/babel-plugin-import-jsx-pragma", - "@wordpress/browserslist-config": "file:packages/browserslist-config" + "@wordpress/browserslist-config": "file:packages/browserslist-config", + "core-js": "^3.1.4" } }, "@wordpress/blob": { @@ -4276,6 +4277,7 @@ "@wordpress/eslint-plugin": "file:packages/eslint-plugin", "@wordpress/jest-preset-default": "file:packages/jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:packages/npm-package-json-lint-config", + "babel-jest": "^24.7.1", "babel-loader": "^8.0.5", "chalk": "^2.4.1", "check-node-version": "^3.1.1", @@ -8138,58 +8140,34 @@ } }, "core-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.1.tgz", - "integrity": "sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz", + "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==", "dev": true }, "core-js-compat": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.0.1.tgz", - "integrity": "sha512-2pC3e+Ht/1/gD7Sim/sqzvRplMiRnFQVlPpDVaHtY9l7zZP7knamr3VRD6NyGfHd84MrDC0tAM9ulNxYMW0T3g==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", + "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", "dev": true, "requires": { - "browserslist": "^4.5.4", - "core-js": "3.0.1", - "core-js-pure": "3.0.1", - "semver": "^6.0.0" + "browserslist": "^4.6.2", + "core-js-pure": "3.1.4", + "semver": "^6.1.1" }, "dependencies": { - "browserslist": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.5.5.tgz", - "integrity": "sha512-0QFO1r/2c792Ohkit5XI8Cm8pDtZxgNl2H6HU4mHrpYz7314pEYcsAVVatM0l/YmxPnEzh9VygXouj4gkFUTKA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000960", - "electron-to-chromium": "^1.3.124", - "node-releases": "^1.1.14" - } - }, - "caniuse-lite": { - "version": "1.0.30000963", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000963.tgz", - "integrity": "sha512-n4HUiullc7Lw0LyzpeLa2ffP8KxFBGdxqD/8G3bSL6oB758hZ2UE2CVK+tQN958tJIi0/tfpjAc67aAtoHgnrQ==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.125", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.125.tgz", - "integrity": "sha512-XxowpqQxJ4nDwUXHtVtmEhRqBpm2OnjBomZmZtHD0d2Eo0244+Ojezhk3sD/MBSSe2nxCdGQFRXHIsf/LUTL9A==", - "dev": true - }, "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", + "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==", "dev": true } } }, "core-js-pure": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.0.1.tgz", - "integrity": "sha512-mSxeQ6IghKW3MoyF4cz19GJ1cMm7761ON+WObSyLfTu/Jn3x7w4NwNFnrZxgl4MTSvYYepVLNuRtlB4loMwJ5g==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", + "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==", "dev": true }, "core-util-is": { diff --git a/package.json b/package.json index 3405bac2525cd6..1429c4a3945f7d 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "commander": "2.20.0", "concurrently": "3.5.0", "copy-webpack-plugin": "4.5.2", - "core-js": "3.0.1", "cross-env": "3.2.4", "cssnano": "4.1.10", "deep-freeze": "0.0.1", diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index 4f214688f24805..ad77db1ec4a04b 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -1,3 +1,10 @@ +## Master + +### Bug Fixes + +- Configure Babel to target your current version of Node as described in [Jest docs](https://jestjs.io/docs/en/getting-started#using-babel). +- Added missing [core-js](https://www.npmjs.com/package/core-js) dependency ([#16259](https://github.com/WordPress/gutenberg/pull/16259)). + ## 4.2.0 (2019-05-21) ### New Features diff --git a/packages/babel-preset-default/index.js b/packages/babel-preset-default/index.js index 1e1ca3c2980b15..a1343706b03b8b 100644 --- a/packages/babel-preset-default/index.js +++ b/packages/babel-preset-default/index.js @@ -18,8 +18,9 @@ module.exports = function( api ) { const opts = {}; if ( isTestEnv ) { - opts.useBuiltIns = 'usage'; - opts.corejs = 3; + opts.targets = { + node: 'current', + }; } else { opts.modules = false; opts.targets = { diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index e0ce0c0f2a3cd1..3bf0df978d6ebb 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -35,7 +35,8 @@ "@babel/preset-env": "^7.4.4", "@babel/runtime": "^7.4.4", "@wordpress/babel-plugin-import-jsx-pragma": "file:../babel-plugin-import-jsx-pragma", - "@wordpress/browserslist-config": "file:../browserslist-config" + "@wordpress/browserslist-config": "file:../browserslist-config", + "core-js": "^3.1.4" }, "peerDependencies": { "@babel/core": "^7.0.0" diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 5fb6c959d8c14c..322369b244bdf0 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -4,6 +4,10 @@ - The `build` and `start` commands supports simplified syntax for multiple entry points: `wp-scripts build entry-one.js entry-two.js` ([15982](https://github.com/WordPress/gutenberg/pull/15982)). +### Bug Fix + +- Added missing [babel-jest](https://www.npmjs.com/package/babel-jest) dependency ([#16259](https://github.com/WordPress/gutenberg/pull/16259)). + ## 3.3.0 (2019-06-12) ### New Features diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 681b40742999c5..4001466804b733 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -37,6 +37,7 @@ "@wordpress/eslint-plugin": "file:../eslint-plugin", "@wordpress/jest-preset-default": "file:../jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", + "babel-jest": "^24.7.1", "babel-loader": "^8.0.5", "chalk": "^2.4.1", "check-node-version": "^3.1.1", From f4206ef4a4f73cb483e8e8e44266e36502b072dc Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Mon, 29 Jul 2019 05:08:38 -0700 Subject: [PATCH 542/664] add placeholder attribute and use it in the UI (#16783) --- packages/block-library/src/button/block.json | 3 +++ packages/block-library/src/button/edit.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index 32fda95348d8a1..9c7f24ea24692a 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -42,6 +42,9 @@ "source": "attribute", "selector": "a", "attribute": "rel" + }, + "placeholder": { + "type": "string" } } } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 967dc29c9e44cb..b258486b989203 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -104,6 +104,7 @@ class ButtonEdit extends Component { title, linkTarget, rel, + placeholder, } = attributes; const linkId = `wp-block-button__inline-link-${ instanceId }`; @@ -111,7 +112,7 @@ class ButtonEdit extends Component { return ( <div className={ className } title={ title } ref={ this.bindRef }> <RichText - placeholder={ __( 'Add text…' ) } + placeholder={ placeholder || __( 'Add text…' ) } value={ text } onChange={ ( value ) => setAttributes( { text: value } ) } withoutInteractiveFormatting From b5a116bebf7b4475d70cd751a76bebdde70bc59d Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 29 Jul 2019 09:08:34 -0400 Subject: [PATCH 543/664] Testing: Whitelist specific files for PHPCS (#16772) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Testing: Whitelist specific files for PHPCS * Testing: Add missing whitelisted files Co-Authored-By: Sören Wrede <soerenwrede@gmail.com> --- phpcs.xml.dist | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 1a8d5adb1183ba..381e9f22e3e2a3 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -25,15 +25,15 @@ <arg value="ps"/> <arg name="extensions" value="php"/> - <file>.</file> - - <!-- Exclude 3rd party libraries --> - <exclude-pattern>./node_modules</exclude-pattern> - <exclude-pattern>./vendor</exclude-pattern> + <file>./bin</file> + <file>./gutenberg.php</file> + <file>./lib</file> + <file>./packages</file> + <file>./phpunit</file> + <file>./post-content.php</file> <!-- Exclude generated files --> <exclude-pattern>./packages/block-serialization-spec-parser/parser.php</exclude-pattern> - <exclude-pattern>./build</exclude-pattern> <!-- These special comments are markers for the build process --> <rule ref="Squiz.Commenting.InlineComment.WrongStyle"> From 40de72728f702e1857947e3dbcba41376c1c490b Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 29 Jul 2019 14:09:55 +0100 Subject: [PATCH 544/664] Fix: Image size labels don't appear on the widget screen (#16763) --- lib/widgets-page.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/widgets-page.php b/lib/widgets-page.php index 27ebadcb9b9aa0..6e5263f267522c 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -50,10 +50,30 @@ function gutenberg_widgets_init( $hook ) { $max_upload_size = 0; } + /** This filter is documented in wp-admin/includes/media.php */ + $image_size_names = apply_filters( + 'image_size_names_choose', + array( + 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), + 'medium' => __( 'Medium', 'gutenberg' ), + 'large' => __( 'Large', 'gutenberg' ), + 'full' => __( 'Full Size', 'gutenberg' ), + ) + ); + + $available_image_sizes = array(); + foreach ( $image_size_names as $image_size_slug => $image_size_name ) { + $available_image_sizes[] = array( + 'slug' => $image_size_slug, + 'name' => $image_size_name, + ); + } + $settings = array_merge( array( 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'imageSizes' => $available_image_sizes, 'maxUploadFileSize' => $max_upload_size, ), gutenberg_get_legacy_widget_settings() From 9f4a76cbc2030770577f89696f99ae9214731057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wrede?= <soerenwrede@gmail.com> Date: Mon, 29 Jul 2019 15:39:20 +0200 Subject: [PATCH 545/664] Docs: Update helphub links (#16787) --- docs/contributors/getting-started.md | 2 +- .../src/components/header/post-publish-button-or-toggle.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributors/getting-started.md b/docs/contributors/getting-started.md index 9fbe2b10c182ee..24217faed45fd9 100644 --- a/docs/contributors/getting-started.md +++ b/docs/contributors/getting-started.md @@ -67,7 +67,7 @@ Alternatively, you can use your own local WordPress environment and clone this r Next, open a terminal (or if on Windows, a command prompt) and navigate to the repository you cloned. Now type `npm install` to get the dependencies all set up. Then you can type `npm run dev` in your terminal or command prompt to keep the plugin building in the background as you work on it. -WordPress comes with specific [debug systems](https://codex.wordpress.org/Debugging_in_WordPress) designed to simplify the process as well as standardize code across core, plugins and themes. It is possible to use environment variables (`WP_DEBUG` and `SCRIPT_DEBUG`) to update a site's configuration constants located in `wp-config.php` file. Both flags can be disabled at any time by running the following command: +WordPress comes with specific [debug systems](https://wordpress.org/support/article/debugging-in-wordpress/) designed to simplify the process as well as standardize code across core, plugins and themes. It is possible to use environment variables (`WP_DEBUG` and `SCRIPT_DEBUG`) to update a site's configuration constants located in `wp-config.php` file. Both flags can be disabled at any time by running the following command: ``` SCRIPT_DEBUG=false WP_DEBUG=false ./bin/setup-local-env.sh ``` diff --git a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js index 828ba3ef6fc049..967b17f43d04a4 100644 --- a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js +++ b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js @@ -32,7 +32,7 @@ export function PostPublishButtonOrToggle( { * Conditions to show a BUTTON (publish directly) or a TOGGLE (open publish sidebar): * * 1) We want to show a BUTTON when the post status is at the _final stage_ - * for a particular role (see https://codex.wordpress.org/Post_Status): + * for a particular role (see https://wordpress.org/support/article/post-status/): * * - is published * - is scheduled to be published From 403604674648e786e532c1bf2578496e81bf92f5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Mon, 29 Jul 2019 16:47:20 +0200 Subject: [PATCH 546/664] Bump plugin version to 6.2.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 64b6a24f8c9cab..ad196b1ba7106c 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.1.1 + * Version: 6.2.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 1908b09d6c3491..9c1ce97f40bd55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.1.1", + "version": "6.2.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1429c4a3945f7d..5f7f4f6b520098 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.1.1", + "version": "6.2.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From f598210ecf3944fd9a66e5266399853b729c7293 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Tue, 30 Jul 2019 01:08:09 +1000 Subject: [PATCH 547/664] Project Management: Assign issues 'fixed' by a PR to the author of that PR (#16700) Adds a GitHub Action which, when a pull request is opened, searches the pull request description for 'Fixes #XXXX' and, for each found issue: - Adds the author of the pull request as an assignee. - Adds the `[Status] In Progress` label. --- .../actions/assign-fixed-issues/Dockerfile | 18 +++++++ .../actions/assign-fixed-issues/entrypoint.sh | 51 +++++++++++++++++++ .github/main.workflow | 16 ++++++ 3 files changed, 85 insertions(+) create mode 100644 .github/actions/assign-fixed-issues/Dockerfile create mode 100755 .github/actions/assign-fixed-issues/entrypoint.sh diff --git a/.github/actions/assign-fixed-issues/Dockerfile b/.github/actions/assign-fixed-issues/Dockerfile new file mode 100644 index 00000000000000..e81b9490ab5fe1 --- /dev/null +++ b/.github/actions/assign-fixed-issues/Dockerfile @@ -0,0 +1,18 @@ +FROM debian:stable-slim + +LABEL "name"="Assign Fixed Issues" +LABEL "maintainer"="The WordPress Contributors" +LABEL "version"="1.0.0" + +LABEL "com.github.actions.name"="Assign Fixed Issues" +LABEL "com.github.actions.description"="Assigns the issues fixed by a pull request to the author of that pull request" +LABEL "com.github.actions.icon"="flag" +LABEL "com.github.actions.color"="green" + +RUN apt-get update && \ + apt-get install --no-install-recommends -y jq curl ca-certificates && \ + apt-get clean -y + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/.github/actions/assign-fixed-issues/entrypoint.sh b/.github/actions/assign-fixed-issues/entrypoint.sh new file mode 100755 index 00000000000000..c8c15d53d88f71 --- /dev/null +++ b/.github/actions/assign-fixed-issues/entrypoint.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -e + +# 1. Find the issues that this PR 'fixes'. + +issues=$( + jq -r '.pull_request.body' $GITHUB_EVENT_PATH | perl -nle 'print $1 while / + (?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) + :? + \ + + (?:\#?|https?:\/\/github\.com\/WordPress\/gutenberg\/issues\/) + (\d+) + /igx' +) + +if [ -z "$issues" ]; then + echo "Pull request does not 'fix' any issues. Aborting." + exit 78 +fi + +# 2. Grab the author of the PR. + +author=$(jq -r '.pull_request.user.login' $GITHUB_EVENT_PATH) + +# 3. Loop through each 'fixed' issue. + +for issue in $issues; do + + # 3a. Add the author as an asignee to the issue. This fails if the author is + # already assigned, which is expected and ignored. + + curl \ + --silent \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"assignees\":[\"$author\"]}" \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/issues/$issue/assignees" > /dev/null + + # 3b. Label the issue as 'In Progress'. This fails if the label is already + # applied, which is expected and ignored. + + curl \ + --silent \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"labels":["[Status] In Progress"]}' \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/issues/$issue/labels" > /dev/null + +done diff --git a/.github/main.workflow b/.github/main.workflow index fcfd011ad3a8f0..5c1481cb96aa9c 100644 --- a/.github/main.workflow +++ b/.github/main.workflow @@ -7,3 +7,19 @@ action "Milestone It" { uses = "./.github/actions/milestone-it" secrets = ["GITHUB_TOKEN"] } + +workflow "Assign fixed issues when pull request opened" { + on = "pull_request" + resolves = ["Assign Fixed Issues"] +} + +action "Filter opened" { + uses = "actions/bin/filter@0dbb077f64d0ec1068a644d25c71b1db66148a24" + args = "action opened" +} + +action "Assign Fixed Issues" { + uses = "./.github/actions/assign-fixed-issues" + needs = ["Filter opened"] + secrets = ["GITHUB_TOKEN"] +} From 442ff9d53ea3960e0a20b666c3e064195ba14c42 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 29 Jul 2019 16:43:47 +0100 Subject: [PATCH 548/664] Update: Document and Remove experimental mark from URLPopover.LinkViewer and URLPopover.LinkEditor. (#16566) --- .../src/components/url-popover/README.md | 77 ++++++++++++++++++ .../src/components/url-popover/index.js | 80 +------------------ .../src/components/url-popover/link-editor.js | 42 ++++++++++ .../src/components/url-popover/link-viewer.js | 56 +++++++++++++ packages/block-library/src/image/edit.js | 6 +- packages/format-library/src/link/inline.js | 6 +- 6 files changed, 185 insertions(+), 82 deletions(-) create mode 100644 packages/block-editor/src/components/url-popover/link-editor.js create mode 100644 packages/block-editor/src/components/url-popover/link-viewer.js diff --git a/packages/block-editor/src/components/url-popover/README.md b/packages/block-editor/src/components/url-popover/README.md index 240daaf43c45d3..86805b10229e87 100644 --- a/packages/block-editor/src/components/url-popover/README.md +++ b/packages/block-editor/src/components/url-popover/README.md @@ -111,3 +111,80 @@ drawer. - Type: `Function` - Required: No + + +## Useful UI pieces + +The URLPopover exposes two components that may be used as child components to make the UI creation process easier. +Although in the editor these components are used inside URLPopover and they were built with URLPopover use cases in mind, it maybe is possible and perfectly fine to use them standalone if they fit a use-case. + +### LinkViewer + +LinkViewer provides a simple UI that allows users to see a link and may also offer a button to switch to a mode that will enable editing that link. +The component accepts the following props. Any other props are passed through to the underlying `div` container. + +### className + +A class that together with "block-editor-url-popover__link-viewer" is used as a class of the wrapper div. +If no className is passed only "block-editor-url-popover__link-viewer" is used. + +- Type: `String` +- Required: No + +### linkClassName + +A class that will be used in the component that renders the link. + +- Type: `String` +- Required: No + +### url + +The current URL to view. + +- Type: `String` +- Required: Yes + +### urlLabel + +The URL label, if not passed a label is automatically generated from the `url`. + +- Type: `String` +- Required: No + +### onEditLinkClick + +A function called when the user presses the button that allows editing a link. If not passed the link-edit button is not rendered. + +- Type: `function` +- Required: No + + +### LinkEditor + +LinkEditor provides a simple UI that allows users to edit link. +The component accepts the following props. Any other props are passed through to the underlying `form` container. + +### value + +This property should be set to the attribute (or component state) property used to store the URL. +This property is directly passed to `URLInput` component ([refer to its documentation](/packages/components/src/url-input/README.md)) to read additional details. + +- Type: `String` +- Required: Yes + +### onChange + +Called when the value changes. The second parameter is `null` unless the user selects a post from the suggestions dropdown. +More +This property is directly passed to component `URLInput` ([refer to its documentation](/packages/components/src/url-input/README.md)) to read additional details. + +- Type: `function` +- Required: Yes + +### autocompleteRef + +Reference passed to the auto complete element of the ([URLInput component](/packages/components/src/url-input/README.md)). + +- Type: `Object` +- Required: no diff --git a/packages/block-editor/src/components/url-popover/index.js b/packages/block-editor/src/components/url-popover/index.js index d3c7c874c6892a..bc4354cbeb9a01 100644 --- a/packages/block-editor/src/components/url-popover/index.js +++ b/packages/block-editor/src/components/url-popover/index.js @@ -1,24 +1,18 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { - ExternalLink, IconButton, Popover, } from '@wordpress/components'; -import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; /** * Internal dependencies */ -import URLInput from '../url-input'; +import LinkViewer from './link-viewer'; +import LinkEditor from './link-editor'; class URLPopover extends Component { constructor() { @@ -91,75 +85,9 @@ class URLPopover extends Component { } } -const LinkEditor = ( { - autocompleteRef, - className, - onChangeInputValue, - value, - ...props -} ) => ( - <form - className={ classnames( - 'block-editor-url-popover__link-editor', - className - ) } - { ...props } - > - <URLInput - value={ value } - onChange={ onChangeInputValue } - autocompleteRef={ autocompleteRef } - /> - <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> - - </form> -); - -URLPopover.__experimentalLinkEditor = LinkEditor; - -const LinkViewerUrl = ( { url, urlLabel, className } ) => { - const linkClassName = classnames( - className, - 'block-editor-url-popover__link-viewer-url' - ); - - if ( ! url ) { - return <span className={ linkClassName }></span>; - } - - return ( - <ExternalLink - className={ linkClassName } - href={ url } - > - { urlLabel || filterURLForDisplay( safeDecodeURI( url ) ) } - </ExternalLink> - ); -}; - -const LinkViewer = ( { - className, - url, - urlLabel, - editLink, - linkClassName, - ...props -} ) => { - return ( - <div - className={ classnames( - 'block-editor-url-popover__link-viewer', - className - ) } - { ...props } - > - <LinkViewerUrl url={ url } urlLabel={ urlLabel } className={ linkClassName } /> - <IconButton icon="edit" label={ __( 'Edit' ) } onClick={ editLink } /> - </div> - ); -}; +URLPopover.LinkEditor = LinkEditor; -URLPopover.__experimentalLinkViewer = LinkViewer; +URLPopover.LinkViewer = LinkViewer; /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/url-popover/README.md diff --git a/packages/block-editor/src/components/url-popover/link-editor.js b/packages/block-editor/src/components/url-popover/link-editor.js new file mode 100644 index 00000000000000..ebb96e00a302a0 --- /dev/null +++ b/packages/block-editor/src/components/url-popover/link-editor.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + IconButton, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import URLInput from '../url-input'; + +export default function LinkEditor( { + autocompleteRef, + className, + onChangeInputValue, + value, + ...props +} ) { + return ( + <form + className={ classnames( + 'block-editor-url-popover__link-editor', + className + ) } + { ...props } + > + <URLInput + value={ value } + onChange={ onChangeInputValue } + autocompleteRef={ autocompleteRef } + /> + <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> + </form> + ); +} diff --git a/packages/block-editor/src/components/url-popover/link-viewer.js b/packages/block-editor/src/components/url-popover/link-viewer.js new file mode 100644 index 00000000000000..3447cb3dfcf15a --- /dev/null +++ b/packages/block-editor/src/components/url-popover/link-viewer.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + ExternalLink, + IconButton, +} from '@wordpress/components'; +import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; + +function LinkViewerUrl( { url, urlLabel, className } ) { + const linkClassName = classnames( + className, + 'block-editor-url-popover__link-viewer-url' + ); + + if ( ! url ) { + return <span className={ linkClassName }></span>; + } + + return ( + <ExternalLink + className={ linkClassName } + href={ url } + > + { urlLabel || filterURLForDisplay( safeDecodeURI( url ) ) } + </ExternalLink> + ); +} + +export default function LinkViewer( { + className, + linkClassName, + onEditLinkClick, + url, + urlLabel, + ...props +} ) { + return ( + <div + className={ classnames( + 'block-editor-url-popover__link-viewer', + className + ) } + { ...props } + > + <LinkViewerUrl url={ url } urlLabel={ urlLabel } className={ linkClassName } /> + { onEditLinkClick && <IconButton icon="edit" label={ __( 'Edit' ) } onClick={ onEditLinkClick } /> } + </div> + ); +} diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index ea948b51915b17..0a51b05d50c73d 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -229,7 +229,7 @@ const ImageURLInputUI = ( { ) } > { ( ! url || isEditingLink ) && ( - <URLPopover.__experimentalLinkEditor + <URLPopover.LinkEditor className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" value={ linkEditorValue } onChangeInputValue={ setUrlInput } @@ -241,11 +241,11 @@ const ImageURLInputUI = ( { ) } { ( url && ! isEditingLink ) && ( <> - <URLPopover.__experimentalLinkViewer + <URLPopover.LinkViewer className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" onKeyPress={ stopPropagation } url={ url } - editLink={ startEditLink } + onEditLinkClick={ startEditLink } urlLabel={ urlLabel } /> <IconButton diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 564ae795fd484b..ac927d2bed1fb0 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -210,7 +210,7 @@ class InlineLinkUI extends Component { ) } > { showInput ? ( - <URLPopover.__experimentalLinkEditor + <URLPopover.LinkEditor className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" value={ inputValue } onChangeInputValue={ this.onChangeInputValue } @@ -220,11 +220,11 @@ class InlineLinkUI extends Component { autocompleteRef={ this.autocompleteRef } /> ) : ( - <URLPopover.__experimentalLinkViewer + <URLPopover.LinkViewer className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content" onKeyPress={ stopKeyPropagation } url={ url } - editLink={ this.editLink } + onEditLinkClick={ this.editLink } linkClassName={ isValidHref( prependHTTP( url ) ) ? undefined : 'has-invalid-link' } /> ) } From fcbdd20ee17b298ac236f71dc4102020882216df Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Mon, 29 Jul 2019 14:12:26 -0400 Subject: [PATCH 549/664] Editor: Avoid passing event object to save button onSave prop (#16770) --- packages/editor/src/components/post-saved-state/index.js | 4 ++-- .../post-saved-state/test/__snapshots__/index.js.snap | 2 +- packages/editor/src/components/post-saved-state/test/index.js | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js index 26716757bc5886..18c694d8c0eb8f 100644 --- a/packages/editor/src/components/post-saved-state/index.js +++ b/packages/editor/src/components/post-saved-state/index.js @@ -103,7 +103,7 @@ export class PostSavedState extends Component { <IconButton className="editor-post-save-draft" label={ label } - onClick={ onSave } + onClick={ () => onSave() } shortcut={ displayShortcut.primary( 's' ) } icon="cloud-upload" /> @@ -113,7 +113,7 @@ export class PostSavedState extends Component { return ( <Button className="editor-post-save-draft" - onClick={ onSave } + onClick={ () => onSave() } shortcut={ displayShortcut.primary( 's' ) } isTertiary > diff --git a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap index e96dde9ca76942..bd41d7a5fbbadc 100644 --- a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap @@ -6,7 +6,7 @@ exports[`PostSavedState should return Save button if edits to be saved 1`] = ` <ForwardRef(Button) className="editor-post-save-draft" isTertiary={true} - onClick={[MockFunction]} + onClick={[Function]} shortcut="Ctrl+S" > Save Draft diff --git a/packages/editor/src/components/post-saved-state/test/index.js b/packages/editor/src/components/post-saved-state/test/index.js index 25e60154997a28..cbb581aa0c6e71 100644 --- a/packages/editor/src/components/post-saved-state/test/index.js +++ b/packages/editor/src/components/post-saved-state/test/index.js @@ -66,7 +66,9 @@ describe( 'PostSavedState', () => { ); expect( wrapper ).toMatchSnapshot(); - wrapper.simulate( 'click' ); + wrapper.simulate( 'click', {} ); expect( saveSpy ).toHaveBeenCalled(); + // Regression: Verify the event object is not passed to prop callback. + expect( saveSpy.mock.calls[ 0 ] ).toEqual( [] ); } ); } ); From d45c8ea9f3adafba76676a29f07f28dfd0f2d444 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski <buritomath@gmail.com> Date: Mon, 29 Jul 2019 20:23:11 +0200 Subject: [PATCH 550/664] Avoid usage of unguarded getRangeAt and add eslint rule (#16212) * Avoid usage of unguarded getRangeAt and add eslint rule * Address PR comments * ESLint Plugin: Add CHANGELOG entry for avoid-unguarded-get-range-at * ESLint Plugin: Rename avoid-unguarded-get-range-at to no-unguarded-get-range-at --- packages/components/src/autocomplete/index.js | 3 +- packages/eslint-plugin/CHANGELOG.md | 1 + packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/configs/custom.js | 1 + .../docs/rules/no-unguarded-get-range-at.md | 18 ++++++++++++ .../__tests__/no-unguarded-get-range-at.js | 29 +++++++++++++++++++ .../rules/no-unguarded-get-range-at.js | 16 ++++++++++ 7 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md create mode 100644 packages/eslint-plugin/rules/__tests__/no-unguarded-get-range-at.js create mode 100644 packages/eslint-plugin/rules/no-unguarded-get-range-at.js diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index 6cc8a87e3a3999..1226ec3bbfd7fe 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -130,7 +130,8 @@ function filterOptions( search, options = [], maxResults = 10 ) { } function getCaretRect() { - const range = window.getSelection().getRangeAt( 0 ); + const selection = window.getSelection(); + const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; if ( range ) { return getRectangleFromRange( range ); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index b209286ab3fd3b..c00d09ef19f02c 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,7 @@ ### New Features - [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) now supports an `excludePattern` option to exempt function calls by name. +- New Rule: [`@wordpress/no-unguarded-get-range-at`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) ### Improvements diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 2c8ef189000c15..f6f89c95f10819 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -55,6 +55,7 @@ Rule|Description|Recommended [react-no-unsafe-timeout](/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md)|Disallow unsafe `setTimeout` in component| [valid-sprintf](/packages/eslint-plugin/docs/rules/valid-sprintf.md)|Enforce valid sprintf usage|✓ [no-base-control-with-label-without-id](/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md)| Disallow the usage of BaseControl component with a label prop set but omitting the id property|✓ +[no-unguarded-get-range-at](/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md)| Disallow the usage of unguarded `getRangeAt` calls|✓ ### Legacy diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index a09c22148cc592..efb873980b22f4 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -8,6 +8,7 @@ module.exports = { '@wordpress/no-unused-vars-before-return': 'error', '@wordpress/valid-sprintf': 'error', '@wordpress/no-base-control-with-label-without-id': 'error', + '@wordpress/no-unguarded-get-range-at': 'error', 'no-restricted-syntax': [ 'error', { diff --git a/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md b/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md new file mode 100644 index 00000000000000..1dd2e3e3ec44b0 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md @@ -0,0 +1,18 @@ +# Avoid unguarded getRangeAt (no-unguarded-get-range-at) + +Some browsers (e.g. Safari) will throw an error when `getRangeAt` is called and there are no ranges in the selection. + +## Rule details + +Example of **incorrect** code for this rule: + +```js +window.getSelection().getRangeAt( 0 ); +``` + +Example of **correct** code for this rule: + +```js +const selection = window.getSelection(); +const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; +``` diff --git a/packages/eslint-plugin/rules/__tests__/no-unguarded-get-range-at.js b/packages/eslint-plugin/rules/__tests__/no-unguarded-get-range-at.js new file mode 100644 index 00000000000000..b3e0a7592f8f08 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/no-unguarded-get-range-at.js @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../no-unguarded-get-range-at'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'no-unguarded-get-range-at', rule, { + valid: [ + { + code: `const selection = window.getSelection(); const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null;`, + }, + ], + invalid: [ + { + code: `window.getSelection().getRangeAt( 0 );`, + errors: [ { message: 'Avoid unguarded getRangeAt' } ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/no-unguarded-get-range-at.js b/packages/eslint-plugin/rules/no-unguarded-get-range-at.js new file mode 100644 index 00000000000000..e74bc775de54f3 --- /dev/null +++ b/packages/eslint-plugin/rules/no-unguarded-get-range-at.js @@ -0,0 +1,16 @@ +module.exports = { + meta: { + type: 'problem', + schema: [], + }, + create( context ) { + return { + 'CallExpression[callee.object.callee.object.name="window"][callee.object.callee.property.name="getSelection"][callee.property.name="getRangeAt"]'( node ) { + context.report( { + node, + message: 'Avoid unguarded getRangeAt', + } ); + }, + }; + }, +}; From 9446f27292e7865ec146acc35a3fe51b84c7e6e3 Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Tue, 30 Jul 2019 10:11:10 +1000 Subject: [PATCH 551/664] Visible focus and active styles for Windows high contrast mode (#16554) * Visible focus/active styles in high contrast mode. * Updated mixin call. --- assets/stylesheets/_mixins.scss | 4 ---- .../src/components/inserter-list-item/style.scss | 3 --- packages/components/src/color-palette/style.scss | 10 ++++++---- packages/components/src/date-time/style.scss | 3 +-- packages/components/src/toolbar-button/style.scss | 12 ++++++++++++ 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index b9b198631e895c..ef15e92e46b81f 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -143,7 +143,6 @@ // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; - outline-offset: -2px; } // Switch. @@ -173,7 +172,6 @@ // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; - outline-offset: -2px; } // Tabs, Inputs, Square buttons. @@ -192,7 +190,6 @@ // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; - outline-offset: -2px; } // Square buttons. @@ -244,7 +241,6 @@ // Windows High Contrast mode will show this outline, but not the box-shadow. outline: 2px solid transparent; - outline-offset: -2px; } @mixin block-style__is-active() { diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index 5725edf2f67b72..cf6bc1ba697a17 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -53,9 +53,6 @@ &:active, &:focus { position: relative; - - // Show the focus style in the icon inside instead. - outline: none; @include block-style__focus(); .block-editor-block-types-list__item-icon, diff --git a/packages/components/src/color-palette/style.scss b/packages/components/src/color-palette/style.scss index 97f7502a53e5d9..3c86f023634bc8 100644 --- a/packages/components/src/color-palette/style.scss +++ b/packages/components/src/color-palette/style.scss @@ -61,12 +61,14 @@ $color-palette-circle-spacing: 14px; &::after { content: ""; position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; + top: -1px; + left: -1px; + bottom: -1px; + right: -1px; border-radius: $radius-round; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); + // Show a thin circular outline in Windows high contrast mode, otherwise the button is invisible. + border: 1px solid transparent; } &:focus { diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 71c44cbcfd3709..1b79df56cd1317 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -67,8 +67,7 @@ top: 20px; &:focus { - border-color: $blue-medium-focus; - box-shadow: 0 0 0 1px $blue-medium-focus; + @include input-style__focus(); } } diff --git a/packages/components/src/toolbar-button/style.scss b/packages/components/src/toolbar-button/style.scss index b35d826ebdfc7b..cd434448784a20 100644 --- a/packages/components/src/toolbar-button/style.scss +++ b/packages/components/src/toolbar-button/style.scss @@ -69,6 +69,18 @@ // Focus style &:not(:disabled):focus > svg { @include formatting-button-style__focus; + // Remove outline from SVG to apply on focused element - see below. + outline: 0; + } + + // Microsoft Edge in high contrast mode only displays outlines on focused elements, not their children. + &:not(:disabled).is-active { + outline: 1px dotted transparent; + outline-offset: -2px; + } + // Microsoft Edge in high contrast mode only displays outlines on focused elements, not their children. + &:not(:disabled):focus { + outline: 2px solid transparent; } } From 4ac7dc9d2d93a1b3ab81b44a6fbd89952a44915e Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 30 Jul 2019 09:47:30 +0100 Subject: [PATCH 552/664] Fix: Close "Block Styles" section on transformBlockTo e2e test helper. (#16739) --- packages/e2e-test-utils/src/transform-block-to.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 64132aac7f45be..9bb11b7bafc81a 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -7,6 +7,14 @@ export async function transformBlockTo( name ) { await page.mouse.move( 200, 300, { steps: 10 } ); await page.mouse.move( 250, 350, { steps: 10 } ); await page.click( '.block-editor-block-switcher__toggle' ); + // Close the "Block Styles" section if it is open. + // Having the section open may make the transform buttons hidden on the testing resolution. + const closeBlockStylesButton = await page.$x( + '//div[contains(@class,"block-editor-block-switcher__popover")]//button[contains(text(),"Block Styles")][@aria-expanded="true"]' + ); + if ( closeBlockStylesButton.length > 0 ) { + await closeBlockStylesButton[ 0 ].click(); + } const insertButton = ( await page.$x( `//button//span[contains(text(), '${ name }')]` ) )[ 0 ]; From 57b2c2c8faa0d3d7a272430212908ae408b54b21 Mon Sep 17 00:00:00 2001 From: Matt Chowning <matt.chowning@automattic.com> Date: Wed, 24 Jul 2019 15:54:33 -0400 Subject: [PATCH 553/664] Mobile: stop always dismissing keyboard when losing focus on empty caption --- .../block-library/src/image/edit.native.js | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 299b595ed896b3..3928691b777aa0 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -270,6 +270,12 @@ class ImageEdit extends React.Component { ); } + // We still want to render the caption so that the soft keyboard is not forced to close on Android + const shouldCaptionDisplay = () => { + const isCaptionEmpty = RichText.isEmpty( caption ) > 0; + return ! isCaptionEmpty || isSelected; + }; + const imageContainerHeight = Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO; const getImageComponent = ( openMediaOptions, getMediaOptions ) => ( <TouchableWithoutFeedback @@ -338,40 +344,38 @@ class ImageEdit extends React.Component { ); } } /> - { ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && ( - <View style={ { padding: 12, flex: 1 } } - accessible={ true } - accessibilityLabel={ - isEmpty( caption ) ? - /* translators: accessibility text. Empty image caption. */ - __( 'Image caption. Empty' ) : - sprintf( - /* translators: accessibility text. %s: image caption. */ - __( 'Image caption. %s' ), - caption - ) - } - accessibilityRole={ 'button' } - > - <RichText - setRef={ ( ref ) => { - this._caption = ref; - } } - rootTagsToEliminate={ [ 'p' ] } - placeholder={ __( 'Write caption…' ) } - value={ caption } - onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } - unstableOnFocus={ this.onFocusCaption } - onBlur={ this.props.onBlur } // always assign onBlur as props - isSelected={ this.state.isCaptionSelected } - __unstableMobileNoFocusOnMount - fontSize={ 14 } - underlineColorAndroid="transparent" - textAlign={ 'center' } - tagName={ '' } - /> - </View> - ) } + <View style={ { padding: 12, flex: 1, display: shouldCaptionDisplay() ? 'flex' : 'none' } } + accessible={ true } + accessibilityLabel={ + isEmpty( caption ) ? + /* translators: accessibility text. Empty image caption. */ + __( 'Image caption. Empty' ) : + sprintf( + /* translators: accessibility text. %s: image caption. */ + __( 'Image caption. %s' ), + caption + ) + } + accessibilityRole={ 'button' } + > + <RichText + setRef={ ( ref ) => { + this._caption = ref; + } } + rootTagsToEliminate={ [ 'p' ] } + placeholder={ __( 'Write caption…' ) } + value={ caption } + onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + unstableOnFocus={ this.onFocusCaption } + onBlur={ this.props.onBlur } // always assign onBlur as props + isSelected={ this.state.isCaptionSelected } + __unstableMobileNoFocusOnMount + fontSize={ 14 } + underlineColorAndroid="transparent" + textAlign={ 'center' } + tagName={ '' } + /> + </View> </View> </TouchableWithoutFeedback> ); From a78fddd06e016ef43eb420b2c82b2cdebbdb0c3c Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Tue, 30 Jul 2019 11:42:36 -0400 Subject: [PATCH 554/664] Additional borders and padding for nested blocks (#14961) * Add extra padding and borders to child blocks (When their parents are selected.) * Small tweaks margin/padding of selected Media+Text/Group blocks. * Add has-child-selected class to parent blocks. Props @gziolo * Adjust border/padding to also apply when child blocks are selected. * Try an animation when nested blocks are selected. * Hide the "Column" block. * Try using a scale animation for child blocks. * Reduce scale animation to be really tiny. * Re-add has-child-selected class. * Selecting parents: Try clickthrough. Clickthrough has you select the parent before you can select the child. This is already in place on the mobile breakpoints, this just expands it to desktop as well. It is a work in progress, right now it is not working as intended: once you have "unlocked" the deepest level, it becomes immediately locked and you have to click through the layers again to unlock it again. The deepest layer should always be unlocked until you deselect all blocks again. * Add some visual debugging for nested overlays * Add hasChildSelected prop * Add borders/padding to direct children + parents only. * Revert "Merge branch 'try/clickthrough' into try/additional-borders-padding-for-child-blocks" This reverts commit 9a9297b0b735d05e6b66d1decccba3e465b0a1d9, reversing changes made to 783708f96847ebec42101c99e372f1651ae2782c. * Revert "Add borders/padding to direct children + parents only." This reverts commit 783708f96847ebec42101c99e372f1651ae2782c. * Revert "Add hasChildSelected prop" This reverts commit a3e9dbd8352c3bc76e9c9a53897fafa27073e581. * Remove animation, adjust specificity. ... so that only direct children get borders/padding, whereas all parents do too. * Columns block adjustments. Ensures better compatibility with the borders/padding. * Clean up media-text block CSS. * Correct block breadcrumb position for the column block. * Move all block movers up above contextual toolbars. * Clean up block list appender margins. * Clean up block list appender margins for the columns block. * Reduce specificity for the group block appender overrides. * Resolve merge inconsitencies after rebasing this branch * Improve compaibiilty with custom inner containers. Like those used for the Group + Cover blocks. * Move extra padding to just the columns and group blocks. * Try fixing e2e test * Clean up columns block placeholder padding. * Modify writing-flow test to pass. Shortens the text inside the column block so that it doesn't wrap into 2 lines. * Followup to 82dfc34 Modifies writing-flow test to include shorter copy in the columns block. * Update snapshots for writing-flow test. * Add inline comments about the test adjustment. * Remove previous attempt to fix failing tests. --- assets/stylesheets/_z-index.scss | 2 +- .../src/components/block-list/block.js | 1 + .../src/components/block-list/style.scss | 27 +++++++++++--- .../block-library/src/columns/editor.scss | 37 +++++++++++++++---- packages/block-library/src/group/editor.scss | 28 ++++++++++++++ .../__snapshots__/writing-flow.test.js.snap | 4 +- packages/e2e-tests/specs/writing-flow.test.js | 8 ++-- 7 files changed, 87 insertions(+), 20 deletions(-) diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 12f9b16de20037..949a0101b1f527 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -8,7 +8,7 @@ $z-layers: ( ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 10, ".block-editor-block-list__layout .reusable-block-indicator": 1, - ".block-editor-block-list__breadcrumb": 2, + ".block-editor-block-list__breadcrumb": 22, ".components-form-toggle__input": 1, ".components-panel__header.edit-post-sidebar__panel-tabs": -1, ".edit-post-sidebar .components-panel": -2, diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 890bfa4ff7aea5..9d91c9e802e399 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -406,6 +406,7 @@ function BlockListBlock( { 'is-typing': isTypingWithinBlock, 'is-focused': isFocusMode && ( isSelected || isParentOfSelectedBlock ), 'is-focus-mode': isFocusMode, + 'has-child-selected': isParentOfSelectedBlock, }, className ); diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 909d4bc7a13802..44570962df65d3 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -168,8 +168,28 @@ opacity: 1; } } -} + // Add extra border to parent blocks when their children are selected. + &.has-child-selected { + + > .block-editor-block-list__block-edit::before { + border: $border-width dashed $dark-opacity-light-800; + } + + > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, + > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, + > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before { + border: $border-width dashed $dark-opacity-light-800; + } + } + + // Add extra border to child blocks when they are selected. + &.is-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, + &.is-selected > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, + &.is-selected > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before { + border: $border-width dashed $dark-opacity-light-800; + } +} /** * Cross-block selection @@ -1055,11 +1075,6 @@ } } - // Position this above the toolbar of parent blocks. - .editor-inner-blocks & { - z-index: z-index(".editor-inner-blocks .block-editor-block-list__breadcrumb"); - } - // Remove negative left breadcrumb position for left aligned blocks. [data-align="left"] & { left: 0; diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index 6e9e2e60659114..1dc626a9e84e06 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -132,9 +132,18 @@ * vertical to ensure there is equal visual spacing around the inserter. Note there * is no formal API for a "passthrough" Block so this is an edge case overide */ -[data-type="core/columns"] .block-list-appender { - margin-top: $block-padding*2; - margin-bottom: $block-padding*2; +[data-type="core/columns"] { + + .block-list-appender { + margin-top: $block-padding*2; + margin-bottom: $block-padding*2; + } + + // When the individual column block is selected, the nested padding overrules + // some of this margin. We need to adjust the appender spacing again as a result. + [data-type="core/column"].is-selected .block-list-appender { + margin: $block-padding 0; + } } /** @@ -158,13 +167,11 @@ div.block-core-columns.is-vertically-aligned-bottom { justify-content: flex-end; } - /** - * Fixes single Column breadcrumb to RHS of Block boundary + * Fixes single Column breadcrumb position. */ [data-type="core/column"] > .editor-block-list__block-edit > .editor-block-list__breadcrumb { - right: 0; - left: auto; + left: -$block-left-border-width; } /** @@ -174,3 +181,19 @@ div.block-core-columns.is-vertically-aligned-bottom { left: 0; right: 0; } + +/** + * Add extra padding when the parent block is selected, for easier interaction. + */ +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/columns"].is-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks, +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/columns"].has-child-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks, +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/column"].is-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks, +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/column"].has-child-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks { + padding: $block-padding; + + // Negate this padding for the placeholder. + > .components-placeholder { + margin: -$block-padding; + width: calc(100% + #{$block-padding * 2}); + } +} diff --git a/packages/block-library/src/group/editor.scss b/packages/block-library/src/group/editor.scss index 68bfde8888604d..166d3992c6daf9 100644 --- a/packages/block-library/src/group/editor.scss +++ b/packages/block-library/src/group/editor.scss @@ -85,3 +85,31 @@ width: calc(100% + 60px); } } + +// Add padding when the block is selected, for easier interaction. +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/group"].has-child-selected > .block-editor-block-list__block-edit > [data-block] > .wp-block-group, +.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/group"].is-selected > .block-editor-block-list__block-edit > [data-block] > .wp-block-group { + + > .wp-block-group__inner-container > .block-editor-inner-blocks { + padding: $block-padding; + } + + &:not(.has-background) > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout { + margin-top: -$block-padding * 2; + margin-bottom: -$block-padding * 2; + } +} + +// Place block list appender in the same place content will appear. +[data-type="core/group"].is-selected { + + .block-list-appender { + margin-left: 0; + margin-right: 0; + } + + .has-background .block-list-appender { + margin-top: $block-padding + $grid-size-small; + margin-bottom: $block-padding + $grid-size-small; + } +} diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index f5e1b7e1afd791..8ebcdd6670e00c 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -8,13 +8,13 @@ exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = ` <!-- wp:columns --> <div class=\\"wp-block-columns\\"><!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> -<p>First col</p> +<p>1st col</p> <!-- /wp:paragraph --></div> <!-- /wp:column --> <!-- wp:column --> <div class=\\"wp-block-column\\"><!-- wp:paragraph --> -<p>Second col</p> +<p>2nd col</p> <!-- /wp:paragraph --></div> <!-- /wp:column --></div> <!-- /wp:columns --> diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index a9923f1d7b6e8f..6528b372584dd3 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -34,7 +34,7 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. - await page.keyboard.type( 'First col' ); + await page.keyboard.type( '1st col' ); // If this text is too long, it may wrap to a new line and cause test failure. That's why we're using "1st" instead of "First" here. // TODO: ArrowDown should traverse into the second column. In slower // CPUs, it can sometimes remain in the first column paragraph. This @@ -45,7 +45,7 @@ describe( 'adding blocks', () => { await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Tab', 3 ); // Tab to paragraph result. await page.keyboard.press( 'Enter' ); // Insert paragraph. - await page.keyboard.type( 'Second col' ); + await page.keyboard.type( '2nd col' ); // If this text is too long, it may wrap to a new line and cause test failure. That's why we're using "2nd" instead of "Second" here. // Arrow down from last of layouts exits nested context to default // appender of root level. @@ -55,14 +55,14 @@ describe( 'adding blocks', () => { // Arrow up into nested context focuses last text input await page.keyboard.press( 'ArrowUp' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); - expect( activeElementText ).toBe( 'Second col' ); + expect( activeElementText ).toBe( '2nd col' ); // Arrow up in inner blocks should navigate through (1) column wrapper, // (2) text fields. await page.keyboard.press( 'ArrowUp' ); await page.keyboard.press( 'ArrowUp' ); activeElementText = await page.evaluate( () => document.activeElement.textContent ); - expect( activeElementText ).toBe( 'First col' ); + expect( activeElementText ).toBe( '1st col' ); // Arrow up from first text field in nested context focuses column and // columns wrappers before escaping out. From 6f56a7a704114980902485879a151bb46d7ba3cb Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Tue, 30 Jul 2019 12:57:57 -0400 Subject: [PATCH 555/664] Clean up Gallery block move/remove UI (#16793) * Use larger buttons when we can. * Avoid adding selected border to the caption section of the image. * Remove hover/focus box shadows because they clash with the blue background. * Revise method of specifying the .is-selected class. --- .../block-library/src/gallery/editor.scss | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 910bed783e67f4..9452f917298f69 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -19,11 +19,11 @@ ul.wp-block-gallery { outline: none; } - .is-selected { + figure.is-selected { outline: 4px solid theme(primary); } - .is-transient img { + figure.is-transient img { opacity: 0.3; } @@ -60,12 +60,30 @@ ul.wp-block-gallery { .components-button { color: $white; + padding: 2px; + width: $icon-button-size-small; + height: $icon-button-size-small; + + // Remove hover/focus box shadows, since they clash with the blue background. + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover, + &:focus:not(:disabled) { + box-shadow: none; + } + + @include break-small() { + // Use smaller buttons to fit when there are many columns. + .columns-7 &, + .columns-8 & { + padding: 0; + width: inherit; + height: inherit; + } + } } .components-button:focus { color: inherit; } - } .block-editor-rich-text figcaption { @@ -80,13 +98,21 @@ ul.wp-block-gallery { .block-library-gallery-item__move-menu, .block-library-gallery-item__inline-menu { - padding: 2px; + padding: $grid-size-small; display: inline-flex; z-index: z-index(".block-library-gallery-item__inline-menu"); .components-button { color: transparent; } + + @include break-small() { + // Use smaller buttons to fit when there are many columns. + .columns-7 &, + .columns-8 & { + padding: $grid-size-small / 2; + } + } } .block-library-gallery-item__inline-menu { From 87d5d5a19e9f57ba6414d51c19657fb6ed6b7d81 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 30 Jul 2019 14:53:35 -0400 Subject: [PATCH 556/664] Block Editor: Merge block wrapper props by reassignment (#16819) --- packages/block-editor/src/components/block-list/block.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 9d91c9e802e399..7d5569502b4569 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -412,10 +412,9 @@ function BlockListBlock( { ); // Determine whether the block has props to apply to the wrapper. - let blockWrapperProps = wrapperProps; if ( blockType.getEditWrapperProps ) { - blockWrapperProps = { - ...blockWrapperProps, + wrapperProps = { + ...wrapperProps, ...blockType.getEditWrapperProps( attributes ), }; } @@ -470,7 +469,7 @@ function BlockListBlock( { aria-label={ blockLabel } childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } tagName={ animated.div } - { ...blockWrapperProps } + { ...wrapperProps } style={ wrapperProps && wrapperProps.style ? { From 208ff09c28339e3e4b49d706c09a0ec45ad4f0d1 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Tue, 30 Jul 2019 16:07:10 -0400 Subject: [PATCH 557/664] Components: Avoid displaying tooltip on focus from mousedown (#16800) * Components: Avoid displaying tooltip on focus from mousedown * Testing: Update Tooltip snapshots * Components: Tooltip: Add JSDoc type for assigned cancelIsMouseDown --- .../test/__snapshots__/index.js.snap | 6 +++ packages/components/src/tooltip/index.js | 54 +++++++++++++++++++ packages/components/src/tooltip/test/index.js | 38 +++++++++++++ .../test/__snapshots__/index.js.snap | 2 + 4 files changed, 100 insertions(+) diff --git a/packages/components/src/color-picker/test/__snapshots__/index.js.snap b/packages/components/src/color-picker/test/__snapshots__/index.js.snap index 1cb694aa24f833..5d61a24e5ac9e6 100644 --- a/packages/components/src/color-picker/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-picker/test/__snapshots__/index.js.snap @@ -148,6 +148,7 @@ exports[`ColorPicker should commit changes to all views on blur 1`] = ` onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" @@ -321,6 +322,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = DOWN 1`] = onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" @@ -494,6 +496,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = ENTER 1`] = onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" @@ -667,6 +670,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = UP 1`] = ` onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" @@ -840,6 +844,7 @@ exports[`ColorPicker should only update input view for draft changes 1`] = ` onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" @@ -1013,6 +1018,7 @@ exports[`ColorPicker should render color picker 1`] = ` onBlur={[Function]} onClick={[Function]} onFocus={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index f20bb647c3cb81..9c7d0acb800ad6 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -35,6 +35,22 @@ class Tooltip extends Component { TOOLTIP_DELAY ); + /** + * Prebound `isInMouseDown` handler, created as a constant reference to + * assure ability to remove in component unmount. + * + * @type {Function} + */ + this.cancelIsMouseDown = this.createSetIsMouseDown( false ); + + /** + * Whether a the mouse is currently pressed, used in determining whether + * to handle a focus event as displaying the tooltip immediately. + * + * @type {boolean} + */ + this.isInMouseDown = false; + this.state = { isOver: false, }; @@ -42,6 +58,8 @@ class Tooltip extends Component { componentWillUnmount() { this.delayedSetIsOver.cancel(); + + document.removeEventListener( 'mouseup', this.cancelIsMouseDown ); } emitToChild( eventName, event ) { @@ -71,6 +89,13 @@ class Tooltip extends Component { return; } + // A focus event will occur as a result of a mouse click, but it + // should be disambiguated between interacting with the button and + // using an explicit focus shift as a cue to display the tooltip. + if ( 'focus' === event.type && this.isInMouseDown ) { + return; + } + // Needed in case unsetting is over while delayed set pending, i.e. // quickly blur/mouseleave before delayedSetIsOver is called this.delayedSetIsOver.cancel(); @@ -88,6 +113,34 @@ class Tooltip extends Component { }; } + /** + * Creates an event callback to handle assignment of the `isInMouseDown` + * instance property in response to a `mousedown` or `mouseup` event. + * + * @param {booelan} isMouseDown Whether handler is to be created for the + * `mousedown` event, as opposed to `mouseup`. + * + * @return {Function} Event callback handler. + */ + createSetIsMouseDown( isMouseDown ) { + return ( event ) => { + // Preserve original child callback behavior + this.emitToChild( isMouseDown ? 'onMouseDown' : 'onMouseUp', event ); + + // On mouse down, the next `mouseup` should revert the value of the + // instance property and remove its own event handler. The bind is + // made on the document since the `mouseup` might not occur within + // the bounds of the element. + document[ + isMouseDown ? + 'addEventListener' : + 'removeEventListener' + ]( 'mouseup', this.cancelIsMouseDown ); + + this.isInMouseDown = isMouseDown; + }; + } + render() { const { children, position, text, shortcut } = this.props; if ( Children.count( children ) !== 1 ) { @@ -107,6 +160,7 @@ class Tooltip extends Component { onClick: this.createToggleIsOver( 'onClick' ), onFocus: this.createToggleIsOver( 'onFocus' ), onBlur: this.createToggleIsOver( 'onBlur' ), + onMouseDown: this.createSetIsMouseDown( true ), children: concatChildren( child.props.children, isOver && ( diff --git a/packages/components/src/tooltip/test/index.js b/packages/components/src/tooltip/test/index.js index bc98cdf1f024b0..7265a345d1a4ea 100644 --- a/packages/components/src/tooltip/test/index.js +++ b/packages/components/src/tooltip/test/index.js @@ -77,6 +77,44 @@ describe( 'Tooltip', () => { expect( popover ).toHaveLength( 1 ); } ); + it( 'should show not popover on focus as result of mousedown', () => { + const originalOnMouseDown = jest.fn(); + const originalOnMouseUp = jest.fn(); + const wrapper = mount( + <Tooltip text="Help text"> + <button + onMouseDown={ originalOnMouseDown } + onMouseUp={ originalOnMouseUp } + > + Hover Me! + </button> + </Tooltip> + ); + + const button = wrapper.find( 'button' ); + + let event; + + event = { type: 'mousedown' }; + button.simulate( event.type, event ); + expect( originalOnMouseDown ).toHaveBeenCalledWith( expect.objectContaining( { + type: event.type, + } ) ); + + event = { type: 'focus', currentTarget: {} }; + button.simulate( event.type, event ); + + const popover = wrapper.find( 'Popover' ); + expect( wrapper.state( 'isOver' ) ).toBe( false ); + expect( popover ).toHaveLength( 0 ); + + event = new window.MouseEvent( 'mouseup' ); + document.dispatchEvent( event ); + expect( originalOnMouseUp ).toHaveBeenCalledWith( expect.objectContaining( { + type: event.type, + } ) ); + } ); + it( 'should show popover on delayed mouseenter', () => { const originalMouseEnter = jest.fn(); const wrapper = TestUtils.renderIntoDocument( diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index cc17255097c02d..889803c75d9107 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -44,6 +44,7 @@ exports[`MoreMenu should match snapshot 1`] = ` onClick={[Function]} onFocus={[Function]} onKeyDown={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} > @@ -56,6 +57,7 @@ exports[`MoreMenu should match snapshot 1`] = ` onClick={[Function]} onFocus={[Function]} onKeyDown={[Function]} + onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={[Function]} type="button" From 823ba362fa3ca0016ef5f690e8f267420b994697 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Tue, 30 Jul 2019 18:10:03 -0400 Subject: [PATCH 558/664] Core Data: Add util for minimally modifying items. (#16823) --- .../src/utils/conservative-map-item.js | 37 +++++++++++++++++++ packages/core-data/src/utils/index.js | 1 + .../src/utils/test/conservative-map-item.js | 33 +++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 packages/core-data/src/utils/conservative-map-item.js create mode 100644 packages/core-data/src/utils/test/conservative-map-item.js diff --git a/packages/core-data/src/utils/conservative-map-item.js b/packages/core-data/src/utils/conservative-map-item.js new file mode 100644 index 00000000000000..370c3cf0861fd6 --- /dev/null +++ b/packages/core-data/src/utils/conservative-map-item.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { isEqual } from 'lodash'; + +/** + * Given the current and next item entity, returns the minimally "modified" + * result of the next item, preferring value references from the original item + * if equal. If all values match, the original item is returned. + * + * @param {Object} item Original item. + * @param {Object} nextItem Next item. + * + * @return {Object} Minimally modified merged item. + */ +export default function conservativeMapItem( item, nextItem ) { + // Return next item in its entirety if there is no original item. + if ( ! item ) { + return nextItem; + } + + let hasChanges = false; + const result = {}; + for ( const key in nextItem ) { + if ( isEqual( item[ key ], nextItem[ key ] ) ) { + result[ key ] = item[ key ]; + } else { + hasChanges = true; + result[ key ] = nextItem[ key ]; + } + } + + if ( ! hasChanges ) { + return item; + } + return result; +} diff --git a/packages/core-data/src/utils/index.js b/packages/core-data/src/utils/index.js index a52cb83430dcbf..7adb57e48d5d7f 100644 --- a/packages/core-data/src/utils/index.js +++ b/packages/core-data/src/utils/index.js @@ -1,3 +1,4 @@ +export { default as conservativeMapItem } from './conservative-map-item'; export { default as ifMatchingAction } from './if-matching-action'; export { default as onSubKey } from './on-sub-key'; export { default as replaceAction } from './replace-action'; diff --git a/packages/core-data/src/utils/test/conservative-map-item.js b/packages/core-data/src/utils/test/conservative-map-item.js new file mode 100644 index 00000000000000..5f42cc73fdde78 --- /dev/null +++ b/packages/core-data/src/utils/test/conservative-map-item.js @@ -0,0 +1,33 @@ +/** + * Internal dependencies + */ +import conservativeMapItem from '../conservative-map-item'; + +describe( 'conservativeMapItem', () => { + it( 'Returns the next item if there is no current item to compare with.', () => { + const item = undefined; + const nextItem = {}; + const result = conservativeMapItem( item, nextItem ); + + expect( result ).toBe( nextItem ); + } ); + + it( 'Returns the original item if all property values are the same, deeply.', () => { + const item = { a: [ {} ] }; + const nextItem = { a: [ {} ] }; + const result = conservativeMapItem( item, nextItem ); + + expect( result ).toBe( item ); + } ); + + it( 'Preserves original references of property values when unchanged, deeply.', () => { + const item = { a: [ {} ], b: [ 1 ] }; + const nextItem = { a: [ {} ], b: [ 2 ] }; + const result = conservativeMapItem( item, nextItem ); + + expect( result ).not.toBe( item ); + expect( result.a ).toBe( item.a ); + expect( result.b ).toBe( nextItem.b ); + expect( result ).toEqual( { a: [ {} ], b: [ 2 ] } ); + } ); +} ); From 91607495a2e347497f15b857c10be680fcd10ee1 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Wed, 31 Jul 2019 14:38:29 +0800 Subject: [PATCH 559/664] First-time contributor github action (#16762) * First draft of first-time contributor action * Try using the github search api * Minor tweaks --- .../actions/first-time-contributor/Dockerfile | 18 ++++++++++ .../first-time-contributor/entrypoint.sh | 35 +++++++++++++++++++ .github/main.workflow | 11 ++++++ 3 files changed, 64 insertions(+) create mode 100644 .github/actions/first-time-contributor/Dockerfile create mode 100755 .github/actions/first-time-contributor/entrypoint.sh diff --git a/.github/actions/first-time-contributor/Dockerfile b/.github/actions/first-time-contributor/Dockerfile new file mode 100644 index 00000000000000..01e3c560b82219 --- /dev/null +++ b/.github/actions/first-time-contributor/Dockerfile @@ -0,0 +1,18 @@ +FROM debian:stable-slim + +LABEL "name"="First Time Contributor" +LABEL "maintainer"="The WordPress Contributors" +LABEL "version"="1.0.0" + +LABEL "com.github.actions.name"="First Time Contributor" +LABEL "com.github.actions.description"="Assigns the first time contributor label to pull requests" +LABEL "com.github.actions.icon"="award" +LABEL "com.github.actions.color"="green" + +RUN apt-get update && \ + apt-get install --no-install-recommends -y jq curl ca-certificates && \ + apt-get clean -y + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/.github/actions/first-time-contributor/entrypoint.sh b/.github/actions/first-time-contributor/entrypoint.sh new file mode 100755 index 00000000000000..c775c4f6150981 --- /dev/null +++ b/.github/actions/first-time-contributor/entrypoint.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e + +# 1. Get the author and pr number for the pull request. +author=$(jq -r '.pull_request.user.login' $GITHUB_EVENT_PATH) +pr_number=$(jq -r '.number' $GITHUB_EVENT_PATH) + +if [ "$pr_number" = "null" ] || [ "$author" = "null" ]; then + echo "Could not find PR number or author. $pr_number / $author" + exit 78 +fi + +# 2. Fetch the author's commit count for the repo to determine if they're a first-time contributor. +commit_count=$( + curl \ + --silent \ + -H "Accept: application/vnd.github.cloak-preview" \ + "https://api.github.com/search/commits?q=repo:$GITHUB_REPOSITORY+author:$author" \ + | jq -r '.total_count' +) + +# 3. If the response has a commit count of zero, exit early, the author is not a first time contributor. +if [ "$commit_count" != "0" ]; then + echo "Pull request #$pr_number was not created by a first-time contributor ($author)." + exit 78 +fi + +# 4. Assign the 'First Time Contributor' label. +curl \ + --silent \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"labels":["First-time Contributor"]}' \ + "https://api.github.com/repos/$GITHUB_REPOSITORY/issues/$pr_number/labels" > /dev/null diff --git a/.github/main.workflow b/.github/main.workflow index 5c1481cb96aa9c..50f5e8cb927036 100644 --- a/.github/main.workflow +++ b/.github/main.workflow @@ -23,3 +23,14 @@ action "Assign Fixed Issues" { needs = ["Filter opened"] secrets = ["GITHUB_TOKEN"] } + +workflow "Add the First-time Contributor label to PRs opened by first-time contributors" { + on = "pull_request" + resolves = ["First Time Contributor"] +} + +action "First Time Contributor" { + uses = "./.github/actions/first-time-contributor" + needs = ["Filter opened"] + secrets = ["GITHUB_TOKEN"] +} From c59cda8d962e0c951d1068278b934f470122a871 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Wed, 31 Jul 2019 16:35:54 +0800 Subject: [PATCH 560/664] Add table block column alignment (#16111) * Initial step at adding alignment options in table block edit component Fix block.json attributes Add cell alignment to save function Add tests for table cell alignment Tidy up test comments Update JSDocs for new functions Add unit tests Use classnames for cell alignment Use shorthand property Remove alignment styles in favour of global styles Update copy of alignment buttons Rework cell selection border to use an actual border around a pseudoelement instead of box-shadow * Make cell alignment work across columns instead of individual cells Add jsdocs Inherit the alignment property of the first cell in the column when adding new rows Update e2e tests Use consistent letter casing * Address review feedback Update params of getCellAttribute function Do nothing if insertRows is unable to determine the correct cellCount for the insertion --- .../src/components/alignment-toolbar/index.js | 12 +- packages/block-library/src/table/block.json | 15 + packages/block-library/src/table/edit.js | 169 ++++++--- packages/block-library/src/table/save.js | 24 +- packages/block-library/src/table/state.js | 199 +++++++--- .../block-library/src/table/test/state.js | 352 ++++++++++++++---- .../blocks/__snapshots__/table.test.js.snap | 6 + packages/e2e-tests/specs/blocks/table.test.js | 60 ++- 8 files changed, 656 insertions(+), 181 deletions(-) diff --git a/packages/block-editor/src/components/alignment-toolbar/index.js b/packages/block-editor/src/components/alignment-toolbar/index.js index 981e8d6f9914d3..aa0233ae0f7518 100644 --- a/packages/block-editor/src/components/alignment-toolbar/index.js +++ b/packages/block-editor/src/components/alignment-toolbar/index.js @@ -27,7 +27,15 @@ const DEFAULT_ALIGNMENT_CONTROLS = [ }, ]; -export function AlignmentToolbar( { value, onChange, alignmentControls = DEFAULT_ALIGNMENT_CONTROLS, isCollapsed = true } ) { +export function AlignmentToolbar( props ) { + const { + value, + onChange, + alignmentControls = DEFAULT_ALIGNMENT_CONTROLS, + label = __( 'Change text alignment' ), + isCollapsed = true, + } = props; + function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } @@ -38,7 +46,7 @@ export function AlignmentToolbar( { value, onChange, alignmentControls = DEFAULT <Toolbar isCollapsed={ isCollapsed } icon={ activeAlignment ? activeAlignment.icon : 'editor-alignleft' } - label={ __( 'Change text alignment' ) } + label={ label } controls={ alignmentControls.map( ( control ) => { const { align } = control; const isActive = ( value === align ); diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index a96913bbc12b5b..539c4df88ab7c7 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -34,6 +34,11 @@ "type": "string", "source": "attribute", "attribute": "scope" + }, + "align": { + "type": "string", + "source": "attribute", + "attribute": "data-align" } } } @@ -64,6 +69,11 @@ "type": "string", "source": "attribute", "attribute": "scope" + }, + "align": { + "type": "string", + "source": "attribute", + "attribute": "data-align" } } } @@ -94,6 +104,11 @@ "type": "string", "source": "attribute", "attribute": "scope" + }, + "align": { + "type": "string", + "source": "attribute", + "attribute": "data-align" } } } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 3c5f6c11fe2776..cb7324a3efe77e 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -14,6 +14,7 @@ import { PanelColorSettings, createCustomColorsHOC, BlockIcon, + AlignmentToolbar, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { @@ -31,13 +32,15 @@ import { */ import { createTable, - updateCellContent, + updateSelectedCell, + getCellAttribute, insertRow, deleteRow, insertColumn, deleteColumn, toggleSection, isEmptyTableSection, + isCellSelected, } from './state'; import icon from './icon'; @@ -64,6 +67,24 @@ const BACKGROUND_COLORS = [ }, ]; +const ALIGNMENT_CONTROLS = [ + { + icon: 'editor-alignleft', + title: __( 'Align Column Left' ), + align: 'left', + }, + { + icon: 'editor-aligncenter', + title: __( 'Align Column Center' ), + align: 'center', + }, + { + icon: 'editor-alignright', + title: __( 'Align Column Right' ), + align: 'right', + }, +]; + const withCustomBackgroundColors = createCustomColorsHOC( BACKGROUND_COLORS ); export class TableEdit extends Component { @@ -87,6 +108,8 @@ export class TableEdit extends Component { this.onDeleteColumn = this.onDeleteColumn.bind( this ); this.onToggleHeaderSection = this.onToggleHeaderSection.bind( this ); this.onToggleFooterSection = this.onToggleFooterSection.bind( this ); + this.onChangeColumnAlignment = this.onChangeColumnAlignment.bind( this ); + this.getCellAlignment = this.getCellAlignment.bind( this ); this.state = { initialRowCount: 2, @@ -156,14 +179,73 @@ export class TableEdit extends Component { } const { attributes, setAttributes } = this.props; - const { section, rowIndex, columnIndex } = selectedCell; - setAttributes( updateCellContent( attributes, { - section, - rowIndex, - columnIndex, - content, - } ) ); + setAttributes( updateSelectedCell( + attributes, + selectedCell, + ( cellAttributes ) => ( { ...cellAttributes, content } ), + ) ); + } + + /** + * Align text within the a column. + * + * @param {string} align The new alignment to apply to the column. + */ + onChangeColumnAlignment( align ) { + const { selectedCell } = this.state; + + if ( ! selectedCell ) { + return; + } + + // Convert the cell selection to a column selection so that alignment + // is applied to the entire column. + const columnSelection = { + type: 'column', + columnIndex: selectedCell.columnIndex, + }; + + const { attributes, setAttributes } = this.props; + const newAttributes = updateSelectedCell( + attributes, + columnSelection, + ( cellAttributes ) => ( { ...cellAttributes, align } ), + ); + setAttributes( newAttributes ); + } + + /** + * Get the alignment of the currently selected cell. + * + * @return {string} The new alignment to apply to the column. + */ + getCellAlignment() { + const { selectedCell } = this.state; + + if ( ! selectedCell ) { + return; + } + + const { attributes } = this.props; + + return getCellAttribute( attributes, selectedCell, 'align' ); + } + + /** + * Add or remove a `head` table section. + */ + onToggleHeaderSection() { + const { attributes, setAttributes } = this.props; + setAttributes( toggleSection( attributes, 'head' ) ); + } + + /** + * Add or remove a `foot` table section. + */ + onToggleFooterSection() { + const { attributes, setAttributes } = this.props; + setAttributes( toggleSection( attributes, 'foot' ) ); } /** @@ -179,11 +261,11 @@ export class TableEdit extends Component { } const { attributes, setAttributes } = this.props; - const { section, rowIndex } = selectedCell; + const { sectionName, rowIndex } = selectedCell; this.setState( { selectedCell: null } ); setAttributes( insertRow( attributes, { - section, + sectionName, rowIndex: rowIndex + delta, } ) ); } @@ -202,16 +284,6 @@ export class TableEdit extends Component { this.onInsertRow( 1 ); } - onToggleHeaderSection() { - const { attributes, setAttributes } = this.props; - setAttributes( toggleSection( attributes, 'head' ) ); - } - - onToggleFooterSection() { - const { attributes, setAttributes } = this.props; - setAttributes( toggleSection( attributes, 'foot' ) ); - } - /** * Deletes the currently selected row. */ @@ -223,10 +295,10 @@ export class TableEdit extends Component { } const { attributes, setAttributes } = this.props; - const { section, rowIndex } = selectedCell; + const { sectionName, rowIndex } = selectedCell; this.setState( { selectedCell: null } ); - setAttributes( deleteRow( attributes, { section, rowIndex } ) ); + setAttributes( deleteRow( attributes, { sectionName, rowIndex } ) ); } /** @@ -275,23 +347,28 @@ export class TableEdit extends Component { } const { attributes, setAttributes } = this.props; - const { section, columnIndex } = selectedCell; + const { sectionName, columnIndex } = selectedCell; this.setState( { selectedCell: null } ); - setAttributes( deleteColumn( attributes, { section, columnIndex } ) ); + setAttributes( deleteColumn( attributes, { sectionName, columnIndex } ) ); } /** * Creates an onFocus handler for a specified cell. * - * @param {Object} selectedCell Object with `section`, `rowIndex`, and + * @param {Object} cellLocation Object with `section`, `rowIndex`, and * `columnIndex` properties. * * @return {Function} Function to call on focus. */ - createOnFocus( selectedCell ) { + createOnFocus( cellLocation ) { return () => { - this.setState( { selectedCell } ); + this.setState( { + selectedCell: { + ...cellLocation, + type: 'cell', + }, + } ); }; } @@ -351,32 +428,31 @@ export class TableEdit extends Component { * * @return {Object} React element for the section. */ - renderSection( { type, rows } ) { + renderSection( { name, rows } ) { if ( isEmptyTableSection( rows ) ) { return null; } - const Tag = `t${ type }`; + const Tag = `t${ name }`; const { selectedCell } = this.state; return ( <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag: CellTag, scope }, columnIndex ) => { - const isSelected = selectedCell && ( - type === selectedCell.section && - rowIndex === selectedCell.rowIndex && - columnIndex === selectedCell.columnIndex - ); - - const cell = { - section: type, + { cells.map( ( { content, tag: CellTag, scope, align }, columnIndex ) => { + const cellLocation = { + sectionName: name, rowIndex, columnIndex, }; - const cellClasses = classnames( { 'is-selected': isSelected } ); + const isSelected = isCellSelected( cellLocation, selectedCell ); + + const cellClasses = classnames( { + 'is-selected': isSelected, + [ `has-text-align-${ align }` ]: align, + } ); return ( <CellTag @@ -388,7 +464,7 @@ export class TableEdit extends Component { className="wp-block-table__cell-content" value={ content } onChange={ this.onChange } - unstableOnFocus={ this.createOnFocus( cell ) } + unstableOnFocus={ this.createOnFocus( cellLocation ) } /> </CellTag> ); @@ -467,6 +543,13 @@ export class TableEdit extends Component { controls={ this.getTableControls() } /> </Toolbar> + <AlignmentToolbar + label={ __( 'Change column alignment' ) } + alignmentControls={ ALIGNMENT_CONTROLS } + value={ this.getCellAlignment() } + onChange={ ( nextAlign ) => this.onChangeColumnAlignment( nextAlign ) } + onHover={ this.onHoverAlignment } + /> </BlockControls> <InspectorControls> <PanelBody title={ __( 'Table Settings' ) } className="blocks-table-settings"> @@ -502,9 +585,9 @@ export class TableEdit extends Component { </InspectorControls> <figure className={ className }> <table className={ tableClasses }> - <Section type="head" rows={ head } /> - <Section type="body" rows={ body } /> - <Section type="foot" rows={ foot } /> + <Section name="head" rows={ head } /> + <Section name="body" rows={ body } /> + <Section name="foot" rows={ foot } /> </table> </figure> </> diff --git a/packages/block-library/src/table/save.js b/packages/block-library/src/table/save.js index 086a0221c045b5..0911f20fe85bfd 100644 --- a/packages/block-library/src/table/save.js +++ b/packages/block-library/src/table/save.js @@ -40,14 +40,22 @@ export default function save( { attributes } ) { <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag, scope }, cellIndex ) => - <RichText.Content - tagName={ tag } - value={ content } - key={ cellIndex } - scope={ tag === 'th' ? scope : undefined } - /> - ) } + { cells.map( ( { content, tag, scope, align }, cellIndex ) => { + const cellClasses = classnames( { + [ `has-text-align-${ align }` ]: align, + } ); + + return ( + <RichText.Content + className={ cellClasses ? cellClasses : undefined } + data-align={ align } + tagName={ tag } + value={ content } + key={ cellIndex } + scope={ tag === 'th' ? scope : undefined } + /> + ); + } ) } </tr> ) ) } </Tag> diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 4aab2f3ee3d54b..89fd1c0227ef35 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,9 @@ /** * External dependencies */ -import { times, get, mapValues, every } from 'lodash'; +import { times, get, mapValues, every, pick } from 'lodash'; + +const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; /** * Creates a table state. @@ -26,70 +28,153 @@ export function createTable( { } /** - * Updates cell content in the table state. + * Returns the first row in the table. * - * @param {Object} state Current table state. - * @param {string} options.section Section of the cell to update. - * @param {number} options.rowIndex Row index of the cell to update. - * @param {number} options.columnIndex Column index of the cell to update. - * @param {Array} options.content Content to set for the cell. + * @param {Object} state Current table state. * - * @return {Object} New table state. + * @return {Object} The first table row. */ -export function updateCellContent( state, { - section, - rowIndex, - columnIndex, - content, -} ) { - return { - [ section ]: state[ section ].map( ( row, currentRowIndex ) => { - if ( currentRowIndex !== rowIndex ) { +export function getFirstRow( state ) { + if ( ! isEmptyTableSection( state.head ) ) { + return state.head[ 0 ]; + } + if ( ! isEmptyTableSection( state.body ) ) { + return state.body[ 0 ]; + } + if ( ! isEmptyTableSection( state.foot ) ) { + return state.foot[ 0 ]; + } +} + +/** + * Gets an attribute for a cell. + * + * @param {Object} state Current table state. + * @param {Object} cellLocation The location of the cell + * @param {string} attributeName The name of the attribute to get the value of. + * + * @return {*} The attribute value. + */ +export function getCellAttribute( state, cellLocation, attributeName ) { + const { + sectionName, + rowIndex, + columnIndex, + } = cellLocation; + return get( state, [ sectionName, rowIndex, 'cells', columnIndex, attributeName ] ); +} + +/** + * Returns updated cell attributes after applying the `updateCell` function to the selection. + * + * @param {Object} state The block attributes. + * @param {Object} selection The selection of cells to update. + * @param {Function} updateCell A function to update the selected cell attributes. + * + * @return {Object} New table state including the updated cells. + */ +export function updateSelectedCell( state, selection, updateCell ) { + if ( ! selection ) { + return state; + } + + const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const { + sectionName: selectionSectionName, + rowIndex: selectionRowIndex, + } = selection; + + return mapValues( tableSections, ( section, sectionName ) => { + if ( selectionSectionName && selectionSectionName !== sectionName ) { + return section; + } + + return section.map( ( row, rowIndex ) => { + if ( selectionRowIndex && selectionRowIndex !== rowIndex ) { return row; } return { - cells: row.cells.map( ( cell, currentColumnIndex ) => { - if ( currentColumnIndex !== columnIndex ) { - return cell; + cells: row.cells.map( ( cellAttributes, columnIndex ) => { + const cellLocation = { + sectionName, + columnIndex, + rowIndex, + }; + + if ( ! isCellSelected( cellLocation, selection ) ) { + return cellAttributes; } - return { - ...cell, - content, - }; + return updateCell( cellAttributes ); } ), }; - } ), - }; + } ); + } ); +} + +/** + * Returns whether the cell at `cellLocation` is included in the selection `selection`. + * + * @param {Object} cellLocation An object containing cell location properties. + * @param {Object} selection An object containing selection properties. + * + * @return {boolean} True if the cell is selected, false otherwise. + */ +export function isCellSelected( cellLocation, selection ) { + if ( ! cellLocation || ! selection ) { + return false; + } + + switch ( selection.type ) { + case 'column': + return selection.type === 'column' && cellLocation.columnIndex === selection.columnIndex; + case 'cell': + return selection.type === 'cell' && + cellLocation.sectionName === selection.sectionName && + cellLocation.columnIndex === selection.columnIndex && + cellLocation.rowIndex === selection.rowIndex; + } } /** * Inserts a row in the table state. * - * @param {Object} state Current table state. - * @param {string} options.section Section in which to insert the row. - * @param {number} options.rowIndex Row index at which to insert the row. + * @param {Object} state Current table state. + * @param {string} options.sectionName Section in which to insert the row. + * @param {number} options.rowIndex Row index at which to insert the row. * * @return {Object} New table state. */ export function insertRow( state, { - section, + sectionName, rowIndex, columnCount, } ) { - const cellCount = columnCount || state[ section ][ 0 ].cells.length; + const firstRow = getFirstRow( state ); + const cellCount = columnCount === undefined ? get( firstRow, [ 'cells', 'length' ] ) : columnCount; + + // Bail early if the function cannot determine how many cells to add. + if ( ! cellCount ) { + return state; + } return { - [ section ]: [ - ...state[ section ].slice( 0, rowIndex ), + [ sectionName ]: [ + ...state[ sectionName ].slice( 0, rowIndex ), { - cells: times( cellCount, () => ( { - content: '', - tag: section === 'head' ? 'th' : 'td', - } ) ), + cells: times( cellCount, ( index ) => { + const firstCellInColumn = get( firstRow, [ 'cells', index ], {} ); + const inheritedAttributes = pick( firstCellInColumn, INHERITED_COLUMN_ATTRIBUTES ); + + return { + ...inheritedAttributes, + content: '', + tag: sectionName === 'head' ? 'th' : 'td', + }; + } ), }, - ...state[ section ].slice( rowIndex ), + ...state[ sectionName ].slice( rowIndex ), ], }; } @@ -97,18 +182,18 @@ export function insertRow( state, { /** * Deletes a row from the table state. * - * @param {Object} state Current table state. - * @param {string} options.section Section in which to delete the row. - * @param {number} options.rowIndex Row index to delete. + * @param {Object} state Current table state. + * @param {string} options.sectionName Section in which to delete the row. + * @param {number} options.rowIndex Row index to delete. * * @return {Object} New table state. */ export function deleteRow( state, { - section, + sectionName, rowIndex, } ) { return { - [ section ]: state[ section ].filter( ( row, index ) => index !== rowIndex ), + [ sectionName ]: state[ sectionName ].filter( ( row, index ) => index !== rowIndex ), }; } @@ -116,7 +201,6 @@ export function deleteRow( state, { * Inserts a column in the table state. * * @param {Object} state Current table state. - * @param {string} options.section Section in which to insert the column. * @param {number} options.columnIndex Column index at which to insert the column. * * @return {Object} New table state. @@ -124,7 +208,9 @@ export function deleteRow( state, { export function insertColumn( state, { columnIndex, } ) { - return mapValues( state, ( section, sectionName ) => { + const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + + return mapValues( tableSections, ( section, sectionName ) => { // Bail early if the table section is empty. if ( isEmptyTableSection( section ) ) { return section; @@ -155,7 +241,6 @@ export function insertColumn( state, { * Deletes a column from the table state. * * @param {Object} state Current table state. - * @param {string} options.section Section in which to delete the column. * @param {number} options.columnIndex Column index to delete. * * @return {Object} New table state. @@ -163,7 +248,9 @@ export function insertColumn( state, { export function deleteColumn( state, { columnIndex, } ) { - return mapValues( state, ( section ) => { + const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + + return mapValues( tableSections, ( section ) => { // Bail early if the table section is empty. if ( isEmptyTableSection( section ) ) { return section; @@ -178,33 +265,33 @@ export function deleteColumn( state, { /** * Toggles the existance of a section. * - * @param {Object} state Current table state. - * @param {string} section Name of the section to toggle. + * @param {Object} state Current table state. + * @param {string} sectionName Name of the section to toggle. * * @return {Object} New table state. */ -export function toggleSection( state, section ) { +export function toggleSection( state, sectionName ) { // Section exists, replace it with an empty row to remove it. - if ( ! isEmptyTableSection( state[ section ] ) ) { - return { [ section ]: [] }; + if ( ! isEmptyTableSection( state[ sectionName ] ) ) { + return { [ sectionName ]: [] }; } // Get the length of the first row of the body to use when creating the header. const columnCount = get( state, [ 'body', 0, 'cells', 'length' ], 1 ); // Section doesn't exist, insert an empty row to create the section. - return insertRow( state, { section, rowIndex: 0, columnCount } ); + return insertRow( state, { sectionName, rowIndex: 0, columnCount } ); } /** * Determines whether a table section is empty. * - * @param {Object} sectionRows Table section state. + * @param {Object} section Table section state. * * @return {boolean} True if the table section is empty, false otherwise. */ -export function isEmptyTableSection( sectionRows ) { - return ! sectionRows || ! sectionRows.length || every( sectionRows, isEmptyRow ); +export function isEmptyTableSection( section ) { + return ! section || ! section.length || every( section, isEmptyRow ); } /** diff --git a/packages/block-library/src/table/test/state.js b/packages/block-library/src/table/test/state.js index cce7fa9836bc70..b946d45fcb50b7 100644 --- a/packages/block-library/src/table/test/state.js +++ b/packages/block-library/src/table/test/state.js @@ -8,7 +8,8 @@ import deepFreeze from 'deep-freeze'; */ import { createTable, - updateCellContent, + getFirstRow, + getCellAttribute, insertRow, deleteRow, insertColumn, @@ -16,6 +17,8 @@ import { toggleSection, isEmptyTableSection, isEmptyRow, + isCellSelected, + updateSelectedCell, } from '../state'; const table = deepFreeze( { @@ -47,6 +50,32 @@ const table = deepFreeze( { ], } ); +const tableWithHead = deepFreeze( { + head: [ + { + cells: [ + { + content: 'test', + tag: 'th', + }, + ], + }, + ], +} ); + +const tableWithFoot = deepFreeze( { + foot: [ + { + cells: [ + { + content: 'test', + tag: 'td', + }, + ], + }, + ], +} ); + const tableWithContent = deepFreeze( { body: [ { @@ -76,6 +105,36 @@ const tableWithContent = deepFreeze( { ], } ); +const tableWithAttribute = deepFreeze( { + body: [ + { + cells: [ + { + content: '', + tag: 'td', + }, + { + content: '', + tag: 'td', + }, + ], + }, + { + cells: [ + { + content: '', + tag: 'td', + }, + { + testAttr: 'testVal', + content: '', + tag: 'td', + }, + ], + }, + ], +} ); + describe( 'createTable', () => { it( 'should create a table', () => { const state = createTable( { rowCount: 2, columnCount: 2 } ); @@ -84,23 +143,41 @@ describe( 'createTable', () => { } ); } ); -describe( 'updateCellContent', () => { - it( 'should update cell content', () => { - const state = updateCellContent( table, { - section: 'body', +describe( 'getFirstRow', () => { + it( 'returns the first row in the head when the body is the first table section', () => { + expect( getFirstRow( tableWithHead ) ).toBe( tableWithHead.head[ 0 ] ); + } ); + + it( 'returns the first row in the body when the body is the first table section', () => { + expect( getFirstRow( table ) ).toBe( table.body[ 0 ] ); + } ); + + it( 'returns the first row in the foot when the body is the first table section', () => { + expect( getFirstRow( tableWithFoot ) ).toBe( tableWithFoot.foot[ 0 ] ); + } ); + + it( 'returns `undefined` for an empty table', () => { + expect( getFirstRow( {} ) ).toBeUndefined(); + } ); +} ); + +describe( 'getCellAttribute', () => { + it( 'should get the cell attribute', () => { + const cellLocation = { + sectionName: 'body', rowIndex: 1, columnIndex: 1, - content: 'test', - } ); + }; + const state = getCellAttribute( tableWithAttribute, cellLocation, 'testAttr' ); - expect( state ).toEqual( tableWithContent ); + expect( state ).toBe( 'testVal' ); } ); } ); describe( 'insertRow', () => { it( 'should insert row', () => { const state = insertRow( tableWithContent, { - section: 'body', + sectionName: 'body', rowIndex: 2, } ); @@ -150,7 +227,7 @@ describe( 'insertRow', () => { it( 'allows the number of columns to be specified', () => { const state = insertRow( tableWithContent, { - section: 'body', + sectionName: 'body', rowIndex: 2, columnCount: 4, } ); @@ -207,11 +284,16 @@ describe( 'insertRow', () => { expect( state ).toEqual( expected ); } ); - it( 'adds `th` cells to the head', () => { - const tableWithHead = { - head: [ + it( 'inherits the `align` property from the first cell in the column when adding a new row', () => { + const tableWithAlignment = { + body: [ { cells: [ + { + align: 'right', + content: 'test', + tag: 'th', + }, { content: '', tag: 'th', @@ -221,15 +303,20 @@ describe( 'insertRow', () => { ], }; - const state = insertRow( tableWithHead, { - section: 'head', + const state = insertRow( tableWithAlignment, { + sectionName: 'body', rowIndex: 1, } ); - const expected = { - head: [ + expect( state ).toEqual( { + body: [ { cells: [ + { + align: 'right', + content: 'test', + tag: 'th', + }, { content: '', tag: 'th', @@ -239,21 +326,27 @@ describe( 'insertRow', () => { { cells: [ { + align: 'right', content: '', - tag: 'th', + tag: 'td', + }, + { + content: '', + tag: 'td', }, ], }, ], - }; - - expect( state ).toEqual( expected ); + } ); } ); -} ); -describe( 'insertColumn', () => { - it( 'inserts before existing content by default', () => { - const tableWithHead = { + it( 'adds `th` cells to the head', () => { + const state = insertRow( tableWithHead, { + sectionName: 'head', + rowIndex: 1, + } ); + + const expected = { head: [ { cells: [ @@ -263,9 +356,43 @@ describe( 'insertColumn', () => { }, ], }, + { + cells: [ + { + content: '', + tag: 'th', + }, + ], + }, ], }; + expect( state ).toEqual( expected ); + } ); + + it( 'should have no effect if `columnCount` is not provided and the table has no existing rows', () => { + const existingState = { body: {} }; + const newState = insertRow( existingState, { + sectionName: 'body', + rowIndex: 0, + } ); + + expect( newState ).toBe( existingState ); + } ); + + it( 'should have no effect if `columnCount` is `0`', () => { + const state = insertRow( tableWithHead, { + sectionName: 'head', + rowIndex: 1, + columnCount: 0, + } ); + + expect( state ).toBe( tableWithHead ); + } ); +} ); + +describe( 'insertColumn', () => { + it( 'inserts before existing content by default', () => { const state = insertColumn( tableWithHead, { columnIndex: 0, } ); @@ -336,19 +463,6 @@ describe( 'insertColumn', () => { } ); it( 'adds `th` cells to the head', () => { - const tableWithHead = { - head: [ - { - cells: [ - { - content: '', - tag: 'th', - }, - ], - }, - ], - }; - const state = insertColumn( tableWithHead, { columnIndex: 1, } ); @@ -358,7 +472,7 @@ describe( 'insertColumn', () => { { cells: [ { - content: '', + content: 'test', tag: 'th', }, { @@ -374,7 +488,7 @@ describe( 'insertColumn', () => { } ); it( 'avoids adding cells to empty rows', () => { - const tableWithHead = { + const tableWithEmptyRow = { head: [ { cells: [ @@ -390,7 +504,7 @@ describe( 'insertColumn', () => { ], }; - const state = insertColumn( tableWithHead, { + const state = insertColumn( tableWithEmptyRow, { columnIndex: 0, } ); @@ -417,8 +531,8 @@ describe( 'insertColumn', () => { expect( state ).toEqual( expected ); } ); - it( 'adds cells across table sections that already have cells', () => { - const tableWithHead = { + it( 'adds cells across table sections that already have rows', () => { + const tableWithAllSections = { head: [ { cells: [ @@ -451,7 +565,7 @@ describe( 'insertColumn', () => { ], }; - const state = insertColumn( tableWithHead, { + const state = insertColumn( tableWithAllSections, { columnIndex: 1, } ); @@ -504,7 +618,7 @@ describe( 'insertColumn', () => { } ); it( 'adds cells only to rows that have enough cells when rows have an unequal number of cells', () => { - const tableWithHead = { + const tableWithUnequalColumns = { head: [ { cells: [ @@ -545,7 +659,7 @@ describe( 'insertColumn', () => { ], }; - const state = insertColumn( tableWithHead, { + const state = insertColumn( tableWithUnequalColumns, { columnIndex: 3, } ); @@ -601,7 +715,7 @@ describe( 'insertColumn', () => { describe( 'deleteRow', () => { it( 'should delete row', () => { const state = deleteRow( tableWithContent, { - section: 'body', + sectionName: 'body', rowIndex: 0, } ); @@ -629,7 +743,6 @@ describe( 'deleteRow', () => { describe( 'deleteColumn', () => { it( 'should delete column', () => { const state = deleteColumn( tableWithContent, { - section: 'body', columnIndex: 0, } ); @@ -679,7 +792,6 @@ describe( 'deleteColumn', () => { ], }; const state = deleteColumn( tableWithOneColumn, { - section: 'body', columnIndex: 0, } ); @@ -732,7 +844,6 @@ describe( 'deleteColumn', () => { ], }; const state = deleteColumn( tableWithOneColumn, { - section: 'body', columnIndex: 0, } ); @@ -788,7 +899,6 @@ describe( 'deleteColumn', () => { }; const state = deleteColumn( tableWithOneColumn, { - section: 'body', columnIndex: 1, } ); @@ -831,19 +941,6 @@ describe( 'deleteColumn', () => { describe( 'toggleSection', () => { it( 'removes rows from the head section if the table already has them', () => { - const tableWithHead = { - head: [ - { - cells: [ - { - content: '', - tag: 'th', - }, - ], - }, - ], - }; - const state = toggleSection( tableWithHead, 'head' ); const expected = { @@ -854,11 +951,11 @@ describe( 'toggleSection', () => { } ); it( 'adds a row to the head section if the table has none', () => { - const tableWithHead = { + const tableWithEmptyHead = { head: [], }; - const state = toggleSection( tableWithHead, 'head' ); + const state = toggleSection( tableWithEmptyHead, 'head' ); const expected = { head: [ @@ -877,7 +974,7 @@ describe( 'toggleSection', () => { } ); it( 'uses the number of cells in the first row of the body for the added table row', () => { - const tableWithHead = { + const tableWithEmptyHead = { head: [], body: [ { @@ -899,7 +996,7 @@ describe( 'toggleSection', () => { ], }; - const state = toggleSection( tableWithHead, 'head' ); + const state = toggleSection( tableWithEmptyHead, 'head' ); const expected = { head: [ @@ -996,3 +1093,122 @@ describe( 'isEmptyRow', () => { expect( isEmptyRow( row ) ).toBe( false ); } ); } ); + +describe( 'isCellSelected', () => { + it( 'returns false when no cellLocation is provided', () => { + const tableSelection = { type: 'table' }; + + expect( isCellSelected( undefined, tableSelection ) ).toBe( false ); + } ); + + it( 'returns false when no selection is provided', () => { + const cellLocation = { sectionName: 'head', columnIndex: 0, rowIndex: 0 }; + + expect( isCellSelected( cellLocation ) ).toBe( false ); + } ); + + it( `considers only cells with the same columnIndex to be selected when the selection.type is 'column'`, () => { + // Valid locations and selections. + const headCellLocationA = { sectionName: 'head', columnIndex: 0, rowIndex: 0 }; + const headCellLocationB = { sectionName: 'head', columnIndex: 0, rowIndex: 1 }; + const bodyCellLocationA = { sectionName: 'body', columnIndex: 0, rowIndex: 0 }; + const bodyCellLocationB = { sectionName: 'body', columnIndex: 0, rowIndex: 1 }; + const footCellLocationA = { sectionName: 'foot', columnIndex: 0, rowIndex: 0 }; + const footCellLocationB = { sectionName: 'foot', columnIndex: 0, rowIndex: 1 }; + const columnSelection = { type: 'column', columnIndex: 0 }; + + // Invalid locations and selections. + const otherColumnCellLocationA = { sectionName: 'head', columnIndex: 1, rowIndex: 0 }; + const otherColumnCellLocationB = { sectionName: 'body', columnIndex: 2, rowIndex: 0 }; + const otherColumnCellLocationC = { sectionName: 'foot', columnIndex: 3, rowIndex: 0 }; + + expect( isCellSelected( headCellLocationA, columnSelection ) ).toBe( true ); + expect( isCellSelected( headCellLocationB, columnSelection ) ).toBe( true ); + expect( isCellSelected( bodyCellLocationA, columnSelection ) ).toBe( true ); + expect( isCellSelected( bodyCellLocationB, columnSelection ) ).toBe( true ); + expect( isCellSelected( footCellLocationA, columnSelection ) ).toBe( true ); + expect( isCellSelected( footCellLocationB, columnSelection ) ).toBe( true ); + expect( isCellSelected( otherColumnCellLocationA, columnSelection ) ).toBe( false ); + expect( isCellSelected( otherColumnCellLocationB, columnSelection ) ).toBe( false ); + expect( isCellSelected( otherColumnCellLocationC, columnSelection ) ).toBe( false ); + } ); + + it( `considers only cells with the same section, columnIndex and rowIndex to be selected when the selection.type is 'cell'`, () => { + // Valid locations and selections. + const cellLocation = { sectionName: 'head', columnIndex: 0, rowIndex: 0 }; + const cellSelection = { type: 'cell', sectionName: 'head', rowIndex: 0, columnIndex: 0 }; + + // Invalid locations and selections. + const otherColumnCellLocation = { sectionName: 'head', columnIndex: 1, rowIndex: 0 }; + const otherRowCellLocation = { sectionName: 'head', columnIndex: 0, rowIndex: 1 }; + const bodyCellLocation = { sectionName: 'body', columnIndex: 0, rowIndex: 0 }; + const footCellLocation = { sectionName: 'foot', columnIndex: 0, rowIndex: 0 }; + + expect( isCellSelected( cellLocation, cellSelection ) ).toBe( true ); + expect( isCellSelected( otherColumnCellLocation, cellSelection ) ).toBe( false ); + expect( isCellSelected( otherRowCellLocation, cellSelection ) ).toBe( false ); + expect( isCellSelected( bodyCellLocation, cellSelection ) ).toBe( false ); + expect( isCellSelected( footCellLocation, cellSelection ) ).toBe( false ); + } ); +} ); + +describe( 'updateSelectedCell', () => { + it( 'returns an unchanged table state if there is no selection', () => { + const updated = updateSelectedCell( table, undefined, ( cell ) => ( { ...cell, content: 'test' } ) ); + expect( table ).toEqual( updated ); + } ); + + it( 'returns an unchanged table state if the selection is outside the bounds of the table', () => { + const cellSelection = { type: 'cell', sectionName: 'body', rowIndex: 100, columnIndex: 100 }; + const updated = updateSelectedCell( table, cellSelection, ( cell ) => ( { ...cell, content: 'test' } ) ); + expect( table ).toEqual( updated ); + } ); + + it( 'updates only the individual cell when the selection type is `cell`', () => { + const cellSelection = { type: 'cell', sectionName: 'body', rowIndex: 0, columnIndex: 0 }; + const updated = updateSelectedCell( table, cellSelection, ( cell ) => ( { ...cell, content: 'test' } ) ); + + expect( updated ).toEqual( { + body: [ + { + cells: [ + { + ...table.body[ 0 ].cells[ 0 ], + content: 'test', + }, + table.body[ 0 ].cells[ 1 ], + ], + }, + table.body[ 1 ], + ], + } ); + } ); + + it( 'updates every cell in the column when the selection type is `column`', () => { + const cellSelection = { type: 'column', columnIndex: 1 }; + const updated = updateSelectedCell( table, cellSelection, ( cell ) => ( { ...cell, content: 'test' } ) ); + + expect( updated ).toEqual( { + body: [ + { + cells: [ + table.body[ 0 ].cells[ 0 ], + { + ...table.body[ 0 ].cells[ 1 ], + content: 'test', + }, + ], + }, + { + cells: [ + table.body[ 1 ].cells[ 0 ], + { + ...table.body[ 1 ].cells[ 1 ], + content: 'test', + }, + ], + }, + ], + } ); + } ); +} ); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap index 1065dc2615e2e5..37189e26d2f43f 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -12,6 +12,12 @@ exports[`Table allows adding and deleting columns across the table header, body <!-- /wp:table -->" `; +exports[`Table allows columns to be aligned 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>None</td><td class=\\"has-text-align-left\\" data-align=\\"left\\">To the left</td><td class=\\"has-text-align-center\\" data-align=\\"center\\">Centered</td><td class=\\"has-text-align-right\\" data-align=\\"right\\">Right aligned</td></tr><tr><td></td><td class=\\"has-text-align-left\\" data-align=\\"left\\"></td><td class=\\"has-text-align-center\\" data-align=\\"center\\"></td><td class=\\"has-text-align-right\\" data-align=\\"right\\"></td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; + exports[`Table allows header and footer rows to be switched on and off 1`] = ` "<!-- wp:table --> <figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table></figure> diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js index 8da1506c1c51ec..19e9a0ca51bef3 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { capitalize } from 'lodash'; + /** * WordPress dependencies */ @@ -10,6 +15,17 @@ import { const createButtonSelector = "//div[@data-type='core/table']//button[text()='Create Table']"; +/** + * Utility function for changing the selected cell alignment. + * + * @param {string} align The alignment (one of 'left', 'center', or 'right'). + */ +async function changeCellAlignment( align ) { + await clickBlockToolbarButton( 'Change column alignment' ); + const alignButton = await page.$x( `//button[text()='Align Column ${ capitalize( align ) }']` ); + await alignButton[ 0 ].click(); +} + describe( 'Table', () => { beforeEach( async () => { await createNewPost(); @@ -29,22 +45,22 @@ describe( 'Table', () => { await page.keyboard.press( 'Backspace' ); await page.keyboard.type( '5' ); - // // Check for existence of the row count field. + // Check for existence of the row count field. const rowCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Row Count']" ); expect( rowCountLabel ).toHaveLength( 1 ); - // // Modify the row count. + // Modify the row count. await rowCountLabel[ 0 ].click(); const currentRowCount = await page.evaluate( () => document.activeElement.value ); expect( currentRowCount ).toBe( '2' ); await page.keyboard.press( 'Backspace' ); await page.keyboard.type( '10' ); - // // Create the table. + // Create the table. const createButton = await page.$x( createButtonSelector ); await createButton[ 0 ].click(); - // // Expect the post content to have a correctly sized table. + // Expect the post content to have a correctly sized table. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -154,4 +170,40 @@ describe( 'Table', () => { // Expect the table to have 2 columns across the header, body and footer. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'allows columns to be aligned', async () => { + await insertBlock( 'Table' ); + + const [ columnCountLabel ] = await page.$x( "//div[@data-type='core/table']//label[text()='Column Count']" ); + await columnCountLabel.click(); + await page.keyboard.press( 'Backspace' ); + await page.keyboard.type( '4' ); + + // Create the table. + const [ createButton ] = await page.$x( createButtonSelector ); + await createButton.click(); + + // Click the first cell and add some text. Don't align. + const cells = await page.$$( '.wp-block-table__cell-content' ); + await cells[ 0 ].click(); + await page.keyboard.type( 'None' ); + + // Click to the next cell and add some text. Align left. + await cells[ 1 ].click(); + await page.keyboard.type( 'To the left' ); + await changeCellAlignment( 'left' ); + + // Click the next cell and add some text. Align center. + await cells[ 2 ].click(); + await page.keyboard.type( 'Centered' ); + await changeCellAlignment( 'center' ); + + // Tab to the next cell and add some text. Align right. + await cells[ 3 ].click(); + await page.keyboard.type( 'Right aligned' ); + await changeCellAlignment( 'right' ); + + // Expect the post to have the correct written content inside the table. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); From 3d65eda96c70645a8f4236783b2e72953e79883c Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Wed, 31 Jul 2019 17:05:41 +0800 Subject: [PATCH 561/664] Fix table block cell selection (#16653) * Forward focus to child RichText when a table cell is selected * Add e2e tests * Update e2e test to ensure cell is clicked instead of the rich text --- packages/block-library/src/table/edit.js | 12 +++++- .../blocks/__snapshots__/table.test.js.snap | 6 +++ packages/e2e-tests/specs/blocks/table.test.js | 38 ++++++++++++++++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index cb7324a3efe77e..6c44a305a9375a 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -446,22 +446,30 @@ export class TableEdit extends Component { rowIndex, columnIndex, }; - const isSelected = isCellSelected( cellLocation, selectedCell ); const cellClasses = classnames( { 'is-selected': isSelected, [ `has-text-align-${ align }` ]: align, } ); + const richTextClassName = 'wp-block-table__cell-content'; return ( <CellTag key={ columnIndex } className={ cellClasses } scope={ CellTag === 'th' ? scope : undefined } + onClick={ ( event ) => { + // When a cell is selected, forward focus to the child RichText. This solves an issue where the + // user may click inside a cell, but outside of the RichText, resulting in nothing happening. + const richTextElement = event && event.target && event.target.querySelector( `.${ richTextClassName }` ); + if ( richTextElement ) { + richTextElement.focus(); + } + } } > <RichText - className="wp-block-table__cell-content" + className={ richTextClassName } value={ content } onChange={ this.onChange } unstableOnFocus={ this.createOnFocus( cellLocation ) } diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap index 37189e26d2f43f..c70080eb6ab2fa 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -12,6 +12,12 @@ exports[`Table allows adding and deleting columns across the table header, body <!-- /wp:table -->" `; +exports[`Table allows cells to be selected when the cell area outside of the RichText is clicked 1`] = ` +"<!-- wp:table {\\"hasFixedLayout\\":true} --> +<figure class=\\"wp-block-table\\"><table class=\\"has-fixed-layout\\"><tbody><tr><td>Some long text that will wrap onto multiple lines.</td><td>This content is in the second cell.</td></tr><tr><td></td><td></td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; + exports[`Table allows columns to be aligned 1`] = ` "<!-- wp:table --> <figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>None</td><td class=\\"has-text-align-left\\" data-align=\\"left\\">To the left</td><td class=\\"has-text-align-center\\" data-align=\\"center\\">Centered</td><td class=\\"has-text-align-right\\" data-align=\\"right\\">Right aligned</td></tr><tr><td></td><td class=\\"has-text-align-left\\" data-align=\\"left\\"></td><td class=\\"has-text-align-center\\" data-align=\\"center\\"></td><td class=\\"has-text-align-right\\" data-align=\\"right\\"></td></tr></tbody></table></figure> diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js index 19e9a0ca51bef3..107e1c2f3fe85f 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -203,7 +203,43 @@ describe( 'Table', () => { await page.keyboard.type( 'Right aligned' ); await changeCellAlignment( 'right' ); - // Expect the post to have the correct written content inside the table. + // Expect the post to have the correct alignment classes inside the table. + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + // Testing for regressions of https://github.com/WordPress/gutenberg/issues/14904. + it( 'allows cells to be selected when the cell area outside of the RichText is clicked', async () => { + await insertBlock( 'Table' ); + + // Create the table. + const createButton = await page.$x( createButtonSelector ); + await createButton[ 0 ].click(); + + // Enable fixed width as it exascerbates the amount of empty space around the RichText. + const fixedWidthSwitch = await page.$x( "//label[text()='Fixed width table cells']" ); + await fixedWidthSwitch[ 0 ].click(); + + // Add lots of text to the first cell. + await page.click( '.wp-block-table__cell-content' ); + await page.keyboard.type( + `Some long text that will wrap onto multiple lines.` + ); + + // Get the bounding client rect for the second cell. + const { x: secondCellX, y: secondCellY } = await page.evaluate( () => { + const secondCell = document.querySelectorAll( '.wp-block-table td' )[ 1 ]; + // Page.evaluate can only return a non-serializable value to the + // parent process, so destructure and restructure the result + // into an object. + const { x, y } = secondCell.getBoundingClientRect(); + return { x, y }; + } ); + + // Click in the top left corner of the second cell and type some text. + await page.mouse.click( secondCellX, secondCellY ); + await page.keyboard.type( 'This content is in the second cell.' ); + + // Expect that the snapshot shows the text in the second cell. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); From 9a79ab1f173221617386513756db53a4de67a5ef Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Wed, 31 Jul 2019 11:01:04 +0200 Subject: [PATCH 562/664] Bump plugin version to 6.2.0 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index ad196b1ba7106c..a8e822b1ea4eee 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.2.0-rc.1 + * Version: 6.2.0 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 9c1ce97f40bd55..4cdb2340943098 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.2.0-rc.1", + "version": "6.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5f7f4f6b520098..360c76bff716d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.2.0-rc.1", + "version": "6.2.0", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 08c514f6b3ef2b5395fd587e33f4330fd22aaab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 31 Jul 2019 13:31:39 +0200 Subject: [PATCH 563/664] Cld --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8ee088faf9d2f0..33f40e357faf3d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ # Documentation /docs @youknowriad @chrisvanpatten @ajitbohra -/docs/designers-developers/developers @youknowriad @gziolo @chrisvanpatten @mkaz @ajitbohra +/docs/designers-developers/developers @youknowriad @chrisvanpatten @mkaz @ajitbohra /docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra # Data From 7d2006619aed575a3f9b19f34e84fb4cd70248a0 Mon Sep 17 00:00:00 2001 From: Philip John <phil@philipjohn.me.uk> Date: Tue, 4 Jun 2019 09:36:20 +0100 Subject: [PATCH 564/664] Fixes #11270. Adds support for marking options within a `SelectControl` as 'disabled', which translates to the `disabled` attribute being added to the HTML `option` element. --- packages/block-library/src/tag-cloud/edit.js | 1 + packages/components/src/select-control/README.md | 3 ++- packages/components/src/select-control/index.js | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index ae653ffbfa9e98..3540fffe53498f 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -34,6 +34,7 @@ class TagCloudEdit extends Component { const selectOption = { label: __( '- Select -' ), value: '', + disabled: true, }; const taxonomyOptions = map( taxonomies, ( taxonomy ) => { return { diff --git a/packages/components/src/select-control/README.md b/packages/components/src/select-control/README.md index ac58106dfd0f7b..996756798b64b4 100644 --- a/packages/components/src/select-control/README.md +++ b/packages/components/src/select-control/README.md @@ -107,6 +107,7 @@ Render a user interface to select multiple users from a list. value={ this.state.users } // e.g: value = [ 'a', 'c' ] onChange={ ( users ) => { this.setState( { users } ) } } options={ [ + { value: null, label: 'Select a User', disabled: true }, { value: 'a', label: 'User A' }, { value: 'b', label: 'User B' }, { value: 'c', label: 'User c' }, @@ -115,7 +116,6 @@ Render a user interface to select multiple users from a list. ### Props - - The set of props accepted by the component will be specified below. - Props not included in this set will be applied to the select element. - One important prop to refer is `value`. If `multiple` is `true`, `value` should be an array with the values of the selected options. @@ -150,6 +150,7 @@ If this property is added, multiple values can be selected. The value passed sho An array of objects containing the following properties: - `label`: (string) The label to be shown to the user. - `value`: (Object) The internal value used to choose the selected value. This is also the value passed to onChange when the option is selected. +- `disabled`: (boolean) Whether or not the option should have the disabled attribute. - Type: `Array` - Required: No diff --git a/packages/components/src/select-control/index.js b/packages/components/src/select-control/index.js index abc72be25be091..a10f051face1c0 100644 --- a/packages/components/src/select-control/index.js +++ b/packages/components/src/select-control/index.js @@ -52,6 +52,7 @@ function SelectControl( { <option key={ `${ option.label }-${ option.value }-${ index }` } value={ option.value } + disabled={ option.disabled } > { option.label } </option> From 84bee751f6ea18b9d1d754b9a6466e9bfa11e8ff Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 31 Jul 2019 08:38:03 -0400 Subject: [PATCH 565/664] ESLint Plugin: no-unused-vars-before-return: Exempt destructuring only if to multiple properties (#16799) * ESLint Plugin: Destructure references immediatley prior to use * ESLint Plugin: Exempt object destructuring only if destructuring to more than one property --- packages/eslint-plugin/CHANGELOG.md | 6 ++++- .../__tests__/no-unused-vars-before-return.js | 23 +++++++++++++++++++ .../rules/no-unused-vars-before-return.js | 17 +++++++++++++- .../rules/react-no-unsafe-timeout.js | 3 +-- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index c00d09ef19f02c..9734da4def88d7 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,4 +1,8 @@ -## 2.4.0 (Unreleased) +## Master + +### Breaking Changes + +- The [`@wordpress/no-unused-vars-before-return` rule](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) has been improved to exempt object destructuring only if destructuring to more than one property. ### New Features diff --git a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js index ae00f9cffe8140..c8fa8c4a216ae4 100644 --- a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js +++ b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js @@ -24,6 +24,17 @@ function example( number ) { } const foo = doSomeCostlyOperation(); + return number + foo; +}`, + }, + { + code: ` +function example() { + const { foo, bar } = doSomeCostlyOperation(); + if ( number > 10 ) { + return number + bar + 1; + } + return number + foo; }`, }, @@ -49,6 +60,18 @@ function example( number ) { return number + 1; } + return number + foo; +}`, + errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], + }, + { + code: ` +function example() { + const { foo } = doSomeCostlyOperation(); + if ( number > 10 ) { + return number + 1; + } + return number + foo; }`, errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], diff --git a/packages/eslint-plugin/rules/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/no-unused-vars-before-return.js index b2951c10f3bfa6..fe8f20f4ded761 100644 --- a/packages/eslint-plugin/rules/no-unused-vars-before-return.js +++ b/packages/eslint-plugin/rules/no-unused-vars-before-return.js @@ -17,6 +17,21 @@ module.exports = { const options = context.options[ 0 ] || {}; const { excludePattern } = options; + /** + * Given an Espree VariableDeclarator node, returns true if the node + * can be exempted from consideration as unused, or false otherwise. A + * node can be exempt if it destructures to multiple variables, since + * those other variables may be used prior to the return statement. A + * future enhancement could validate that they are in-fact referenced. + * + * @param {Object} node Node to test. + * + * @return {boolean} Whether declarator is emempt from consideration. + */ + function isExemptObjectDestructureDeclarator( node ) { + return node.id.type === 'ObjectPattern' && node.id.properties.length > 1; + } + return { ReturnStatement( node ) { let functionScope = context.getScope(); @@ -37,7 +52,7 @@ module.exports = { // Target function calls as "expensive". def.node.init.type === 'CallExpression' && // Allow unused if part of an object destructuring. - def.node.id.type !== 'ObjectPattern' && + ! isExemptObjectDestructureDeclarator( def.node ) && // Only target assignments preceding `return`. def.node.end < node.end ); diff --git a/packages/eslint-plugin/rules/react-no-unsafe-timeout.js b/packages/eslint-plugin/rules/react-no-unsafe-timeout.js index 64ff537199caac..f97a4d7384fe36 100644 --- a/packages/eslint-plugin/rules/react-no-unsafe-timeout.js +++ b/packages/eslint-plugin/rules/react-no-unsafe-timeout.js @@ -44,8 +44,6 @@ module.exports = { create( context ) { return { 'CallExpression[callee.name="setTimeout"]'( node ) { - const { references } = context.getScope(); - // If the result of a `setTimeout` call is assigned to a // variable, assume the timer ID is handled by a cancellation. const hasAssignment = ( @@ -74,6 +72,7 @@ module.exports = { // Consider whether `setTimeout` is a reference to the global // by checking references to see if `setTimeout` resolves to a // variable in scope. + const { references } = context.getScope(); const hasResolvedReference = references.some( ( reference ) => ( reference.identifier.name === 'setTimeout' && !! reference.resolved && From becdb52c0d1dbdd72b296689b32f809ff208188e Mon Sep 17 00:00:00 2001 From: andrei draganescu <me@andreidraganescu.info> Date: Wed, 31 Jul 2019 15:40:05 +0300 Subject: [PATCH 566/664] adds simple logic that keeps authored captions in galleries (#15004) * adds simple logic that keeps authored captions in galleries * adds caption updating if changed in gallery, refactores some code * refactor code so as to avoig adding data to the DOM and updated the fixtures * updated new deprecated fixture * removes manual fixture edits * moves attachment captions to state * fixed a problem caused by rebasing --- packages/block-library/src/gallery/edit.js | 46 +++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index f8d1db665208f0..8c4408c3a6746f 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { every, filter, forEach, map } from 'lodash'; +import { filter, forEach, map, find, every } from 'lodash'; /** * WordPress dependencies @@ -63,6 +63,7 @@ class GalleryEdit extends Component { this.state = { selectedImage: null, + attachmentCaptions: null, }; } @@ -129,11 +130,46 @@ class GalleryEdit extends Component { }; } - onSelectImages( images ) { - const { columns } = this.props.attributes; + selectCaption( newImage, images, attachmentCaptions ) { + const currentImage = find( + images, { id: newImage.id } + ); + + const currentImageCaption = currentImage ? currentImage.caption : newImage.caption; + + if ( ! attachmentCaptions ) { + return currentImageCaption; + } + + const attachment = find( + attachmentCaptions, { id: newImage.id } + ); + + // if the attachment caption is updated + if ( attachment && ( attachment.caption !== newImage.caption ) ) { + return newImage.caption; + } + + return currentImageCaption; + } + + onSelectImages( newImages ) { + const { columns, images } = this.props.attributes; + const { attachmentCaptions } = this.state; + this.setState( + { + attachmentCaptions: newImages.map( ( newImage ) => ( { + id: newImage.id, + caption: newImage.caption, + } ) ), + } + ); this.setAttributes( { - images: images.map( ( image ) => pickRelevantMediaFiles( image ) ), - columns: columns ? Math.min( images.length, columns ) : columns, + images: newImages.map( ( newImage ) => ( { + ...pickRelevantMediaFiles( newImage ), + caption: this.selectCaption( newImage, images, attachmentCaptions ), + } ) ), + columns: columns ? Math.min( newImages.length, columns ) : columns, } ); } From f380cc4bc8b56897adea3b0c8ffbaa346e81c9ed Mon Sep 17 00:00:00 2001 From: Thorsten Frommen <info@tfrommen.de> Date: Wed, 31 Jul 2019 15:44:25 +0200 Subject: [PATCH 567/664] Fix FormTokenField rendering (#14819) * Fix rendering/reselection upon prop changes * Add unit test * Use isShallowEqual to compare suggestions --- .../components/src/form-token-field/index.js | 68 ++++++++++++------- .../src/form-token-field/test/index.js | 20 ++++++ .../src/form-token-field/test/lib/fixtures.js | 5 ++ .../test/lib/token-field-wrapper.js | 7 +- 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/packages/components/src/form-token-field/index.js b/packages/components/src/form-token-field/index.js index fa44e49494ff38..bdb8871f0a5aa3 100644 --- a/packages/components/src/form-token-field/index.js +++ b/packages/components/src/form-token-field/index.js @@ -11,6 +11,7 @@ import { __, _n, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { withInstanceId } from '@wordpress/compose'; import { BACKSPACE, ENTER, UP, DOWN, LEFT, RIGHT, SPACE, DELETE, ESCAPE } from '@wordpress/keycodes'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -48,13 +49,20 @@ class FormTokenField extends Component { this.onInputChange = this.onInputChange.bind( this ); this.bindInput = this.bindInput.bind( this ); this.bindTokensAndInput = this.bindTokensAndInput.bind( this ); + this.updateSuggestions = this.updateSuggestions.bind( this ); } - componentDidUpdate() { + componentDidUpdate( prevProps ) { // Make sure to focus the input when the isActive state is true. if ( this.state.isActive && ! this.input.hasFocus() ) { this.input.focus(); } + + const { suggestions, value } = this.props; + const suggestionsDidUpdate = ! isShallowEqual( suggestions, prevProps.suggestions ); + if ( suggestionsDidUpdate || value !== prevProps.value ) { + this.updateSuggestions( suggestionsDidUpdate ); + } } static getDerivedStateFromProps( props, state ) { @@ -193,38 +201,14 @@ class FormTokenField extends Component { const separator = this.props.tokenizeOnSpace ? /[ ,\t]+/ : /[,\t]+/; const items = text.split( separator ); const tokenValue = last( items ) || ''; - const inputHasMinimumChars = tokenValue.trim().length > 1; - const matchingSuggestions = this.getMatchingSuggestions( tokenValue ); - const hasVisibleSuggestions = inputHasMinimumChars && !! matchingSuggestions.length; if ( items.length > 1 ) { this.addNewTokens( items.slice( 0, -1 ) ); } - this.setState( { - incompleteTokenValue: tokenValue, - selectedSuggestionIndex: -1, - selectedSuggestionScroll: false, - isExpanded: false, - } ); + this.setState( { incompleteTokenValue: tokenValue }, this.updateSuggestions ); this.props.onInputChange( tokenValue ); - - if ( inputHasMinimumChars ) { - this.setState( { - isExpanded: hasVisibleSuggestions, - } ); - - if ( !! matchingSuggestions.length ) { - this.props.debouncedSpeak( sprintf( _n( - '%d result found, use up and down arrow keys to navigate.', - '%d results found, use up and down arrow keys to navigate.', - matchingSuggestions.length - ), matchingSuggestions.length ), 'assertive' ); - } else { - this.props.debouncedSpeak( __( 'No results.' ), 'assertive' ); - } - } } handleDeleteKey( deleteToken ) { @@ -467,6 +451,38 @@ class FormTokenField extends Component { return this.props.saveTransform( this.state.incompleteTokenValue ).length > 0; } + updateSuggestions( resetSelectedSuggestion = true ) { + const { incompleteTokenValue } = this.state; + + const inputHasMinimumChars = incompleteTokenValue.trim().length > 1; + const matchingSuggestions = this.getMatchingSuggestions( incompleteTokenValue ); + const hasMatchingSuggestions = matchingSuggestions.length > 0; + + const newState = { + isExpanded: inputHasMinimumChars && hasMatchingSuggestions, + }; + if ( resetSelectedSuggestion ) { + newState.selectedSuggestionIndex = -1; + newState.selectedSuggestionScroll = false; + } + + this.setState( newState ); + + if ( inputHasMinimumChars ) { + const { debouncedSpeak } = this.props; + + const message = hasMatchingSuggestions ? + sprintf( _n( + '%d result found, use up and down arrow keys to navigate.', + '%d results found, use up and down arrow keys to navigate.', + matchingSuggestions.length + ), matchingSuggestions.length ) : + __( 'No results.' ); + + debouncedSpeak( message, 'assertive' ); + } + } + renderTokensAndInput() { const components = map( this.props.value, this.renderToken ); components.splice( this.getIndexOfInput(), 0, this.renderInput() ); diff --git a/packages/components/src/form-token-field/test/index.js b/packages/components/src/form-token-field/test/index.js index 3199cdfa447cd3..3bfcf6e5baf0d7 100644 --- a/packages/components/src/form-token-field/test/index.js +++ b/packages/components/src/form-token-field/test/index.js @@ -237,6 +237,26 @@ describe( 'FormTokenField', function() { expect( getSelectedSuggestion() ).toBe( null ); expect( getTokensHTML() ).toEqual( [ 'foo', 'bar', 'with' ] ); } ); + + it( 'should re-render when suggestions prop has changed', function() { + wrapper.setState( { + tokenSuggestions: [], + isExpanded: true, + } ); + expect( getSuggestionsText() ).toEqual( [] ); + setText( 'so' ); + expect( getSuggestionsText() ).toEqual( [] ); + + wrapper.setState( { + tokenSuggestions: fixtures.specialSuggestions.default, + } ); + expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.so ); + + wrapper.setState( { + tokenSuggestions: [], + } ); + expect( getSuggestionsText() ).toEqual( [] ); + } ); } ); describe( 'adding tokens', function() { diff --git a/packages/components/src/form-token-field/test/lib/fixtures.js b/packages/components/src/form-token-field/test/lib/fixtures.js index b5d27598fd7802..6811275377788d 100644 --- a/packages/components/src/form-token-field/test/lib/fixtures.js +++ b/packages/components/src/form-token-field/test/lib/fixtures.js @@ -6,6 +6,11 @@ export default { htmlUnescaped: [ 'a&nbsp;&nbsp;&nbsp;b', 'i&nbsp;&lt;3&nbsp;tags', '1&amp;2&amp;3&amp;4' ], }, specialSuggestions: { + default: [ + 'the', 'of', 'and', 'to', 'a', 'in', 'for', 'is', 'on', 'that', 'by', 'this', 'with', 'i', 'you', 'it', + 'not', 'or', 'be', 'are', 'from', 'at', 'as', 'your', 'all', 'have', 'new', 'more', 'an', 'was', 'we', + 'associate', 'snake', 'pipes', 'sound', + ], textEscaped: [ '&lt;3', 'Stuff &amp; Things', 'Tags &amp; Stuff', 'Tags &amp; Stuff 2' ], textUnescaped: [ '<3', 'Stuff & Things', 'Tags & Stuff', 'Tags & Stuff 2' ], matchAmpersandUnescaped: [ diff --git a/packages/components/src/form-token-field/test/lib/token-field-wrapper.js b/packages/components/src/form-token-field/test/lib/token-field-wrapper.js index 15729d4404041f..12dc95581b4336 100644 --- a/packages/components/src/form-token-field/test/lib/token-field-wrapper.js +++ b/packages/components/src/form-token-field/test/lib/token-field-wrapper.js @@ -11,13 +11,10 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ +import fixtures from './fixtures'; import TokenField from '../../'; -const suggestions = [ - 'the', 'of', 'and', 'to', 'a', 'in', 'for', 'is', 'on', 'that', 'by', 'this', 'with', 'i', 'you', 'it', - 'not', 'or', 'be', 'are', 'from', 'at', 'as', 'your', 'all', 'have', 'new', 'more', 'an', 'was', 'we', - 'associate', 'snake', 'pipes', 'sound', -]; +const { specialSuggestions: { default: suggestions } } = fixtures; function unescapeAndFormatSpaces( str ) { const nbsp = String.fromCharCode( 160 ); From 08b9a457173a38a9f8ed8672fae5dba4c458a90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 31 Jul 2019 15:56:43 +0200 Subject: [PATCH 568/664] Deprecated: Output an informational message for deprecation when no version provided (#16774) * Deprecated: Output an informational message for deprecation when no version provided When there is no `options.version` param provided `deprecated` method outputs an informational message to the Web Console rather than warns as previously. * Use warn for all deprecations * Update unit tests for deprecated method to make them less confusing around version param --- packages/deprecated/CHANGELOG.md | 6 ++++++ packages/deprecated/src/index.js | 4 ++-- packages/deprecated/src/test/index.js | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/deprecated/CHANGELOG.md b/packages/deprecated/CHANGELOG.md index 0ea8fe0294d33e..958f5059344f2d 100644 --- a/packages/deprecated/CHANGELOG.md +++ b/packages/deprecated/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Bug Fix + +- When there is no `options.version` param provided `deprecated` method warns with more relaxed tone. + ## 2.0.4 (2019-01-03) ## 2.0.0 (2018-09-05) diff --git a/packages/deprecated/src/index.js b/packages/deprecated/src/index.js index 840c694994a170..633f54c9aea658 100644 --- a/packages/deprecated/src/index.js +++ b/packages/deprecated/src/index.js @@ -46,11 +46,11 @@ export default function deprecated( feature, options = {} ) { } = options; const pluginMessage = plugin ? ` from ${ plugin }` : ''; - const versionMessage = version ? `${ pluginMessage } in ${ version }` : ''; + const versionMessage = version ? ` and will be removed${ pluginMessage } in version ${ version }` : ''; const useInsteadMessage = alternative ? ` Please use ${ alternative } instead.` : ''; const linkMessage = link ? ` See: ${ link }` : ''; const hintMessage = hint ? ` Note: ${ hint }` : ''; - const message = `${ feature } is deprecated and will be removed${ versionMessage }.${ useInsteadMessage }${ linkMessage }${ hintMessage }`; + const message = `${ feature } is deprecated${ versionMessage }.${ useInsteadMessage }${ linkMessage }${ hintMessage }`; // Skip if already logged. if ( message in logged ) { diff --git a/packages/deprecated/src/test/index.js b/packages/deprecated/src/test/index.js index cb5d59e9459d2f..6ef419bdeb43c6 100644 --- a/packages/deprecated/src/test/index.js +++ b/packages/deprecated/src/test/index.js @@ -19,61 +19,61 @@ describe( 'deprecated', () => { deprecated( 'Eating meat' ); expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated and will be removed.' + 'Eating meat is deprecated.' ); } ); it( 'should show a deprecation warning with a version', () => { - deprecated( 'Eating meat', { version: 'the future' } ); + deprecated( 'Eating meat', { version: '2020.01.01' } ); expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated and will be removed in the future.' + 'Eating meat is deprecated and will be removed in version 2020.01.01.' ); } ); it( 'should show a deprecation warning with an alternative', () => { - deprecated( 'Eating meat', { version: 'the future', alternative: 'vegetables' } ); + deprecated( 'Eating meat', { version: '2020.01.01', alternative: 'vegetables' } ); expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated and will be removed in the future. Please use vegetables instead.' + 'Eating meat is deprecated and will be removed in version 2020.01.01. Please use vegetables instead.' ); } ); it( 'should show a deprecation warning with an alternative specific to a plugin', () => { deprecated( 'Eating meat', { - version: 'the future', + version: '2020.01.01', alternative: 'vegetables', plugin: 'the earth', } ); expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated and will be removed from the earth in the future. Please use vegetables instead.' + 'Eating meat is deprecated and will be removed from the earth in version 2020.01.01. Please use vegetables instead.' ); } ); it( 'should show a deprecation warning with a link', () => { deprecated( 'Eating meat', { - version: 'the future', + version: '2020.01.01', alternative: 'vegetables', plugin: 'the earth', link: 'https://en.wikipedia.org/wiki/Vegetarianism', } ); expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated and will be removed from the earth in the future. Please use vegetables instead. See: https://en.wikipedia.org/wiki/Vegetarianism' + 'Eating meat is deprecated and will be removed from the earth in version 2020.01.01. Please use vegetables instead. See: https://en.wikipedia.org/wiki/Vegetarianism' ); } ); it( 'should show a deprecation warning with a hint', () => { deprecated( 'Eating meat', { - version: 'the future', + version: '2020.01.01', alternative: 'vegetables', plugin: 'the earth', hint: 'You may find it beneficial to transition gradually.', } ); expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated and will be removed from the earth in the future. Please use vegetables instead. Note: You may find it beneficial to transition gradually.' + 'Eating meat is deprecated and will be removed from the earth in version 2020.01.01. Please use vegetables instead. Note: You may find it beneficial to transition gradually.' ); } ); From 916cbb0817f3c60b54067b361f56475acf511cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 31 Jul 2019 15:59:14 +0200 Subject: [PATCH 569/664] Block library: Refactor Verse block to use class names for text align (#16777) --- packages/block-library/CHANGELOG.md | 5 +++ packages/block-library/src/style.scss | 2 ++ .../block-library/src/verse/deprecated.js | 35 +++++++++++++++++++ packages/block-library/src/verse/edit.js | 9 ++++- packages/block-library/src/verse/index.js | 6 ++-- packages/block-library/src/verse/save.js | 11 +++++- .../blocks/core__verse__deprecated.html | 3 ++ .../blocks/core__verse__deprecated.json | 13 +++++++ .../core__verse__deprecated.parsed.json | 22 ++++++++++++ .../core__verse__deprecated.serialized.html | 3 ++ 10 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 packages/block-library/src/verse/deprecated.js create mode 100644 packages/e2e-tests/fixtures/blocks/core__verse__deprecated.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__verse__deprecated.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__verse__deprecated.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__verse__deprecated.serialized.html diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index e61bddfbbf2de0..78e90241d1869a 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,5 +1,10 @@ ## Master (unreleased) +### Enhancements + +- Heading block uses `has-text-align-*` class names rather than inline style for text alignment. +- Verse block uses `has-text-align-*` class names rather than inline style for text alignment. + ### Bug Fixes - Fixed insertion of columns in the table block, which now inserts columns for all table sections ([#16410](https://github.com/WordPress/gutenberg/pull/16410)) diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index f9a2909ff360a8..540fd4cd99ec51 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -153,10 +153,12 @@ } .has-text-align-left { + /*rtl:ignore*/ text-align: left; } .has-text-align-right { + /*rtl:ignore*/ text-align: right; } diff --git a/packages/block-library/src/verse/deprecated.js b/packages/block-library/src/verse/deprecated.js new file mode 100644 index 00000000000000..f26ac103c226b6 --- /dev/null +++ b/packages/block-library/src/verse/deprecated.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { RichText } from '@wordpress/block-editor'; + +const blockAttributes = { + content: { + type: 'string', + source: 'html', + selector: 'pre', + default: '', + }, + textAlign: { + type: 'string', + }, +}; + +const deprecated = [ + { + attributes: blockAttributes, + save( { attributes } ) { + const { textAlign, content } = attributes; + + return ( + <RichText.Content + tagName="pre" + style={ { textAlign } } + value={ content } + /> + ); + }, + }, +]; + +export default deprecated; diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index edb17517d5f093..ff0beac6622fc1 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -29,9 +34,11 @@ export default function VerseEdit( { attributes, setAttributes, className, merge content: nextContent, } ); } } - style={ { textAlign } } placeholder={ __( 'Write…' ) } wrapperClassName={ className } + className={ classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ) } onMerge={ mergeBlocks } /> </> diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index 5752f15efb7d20..ffabff1dba480a 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ +import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; @@ -22,11 +23,12 @@ export const settings = { icon, keywords: [ __( 'poetry' ) ], transforms, - edit, - save, + deprecated, merge( attributes, attributesToMerge ) { return { content: attributes.content + attributesToMerge.content, }; }, + edit, + save, }; diff --git a/packages/block-library/src/verse/save.js b/packages/block-library/src/verse/save.js index ea9de0abcc8f0c..59bb8557462699 100644 --- a/packages/block-library/src/verse/save.js +++ b/packages/block-library/src/verse/save.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -6,10 +11,14 @@ import { RichText } from '@wordpress/block-editor'; export default function save( { attributes } ) { const { textAlign, content } = attributes; + const className = classnames( { + [ `has-text-align-${ textAlign }` ]: textAlign, + } ); + return ( <RichText.Content tagName="pre" - style={ { textAlign } } + className={ className } value={ content } /> ); diff --git a/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.html b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.html new file mode 100644 index 00000000000000..65fd8787b921c7 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.html @@ -0,0 +1,3 @@ +<!-- wp:verse {"textAlign":"left"} --> +<pre style="text-align:left" class="wp-block-verse">A <em>verse</em>…<br>And more!</pre> +<!-- /wp:core/verse --> diff --git a/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.json b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.json new file mode 100644 index 00000000000000..002e26a7da88b9 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.json @@ -0,0 +1,13 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/verse", + "isValid": true, + "attributes": { + "content": "A <em>verse</em>…<br>And more!", + "textAlign": "left" + }, + "innerBlocks": [], + "originalContent": "<pre style=\"text-align:left\" class=\"wp-block-verse\">A <em>verse</em>…<br>And more!</pre>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.parsed.json b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.parsed.json new file mode 100644 index 00000000000000..2e703ee0d855ea --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/verse", + "attrs": { + "textAlign": "left" + }, + "innerBlocks": [], + "innerHTML": "\n<pre style=\"text-align:left\" class=\"wp-block-verse\">A <em>verse</em>…<br>And more!</pre>\n", + "innerContent": [ + "\n<pre style=\"text-align:left\" class=\"wp-block-verse\">A <em>verse</em>…<br>And more!</pre>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.serialized.html b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.serialized.html new file mode 100644 index 00000000000000..a1075252bbc83c --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__verse__deprecated.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:verse {"textAlign":"left"} --> +<pre class="wp-block-verse has-text-align-left">A <em>verse</em>…<br>And more!</pre> +<!-- /wp:verse --> From cd3baf4d9999886b7b7a3add730456d56b75c373 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 31 Jul 2019 10:58:18 -0400 Subject: [PATCH 570/664] Editor: Optimize common dependencies bail-out (#16839) --- packages/editor/src/store/actions.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 8ea2c9a408de84..36190a77b47ade 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -55,6 +55,11 @@ const lastBlockSourceDependenciesByRegistry = new WeakMap; */ function* getBlocksWithSourcedAttributes( blocks ) { const registry = yield getRegistry(); + if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { + return blocks; + } + + const blockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); let workingBlocks = blocks; for ( let i = 0; i < blocks.length; i++ ) { @@ -66,11 +71,6 @@ function* getBlocksWithSourcedAttributes( blocks ) { continue; } - if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { - continue; - } - - const blockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); if ( ! blockSourceDependencies.has( sources[ schema.source ] ) ) { continue; } @@ -136,13 +136,13 @@ function* resetLastBlockSourceDependencies( sourcesToUpdate = Object.values( sou } const registry = yield getRegistry(); + if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { + lastBlockSourceDependenciesByRegistry.set( registry, new WeakMap ); + } - for ( const source of sourcesToUpdate ) { - if ( ! lastBlockSourceDependenciesByRegistry.has( registry ) ) { - lastBlockSourceDependenciesByRegistry.set( registry, new WeakMap ); - } + const lastBlockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); - const lastBlockSourceDependencies = lastBlockSourceDependenciesByRegistry.get( registry ); + for ( const source of sourcesToUpdate ) { const dependencies = yield* source.getDependencies(); lastBlockSourceDependencies.set( source, dependencies ); } From 96dcc4ffac4fe18e94b302dcf8202bd886c0f57d Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Wed, 31 Jul 2019 09:14:31 -0700 Subject: [PATCH 571/664] Update BlockPreview component to accept multiple Blocks to preview (#16033) * export BlockPreview * Update to accept multiple Blocks * Update reusable Blocks preview to use the new single blocks prop * Remove unecessary clone of Blocks Not sure why this was introduced. * Remove export. This is being handled in another PR See https://github.com/WordPress/gutenberg/pull/16801 * Simplify casting to array via lodash * Utilise cloneBlocks to safely merge attributes on blocks prop * Spread reusable block initial attrs correctly * Fix cloneBlock fn to check for innerBlocks before attempting to map The `innerBlocks` of the `block` being cloned can be `undefined`. Therefore, by attempting to map these we trigger an error. Fixed to introduce existence check before attempting to manipulate innerBlocks. * Simplify modifying passed block attrs via cloneBlock * Removes unecessary spread operation initalAttributes is already an object so no need to spread into an object. * block-preview: ensuring to cast BlockEditorProvider value prop as an Array * don't call cloneBlock on a non-block object * bring back the old behavior of cloneBlock --- .../src/components/block-preview/index.js | 15 +++++++++++---- .../src/components/block-styles/index.js | 13 +++---------- .../src/components/block-switcher/index.js | 11 +++++++---- .../block-editor/src/components/inserter/menu.js | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index cc4c846a1cff3e..b4d0850f3b8108 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -1,8 +1,12 @@ +/** + * External dependencies + */ +import { castArray } from 'lodash'; + /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; import { Disabled } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; @@ -28,12 +32,15 @@ function BlockPreview( props ) { ); } -export function BlockPreviewContent( { name, attributes, innerBlocks, settings } ) { - const block = createBlock( name, attributes, innerBlocks ); +export function BlockPreviewContent( { blocks, settings } ) { + if ( ! blocks ) { + return null; + } + return ( <Disabled className="editor-block-preview__content block-editor-block-preview__content editor-styles-wrapper" aria-hidden> <BlockEditorProvider - value={ [ block ] } + value={ castArray( blocks ) } settings={ settings } > <BlockList /> diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index 93c522eb227f51..2485c73e9a0a44 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -12,7 +12,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; import TokenList from '@wordpress/token-list'; import { ENTER, SPACE } from '@wordpress/keycodes'; import { _x } from '@wordpress/i18n'; -import { getBlockType } from '@wordpress/blocks'; +import { getBlockType, cloneBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -68,8 +68,6 @@ function BlockStyles( { styles, className, onChangeClassName, - name, - attributes, type, block, onSwitch = noop, @@ -125,12 +123,9 @@ function BlockStyles( { > <div className="editor-block-styles__item-preview block-editor-block-styles__item-preview"> <BlockPreviewContent - name={ name } - attributes={ { - ...attributes, + blocks={ cloneBlock( block, { className: styleClassName, - } } - innerBlocks={ block.innerBlocks } + } ) } /> </div> <div className="editor-block-styles__item-label block-editor-block-styles__item-label"> @@ -152,8 +147,6 @@ export default compose( [ return { block, - name: block.name, - attributes: block.attributes, className: block.attributes.className || '', styles: getBlockStyles( block.name ), type: blockType, diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index eae64277471b2e..4101092db6d4d1 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -8,7 +8,7 @@ import { castArray, filter, first, mapKeys, orderBy, uniq, map } from 'lodash'; */ import { __, _n, sprintf } from '@wordpress/i18n'; import { Dropdown, IconButton, Toolbar, PanelBody, Path, SVG } from '@wordpress/components'; -import { getBlockType, getPossibleBlockTransformations, switchToBlockType, hasChildBlocksWithInserterSupport } from '@wordpress/blocks'; +import { getBlockType, getPossibleBlockTransformations, switchToBlockType, hasChildBlocksWithInserterSupport, cloneBlock } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; import { DOWN } from '@wordpress/keycodes'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -161,9 +161,12 @@ export class BlockSwitcher extends Component { { ( hoveredClassName !== null ) && <BlockPreview - name={ blocks[ 0 ].name } - attributes={ { ...blocks[ 0 ].attributes, className: hoveredClassName } } - innerBlocks={ blocks[ 0 ].innerBlocks } + blocks={ + cloneBlock( blocks[ 0 ], { + className: hoveredClassName, + } ) + } + /> } </> diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index bf659f789d1881..12d119ce4ab519 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -351,7 +351,7 @@ export class InserterMenu extends Component { </div> { hoveredItem && isReusableBlock( hoveredItem ) && - <BlockPreview name={ hoveredItem.name } attributes={ hoveredItem.initialAttributes } /> + <BlockPreview blocks={ createBlock( hoveredItem.name, hoveredItem.initialAttributes ) } /> } </div> ); From e4b13379c523eed69f3ca4e2f7407bcbea6dd659 Mon Sep 17 00:00:00 2001 From: Seghir Nadir <nadir.seghir@gmail.com> Date: Wed, 31 Jul 2019 19:36:00 +0100 Subject: [PATCH 572/664] add color support to separator block (#16784) * add color support to separator block * add color to frontend render * add support for dots style in frontend * fix review problems, update block.json file * fix border issue with theme.scss * extend border remove in editor * fix problems with no color selected --- .../block-library/src/separator/block.json | 10 ++++- packages/block-library/src/separator/edit.js | 45 ++++++++++++++++++- packages/block-library/src/separator/save.js | 42 ++++++++++++++++- .../block-library/src/separator/style.scss | 6 ++- .../block-library/src/separator/theme.scss | 9 ++++ 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/packages/block-library/src/separator/block.json b/packages/block-library/src/separator/block.json index 70060a329cdfd1..98bcbb9150609f 100644 --- a/packages/block-library/src/separator/block.json +++ b/packages/block-library/src/separator/block.json @@ -1,4 +1,12 @@ { "name": "core/separator", - "category": "layout" + "category": "layout", + "attributes": { + "color": { + "type": "string" + }, + "customColor": { + "type": "string" + } + } } diff --git a/packages/block-library/src/separator/edit.js b/packages/block-library/src/separator/edit.js index 26ca8d04f2295d..050bd7423fb665 100644 --- a/packages/block-library/src/separator/edit.js +++ b/packages/block-library/src/separator/edit.js @@ -1,8 +1,49 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { HorizontalRule } from '@wordpress/components'; +import { + InspectorControls, + withColors, + PanelColorSettings, +} from '@wordpress/block-editor'; -export default function SeparatorEdit( { className } ) { - return <HorizontalRule className={ className } />; +function SeparatorEdit( { color, setColor, className } ) { + return ( + <> + <HorizontalRule + className={ classnames( + className, { + 'has-background': color.color, + [ color.class ]: color.class, + } + ) } + style={ { + backgroundColor: color.color, + color: color.color, + } } + /> + <InspectorControls> + <PanelColorSettings + title={ __( 'Color Settings' ) } + colorSettings={ [ + { + value: color.color, + onChange: setColor, + label: __( 'Color' ), + }, + ] } + > + </PanelColorSettings> + </InspectorControls> + </> + ); } + +export default withColors( 'color', { textColor: 'color' } )( SeparatorEdit ); diff --git a/packages/block-library/src/separator/save.js b/packages/block-library/src/separator/save.js index 72ddec8db51cee..d9b899486233c8 100644 --- a/packages/block-library/src/separator/save.js +++ b/packages/block-library/src/separator/save.js @@ -1,3 +1,41 @@ -export default function save() { - return <hr />; +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { + getColorClassName, +} from '@wordpress/block-editor'; + +export default function separatorSave( { attributes } ) { + const { + color, + customColor, + } = attributes; + + // the hr support changing color using border-color, since border-color + // is not yet supported in the color palette, we use background-color + const backgroundClass = getColorClassName( 'background-color', color ); + // the dots styles uses text for the dots, to change those dots color is + // using color, not backgroundColor + const colorClass = getColorClassName( 'color', color ); + + const separatorClasses = classnames( { + 'has-text-color has-background': color || customColor, + [ backgroundClass ]: backgroundClass, + [ colorClass ]: colorClass, + } ); + + const separatorStyle = { + backgroundColor: backgroundClass ? undefined : customColor, + color: colorClass ? undefined : customColor, + }; + + return ( <hr + className={ separatorClasses } + style={ separatorStyle } + /> ); } diff --git a/packages/block-library/src/separator/style.scss b/packages/block-library/src/separator/style.scss index 57175b5f83bedb..a4cf1f2fdb6d27 100644 --- a/packages/block-library/src/separator/style.scss +++ b/packages/block-library/src/separator/style.scss @@ -8,7 +8,9 @@ // Dots style &.is-style-dots { - background: none; // Override any background themes often set on the hr tag for this style. + // Override any background themes often set on the hr tag for this style. + // also override the color set in the editor since it's intented for normal HR + background: none !important; border: none; text-align: center; max-width: none; @@ -17,7 +19,7 @@ &::before { content: "\00b7 \00b7 \00b7"; - color: $dark-gray-900; + color: currentColor; font-size: 20px; letter-spacing: 2em; padding-left: 2em; diff --git a/packages/block-library/src/separator/theme.scss b/packages/block-library/src/separator/theme.scss index 80834e1ebf8392..f0673d7bb6f5d4 100644 --- a/packages/block-library/src/separator/theme.scss +++ b/packages/block-library/src/separator/theme.scss @@ -8,4 +8,13 @@ &:not(.is-style-wide):not(.is-style-dots) { max-width: 100px; } + + &.has-background:not(.is-style-dots) { + border-bottom: none; + height: 1px; + } + + &.has-background:not(.is-style-wide):not(.is-style-dots) { + height: 2px; + } } From 1a929eb4074597034ecd1d2ee0885595b9981378 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 31 Jul 2019 16:27:31 -0400 Subject: [PATCH 573/664] Data: Create registry selector with self-contained registry proxying (#16692) * Data: Create registry selector with self-contained registry proxying * Editor: Fix tests that relied on registry selectors being higher order. * Data: Add typedef import reference for WPDataRegistry * Data: Omit removed registry argument from mapSelectors --- packages/data/src/factory.js | 29 +++++++++++++++++-- packages/data/src/namespace-store/index.js | 26 ++++++----------- packages/data/src/test/registry.js | 32 +++++++++++++++++++++ packages/editor/src/store/test/selectors.js | 7 ++++- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/packages/data/src/factory.js b/packages/data/src/factory.js index f7dbb7f269a869..26fceb9bd456d5 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.js @@ -1,3 +1,12 @@ +/** + * Internal dependencies + */ +import defaultRegistry from './default-registry'; + +/** + * @typedef {import('./registry').WPDataRegistry} WPDataRegistry + */ + /** * Mark a selector as a registry selector. * @@ -6,9 +15,25 @@ * @return {function} marked registry selector. */ export function createRegistrySelector( registrySelector ) { - registrySelector.isRegistrySelector = true; + const selector = ( ...args ) => registrySelector( selector.registry.select )( ...args ); + + /** + * Flag indicating to selector registration mapping that the selector should + * be mapped as a registry selector. + * + * @type {boolean} + */ + selector.isRegistrySelector = true; + + /** + * Registry on which to call `select`, stubbed for non-standard usage to + * use the default registry. + * + * @type {WPDataRegistry} + */ + selector.registry = defaultRegistry; - return registrySelector; + return selector; } /** diff --git a/packages/data/src/namespace-store/index.js b/packages/data/src/namespace-store/index.js index 8d2b009cc4d7d5..b3bd639fcddfa3 100644 --- a/packages/data/src/namespace-store/index.js +++ b/packages/data/src/namespace-store/index.js @@ -51,16 +51,12 @@ export default function createNamespace( key, options, registry ) { ...mapValues( metadataSelectors, ( selector ) => ( state, ...args ) => selector( state.metadata, ...args ) ), ...mapValues( options.selectors, ( selector ) => { if ( selector.isRegistrySelector ) { - const mappedSelector = ( reg ) => ( state, ...args ) => { - return selector( reg )( state.root, ...args ); - }; - mappedSelector.isRegistrySelector = selector.isRegistrySelector; - return mappedSelector; + selector.registry = registry; } return ( state, ...args ) => selector( state.root, ...args ); } ), - }, store, registry ); + }, store ); if ( options.resolvers ) { const result = mapResolvers( options.resolvers, selectors, store ); resolvers = result.resolvers; @@ -152,21 +148,15 @@ function createReduxStore( key, options, registry ) { /** * Maps selectors to a store. * - * @param {Object} selectors Selectors to register. Keys will be used as - * the public facing API. Selectors will get - * passed the state as first argument. - * @param {Object} store The store to which the selectors should be - * mapped. - * @param {WPDataRegistry} registry Registry reference. + * @param {Object} selectors Selectors to register. Keys will be used as the + * public facing API. Selectors will get passed the + * state as first argument. + * @param {Object} store The store to which the selectors should be mapped. * * @return {Object} Selectors mapped to the provided store. */ -function mapSelectors( selectors, store, registry ) { - const createStateSelector = ( registeredSelector ) => { - const registrySelector = registeredSelector.isRegistrySelector ? - registeredSelector( registry.select ) : - registeredSelector; - +function mapSelectors( selectors, store ) { + const createStateSelector = ( registrySelector ) => { const selector = function runSelector() { // This function is an optimized implementation of: // diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index 57b8789e7095c3..23a6d399ce7786 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -464,6 +464,38 @@ describe( 'createRegistry', () => { expect( registry.select( 'reducer2' ).selector2() ).toEqual( 'result1' ); } ); + + it( 'should run the registry selector from a non-registry selector', () => { + const selector1 = () => 'result1'; + const selector2 = createRegistrySelector( ( select ) => () => + select( 'reducer1' ).selector1() + ); + const selector3 = () => selector2(); + registry.registerStore( 'reducer1', { + reducer: () => 'state1', + selectors: { + selector1, + }, + } ); + registry.registerStore( 'reducer2', { + reducer: () => 'state1', + selectors: { + selector2, + selector3, + }, + } ); + + expect( registry.select( 'reducer2' ).selector3() ).toEqual( 'result1' ); + } ); + + it( 'gracefully stubs select on selector calls', () => { + const selector = createRegistrySelector( ( select ) => () => select ); + + const maybeSelect = selector(); + + expect( maybeSelect ).toEqual( expect.any( Function ) ); + expect( maybeSelect() ).toEqual( expect.any( Object ) ); + } ); } ); describe( 'subscribe', () => { diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index f2e30f17372e10..6d10b7f863aead 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -44,7 +44,7 @@ const { isCurrentPostScheduled, isEditedPostPublishable, isEditedPostSaveable, - isEditedPostAutosaveable: isEditedPostAutosaveableRegistrySelector, + isEditedPostAutosaveable: _isEditedPostAutosaveableRegistrySelector, isEditedPostEmpty, isEditedPostBeingScheduled, isEditedPostDateFloating, @@ -71,9 +71,14 @@ const { describe( 'selectors', () => { let cachedSelectors; + let isEditedPostAutosaveableRegistrySelector; beforeAll( () => { cachedSelectors = filter( selectors, ( selector ) => selector.clear ); + isEditedPostAutosaveableRegistrySelector = ( select ) => { + _isEditedPostAutosaveableRegistrySelector.registry = { select }; + return _isEditedPostAutosaveableRegistrySelector; + }; } ); beforeEach( () => { From 467e45f787f93d35030ce266a500c627db0e18e5 Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Wed, 31 Jul 2019 17:02:55 -0400 Subject: [PATCH 574/664] eslint-plugin: Fix CHANGELOG URL typo --- packages/eslint-plugin/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 9734da4def88d7..b83a615c712818 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -11,7 +11,7 @@ ### Improvements -- The recommended `react` configuration specifies an option to [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/react-unused-vars-before-return.md) to exempt React hooks usage, by convention of hooks beginning with "use" prefix. +- The recommended `react` configuration specifies an option to [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) to exempt React hooks usage, by convention of hooks beginning with "use" prefix. ## 2.3.0 (2019-06-12) From b7528b82684350fb2a35596f850d30dd16220138 Mon Sep 17 00:00:00 2001 From: Rishabh Budhiraja <rishabh.budhiraja@gmail.com> Date: Thu, 1 Aug 2019 10:32:54 +0530 Subject: [PATCH 575/664] Fixed typo mistake (#16852) Fixes #16809 Removes unnecessary comma after 'PluginMoreyMenuItem'. --- .../developers/slotfills/plugin-more-menu-item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md b/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md index 627d6fab767a05..23efd6a021685c 100644 --- a/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md +++ b/docs/designers-developers/developers/slotfills/plugin-more-menu-item.md @@ -6,7 +6,7 @@ This slot will add a new item to the More Tools & Options section. ```js const { registerPlugin } = wp.plugins; -const { PluginMoreMenuItem,} = wp.editPost; +const { PluginMoreMenuItem } = wp.editPost; const MyButtonMoreMenuItemTest = () => ( <PluginMoreMenuItem From 4a2c1f426daa26561b36a33d3c9006e317e0863e Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Thu, 1 Aug 2019 11:04:30 +0200 Subject: [PATCH 576/664] Update wording and fix some typos in the release tool (#16832) --- bin/commander.js | 36 ++++++++++++++++++------------------ docs/contributors/release.md | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bin/commander.js b/bin/commander.js index 9eeae01834ac55..bb5f508ca56965 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -122,7 +122,7 @@ async function runSvnRepositoryCloneStep( abortMessage ) { } /** - * Updates and commits the content of the SVN repo using the new plugin zip. + * Updates and commits the content of the SVN repo using the new plugin ZIP. * * @param {string} version Version. * @param {string} changelog Changelog. @@ -131,12 +131,12 @@ async function runSvnRepositoryCloneStep( abortMessage ) { async function runUpdateTrunkContentStep( version, changelog, abortMessage ) { // Updating the content of the svn await runStep( 'Updating trunk content', abortMessage, async () => { - console.log( '>> Replacing trunk content using the new plugin zip' ); + console.log( '>> Replacing trunk content using the new plugin ZIP' ); // Delete everything except readme.txt and changelog.txt runShellScript( 'find . -maxdepth 1 -not -name "changelog.txt" -not -name "readme.txt" -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +', svnWorkingDirectoryPath ); - // Update the content using the plugin zip + // Update the content using the plugin ZIP const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; runShellScript( 'unzip ' + gutenbergZipPath + ' -d ' + svnWorkingDirectoryPath ); @@ -385,13 +385,13 @@ async function runPluginZIPCreationStep( abortMessage ) { await runStep( 'Plugin ZIP creation', abortMessage, async () => { const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; await askForConfirmationToContinue( - 'Proceed and build the plugin zip? (It takes a few minutes)', + 'Proceed and build the plugin ZIP? (It takes a few minutes)', true, abortMessage ); runShellScript( '/bin/bash bin/build-plugin-zip.sh', gitWorkingDirectoryPath ); - console.log( '>> The plugin zip has been built successfully. Path: ' + success( gutenbergZipPath ) ); + console.log( '>> The plugin ZIP has been built successfully. Path: ' + success( gutenbergZipPath ) ); } ); } @@ -483,7 +483,7 @@ async function runGithubReleaseStep( version, versionLabel, isPrerelease, abortM abortMessage = abortMessage + ' Make sure to remove the the GitHub release as well.'; // Uploading the Gutenberg Zip to the release - await runStep( 'Uploading the plugin zip', abortMessage, async () => { + await runStep( 'Uploading the plugin ZIP', abortMessage, async () => { const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; const filestats = fs.statSync( gutenbergZipPath ); await octokit.repos.uploadReleaseAsset( { @@ -495,10 +495,10 @@ async function runGithubReleaseStep( version, versionLabel, isPrerelease, abortM name: 'gutenberg.zip', file: fs.createReadStream( gutenbergZipPath ), } ); - console.log( '>> The plugin zip has been successfully uploaded.' ); + console.log( '>> The plugin ZIP has been successfully uploaded.' ); } ); - console.log( '>> The Github release is available here: ' + success( release.html_url ) ); + console.log( '>> The GitHub release is available here: ' + success( release.html_url ) ); return release; } @@ -570,9 +570,9 @@ async function releasePlugin( isRC = true ) { await runCherrypickBumpCommitIntoMasterStep( commitHash, abortMessage ); if ( ! isRC ) { - abortMessage = 'Aborting! The Github release is done. Make sure to perform the SVN release manually.'; + abortMessage = 'Aborting! The GitHub release is done. Make sure to perform the SVN release manually.'; - await askForConfirmationToContinue( 'The Gihub release is complete. Proceed with the SVN release? ', abortMessage ); + await askForConfirmationToContinue( 'The GitHub release is complete. Proceed with the SVN release? ', abortMessage ); // Fetching the SVN repository await runSvnRepositoryCloneStep( abortMessage ); @@ -580,10 +580,10 @@ async function releasePlugin( isRC = true ) { // Updating the SVN trunk content await runUpdateTrunkContentStep( version, release.body, abortMessage ); - abortMessage = 'Aborting! The Github release is done, SVN trunk updated. Make sure to create the SVN tag and update the stable version manually.'; + abortMessage = 'Aborting! The GitHub release is done, SVN trunk updated. Make sure to create the SVN tag and update the stable version manually.'; await runSvnTagStep( version, abortMessage ); - abortMessage = 'Aborting! The Github release is done, SVN tagged. Make sure to update the stable version manually.'; + abortMessage = 'Aborting! The GitHub release is done, SVN tagged. Make sure to update the stable version manually.'; await updateThePluginStableVersion( version, abortMessage ); } @@ -601,15 +601,15 @@ program console.log( chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), 'Welcome! This tool is going to help you release a new RC version of the Gutenberg Plugin.\n', - 'It goes throught different steps : creating the release branch, bumping the plugin version, tagging and creating the github release, building the zip...\n', + 'It goes through different steps : creating the release branch, bumping the plugin version, tagging and creating the GitHub release, building the ZIP...\n', 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' ); const release = await releasePlugin( true ); console.log( - '\n>> 🎉 The Gutenberg ' + success( release.name ) + ' has been successfully released.\n', - 'You can access the Github release here: ' + success( release.html_url ) + '\n', + '\n>> 🎉 The Gutenberg version ' + success( release.name ) + ' has been successfully released.\n', + 'You can access the GitHub release here: ' + success( release.html_url ) + '\n', 'Thanks for performing the release!' ); } ); @@ -622,7 +622,7 @@ program console.log( chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), 'Welcome! This tool is going to help you release a new stable version of the Gutenberg Plugin.\n', - 'It goes throught different steps : bumping the plugin version, tagging and creating the github release, building the zip, pushing the release to the SVN repository...\n', + 'It goes through different steps : bumping the plugin version, tagging and creating the GitHub release, building the ZIP, pushing the release to the SVN repository...\n', 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' ); @@ -630,8 +630,8 @@ program console.log( '\n>> 🎉 The Gutenberg ' + success( release.name ) + ' has been successfully released.\n', - 'You can access the Github release here: ' + success( release.html_url ) + '\n', - 'In a few seconds, you\'ll be able to update the plugin from the WordPress repository.\n', + 'You can access the GitHub release here: ' + success( release.html_url ) + '\n', + 'In a few minutes, you\'ll be able to update the plugin from the WordPress repository.\n', 'Thanks for performing the release! and don\'t forget to publish the release post.' ); } ); diff --git a/docs/contributors/release.md b/docs/contributors/release.md index c9d4a99715dd9c..8b3fb3f41b745b 100644 --- a/docs/contributors/release.md +++ b/docs/contributors/release.md @@ -33,7 +33,7 @@ The plugin release process is entirely automated. To release the RC version of t To release a stable version, run: ```bash -./bin.commander.js stable +./bin/commander.js stable ``` It is possible to run the "stable" release CLI in a consecutive way to release patch releases following the first stable release. From 173af26d572b7071a9da423f6b81b99e7b427e6a Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Thu, 1 Aug 2019 11:05:06 +0200 Subject: [PATCH 577/664] Bail early in deactivatePlugin if plugin is already inactive (#16816) --- packages/e2e-test-utils/src/activate-plugin.js | 1 + packages/e2e-test-utils/src/deactivate-plugin.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/e2e-test-utils/src/activate-plugin.js b/packages/e2e-test-utils/src/activate-plugin.js index 1cfe3786f4cdbd..9d90f866ff5dd5 100644 --- a/packages/e2e-test-utils/src/activate-plugin.js +++ b/packages/e2e-test-utils/src/activate-plugin.js @@ -15,6 +15,7 @@ export async function activatePlugin( slug ) { await visitAdminPage( 'plugins.php' ); const disableLink = await page.$( `tr[data-slug="${ slug }"] .deactivate a` ); if ( disableLink ) { + await switchUserToTest(); return; } await page.click( `tr[data-slug="${ slug }"] .activate a` ); diff --git a/packages/e2e-test-utils/src/deactivate-plugin.js b/packages/e2e-test-utils/src/deactivate-plugin.js index bfaaf7e0a7eeba..88603d0aa47729 100644 --- a/packages/e2e-test-utils/src/deactivate-plugin.js +++ b/packages/e2e-test-utils/src/deactivate-plugin.js @@ -13,6 +13,11 @@ import { visitAdminPage } from './visit-admin-page'; export async function deactivatePlugin( slug ) { await switchUserToAdmin(); await visitAdminPage( 'plugins.php' ); + const deleteLink = await page.$( `tr[data-slug="${ slug }"] .delete a` ); + if ( deleteLink ) { + await switchUserToTest(); + return; + } await page.click( `tr[data-slug="${ slug }"] .deactivate a` ); await page.waitForSelector( `tr[data-slug="${ slug }"] .delete a` ); await switchUserToTest(); From 3b87561c166d1e3f23c1df2980d04109b57fb6c5 Mon Sep 17 00:00:00 2001 From: Courtney <9257264+cburton4@users.noreply.github.com> Date: Thu, 1 Aug 2019 05:05:52 -0400 Subject: [PATCH 578/664] DateTime component: Add additional design documentation and image (#16757) --- packages/components/src/date-time/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md index 530a67e0e8e576..3cd2392b09e2fc 100644 --- a/packages/components/src/date-time/README.md +++ b/packages/components/src/date-time/README.md @@ -1,6 +1,14 @@ # DateTimePicker -DateTimePicker is a React component to render a calendar and clock for selecting a date and time. The calendar and clock components can be accessed individually using the `DatePicker` and `TimePicker` components respectively. +DateTimePicker is a React component that renders a calendar and clock for date and time selection. The calendar and clock components can be accessed individually using the `DatePicker` and `TimePicker` components respectively. + +![Date Time component](https://wordpress.org/gutenberg/files/2019/07/date-time-picker.png) + +## Best practices + +Date pickers should: + +- Use smart defaults and highlight the current date. ## Usage From c0e4f538ad2ed96eed064b39657b35f5645238a1 Mon Sep 17 00:00:00 2001 From: Courtney <9257264+cburton4@users.noreply.github.com> Date: Thu, 1 Aug 2019 05:06:28 -0400 Subject: [PATCH 579/664] Spinner Component: Added design documentation and image (#16760) --- packages/components/src/spinner/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/components/src/spinner/README.md b/packages/components/src/spinner/README.md index b4978f5f4607c2..38d3c8ec1f39f4 100644 --- a/packages/components/src/spinner/README.md +++ b/packages/components/src/spinner/README.md @@ -1,5 +1,15 @@ # Spinner +Spinners notify users that their action is being processed. + +![Spinner component](https://wordpress.org/gutenberg/files/2019/07/spinner.png) + +## Best practices + +The spinner component should: + +- Signal to users that the processing of their request is underway and will soon complete. + ## Usage ```jsx From ee3a375d0b4e9f7ad6f180e386c4109b5bd8b965 Mon Sep 17 00:00:00 2001 From: Darren Ethier <darren@roughsmootheng.in> Date: Thu, 1 Aug 2019 05:08:12 -0400 Subject: [PATCH 580/664] @wordpress/data: Handle more return type values from a mapSelect argument on useSelect (#16669) --- .../data/src/components/use-select/index.js | 13 ++- .../src/components/use-select/test/index.js | 86 +++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index d71f72b21c917f..a87d20422a9ca4 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -10,7 +10,7 @@ import { useEffect, useReducer, } from '@wordpress/element'; -import { isShallowEqualObjects } from '@wordpress/is-shallow-equal'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -90,16 +90,15 @@ export default function useSelect( _mapSelect, deps ) { let mapOutput; try { - if ( - latestMapSelect.current !== mapSelect || - latestMapOutputError.current - ) { + if ( latestMapSelect.current !== mapSelect || latestMapOutputError.current ) { mapOutput = mapSelect( registry.select, registry ); } else { mapOutput = latestMapOutput.current; } } catch ( error ) { - let errorMessage = `An error occurred while running 'mapSelect': ${ error.message }`; + let errorMessage = `An error occurred while running 'mapSelect': ${ + error.message + }`; if ( latestMapOutputError.current ) { errorMessage += `\nThe error may be correlated with this previous error:\n`; @@ -129,7 +128,7 @@ export default function useSelect( _mapSelect, deps ) { registry.select, registry ); - if ( isShallowEqualObjects( latestMapOutput.current, newMapOutput ) ) { + if ( isShallowEqual( latestMapOutput.current, newMapOutput ) ) { return; } latestMapOutput.current = newMapOutput; diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index ce8d15f3aa0707..1aa2ad0d7b9d70 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -132,5 +132,91 @@ describe( 'useSelect', () => { children: 'bar', } ); } ); + describe( 'rerenders as expected with various mapSelect return types', () => { + const getComponent = ( mapSelectSpy ) => () => { + const data = useSelect( mapSelectSpy, [] ); + return <div data={ data } />; + }; + let subscribedSpy, TestComponent; + const mapSelectSpy = jest.fn( + ( select ) => select( 'testStore' ).testSelector() + ); + const selectorSpy = jest.fn(); + const subscribeCallback = ( subscription ) => { + subscribedSpy = subscription; + }; + + beforeEach( () => { + registry.registerStore( 'testStore', { + reducer: () => null, + selectors: { + testSelector: selectorSpy, + }, + } ); + registry.subscribe = subscribeCallback; + TestComponent = getComponent( mapSelectSpy ); + } ); + afterEach( () => { + selectorSpy.mockClear(); + mapSelectSpy.mockClear(); + } ); + + it.each( [ + [ + 'boolean', + [ false, true ], + ], + [ + 'number', + [ 10, 20 ], + ], + [ + 'string', + [ 'bar', 'cheese' ], + ], + [ + 'array', + [ [ 10, 20 ], [ 10, 30 ] ], + ], + [ + 'object', + [ { foo: 'bar' }, { foo: 'cheese' } ], + ], + [ + 'null', + [ null, undefined ], + ], + [ + 'undefined', + [ undefined, 42 ], + ], + ] )( 'renders as expected with %s return values', ( + type, + testValues, + ) => { + const [ valueA, valueB ] = testValues; + selectorSpy.mockReturnValue( valueA ); + let renderer; + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + const testInstance = renderer.root; + // ensure expected state was rendered. + expect( testInstance.findByType( 'div' ).props.data ).toEqual( valueA ); + + // Update the returned value from the selector and trigger the + // subscription which should in turn trigger a re-render. + act( () => { + selectorSpy.mockReturnValue( valueB ); + subscribedSpy(); + } ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( valueB ); + expect( mapSelectSpy ).toHaveBeenCalledTimes( 3 ); + } ); + } ); } ); From 34210065ed7a549ebbbcbf1d3075701408d08ddc Mon Sep 17 00:00:00 2001 From: Jeff Ong <jonger4@gmail.com> Date: Thu, 1 Aug 2019 05:09:24 -0400 Subject: [PATCH 581/664] [UI Components] resolve missing styles from checkbox-control component (#11089) (#16551) --- .../components/src/checkbox-control/index.js | 2 + .../src/checkbox-control/style.scss | 71 ++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/components/src/checkbox-control/index.js b/packages/components/src/checkbox-control/index.js index d84be2d56e24c4..0cf12f0b9dcd90 100644 --- a/packages/components/src/checkbox-control/index.js +++ b/packages/components/src/checkbox-control/index.js @@ -7,6 +7,7 @@ import { withInstanceId } from '@wordpress/compose'; * Internal dependencies */ import BaseControl from '../base-control'; +import Dashicon from '../dashicon'; function CheckboxControl( { label, className, heading, checked, help, instanceId, onChange, ...props } ) { const id = `inspector-checkbox-control-${ instanceId }`; @@ -25,6 +26,7 @@ function CheckboxControl( { label, className, heading, checked, help, instanceId { ...props } /> <label className="components-checkbox-control__label" htmlFor={ id }> + { checked ? <Dashicon icon="yes" className="components-checkbox-control__checked" role="presentation" /> : null } { label } </label> </BaseControl> diff --git a/packages/components/src/checkbox-control/style.scss b/packages/components/src/checkbox-control/style.scss index 861eddb655ad8a..240b1ad7623b83 100644 --- a/packages/components/src/checkbox-control/style.scss +++ b/packages/components/src/checkbox-control/style.scss @@ -1,3 +1,72 @@ .components-checkbox-control__input[type="checkbox"] { - margin-top: 0; + border: 1px solid #b4b9be; + background: #fff; + color: #555; + clear: none; + cursor: pointer; + display: inline-block; + line-height: 0; + height: 16px; + margin: 0 4px 0 0; + outline: 0; + padding: 0 !important; + text-align: center; + vertical-align: middle; + width: 16px; + min-width: 16px; + -webkit-appearance: none; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + transition: 0.05s border-color ease-in-out; + + &:focus { + border-color: #5b9dd9; + box-shadow: 0 0 2px rgba(30, 140, 190, 0.8); + // Only visible in Windows High Contrast mode. + outline: 2px solid transparent; + } + + &:checked { + background: #11a0d2; + border-color: #11a0d2; + } + + &:focus:checked { + border: none; + } + + &:checked::before { + content: none; + } +} + +.components-checkbox-control__label { + position: relative; + vertical-align: middle; + line-height: 1; +} + +svg.dashicon.components-checkbox-control__checked { + width: 21px; + height: 21px; + fill: #fff; + cursor: pointer; + position: absolute; + left: -31px; + bottom: -3px; + user-select: none; + pointer-events: none; +} + +@media screen and ( max-width: 728px ) { + .components-checkbox-control__input[type="checkbox"] { + width: 25px; + height: 25px; + } + + svg.dashicon.components-checkbox-control__checked { + width: 31px; + height: 31px; + left: -41px; + bottom: -9px; + } } From 5008a50912518f0c89014fe2f68fc782ecc39bc4 Mon Sep 17 00:00:00 2001 From: Sergey Biryukov <sergeybiryukov.ru@gmail.com> Date: Thu, 1 Aug 2019 12:16:23 +0300 Subject: [PATCH 582/664] Make sure $label_markup is always defined. (#16189) Fixes #16188. --- packages/block-library/src/search/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index 7952a9085c51f0..f76882f8b41d5a 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -15,7 +15,8 @@ function render_block_core_search( $attributes ) { static $instance_id = 0; - $input_id = 'wp-block-search__input-' . ++$instance_id; + $input_id = 'wp-block-search__input-' . ++$instance_id; + $label_markup = ''; if ( ! empty( $attributes['label'] ) ) { $label_markup = sprintf( From 30c1ec95937325f8cc6aee9af1b948924be8402a Mon Sep 17 00:00:00 2001 From: Foysal Remon <foysalremon@users.noreply.github.com> Date: Thu, 1 Aug 2019 15:44:35 +0600 Subject: [PATCH 583/664] Resolve block modal: make the columns have the same content space (#15581) --- packages/block-editor/src/components/block-compare/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-editor/src/components/block-compare/style.scss b/packages/block-editor/src/components/block-compare/style.scss index f520ee566097bb..f1be4723ce4d69 100644 --- a/packages/block-editor/src/components/block-compare/style.scss +++ b/packages/block-editor/src/components/block-compare/style.scss @@ -32,6 +32,7 @@ .block-editor-block-compare__converted { border-left: 1px solid #ddd; padding-left: 15px; + padding-right: 0; } .block-editor-block-compare__html { From fea9b4b8d7d0737c2a2fcea4707ec40bb0209b48 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Thu, 1 Aug 2019 11:08:39 -0400 Subject: [PATCH 584/664] Editor: Move auto-draft status change to the server. (#16814) --- lib/compat.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/compat.php b/lib/compat.php index 000279832a7839..0ecae1ca55ad50 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -29,3 +29,32 @@ function gutenberg_safe_style_css_column_flex_basis( $attr ) { return $attr; } add_filter( 'safe_style_css', 'gutenberg_safe_style_css_column_flex_basis' ); + +/** + * Filters inserted post data to unset any auto-draft assigned post title. The status + * of an auto-draft should be read from its `post_status` and not inferred via its + * title. A post with an explicit title should be created with draft status, not + * with auto-draft status. It will also update an existing post's status to draft if + * currently an auto-draft. This is intended to ensure that a post which is + * explicitly updated should no longer be subject to auto-draft purge. + * + * @see https://core.trac.wordpress.org/ticket/43316#comment:88 + * @see https://core.trac.wordpress.org/ticket/43316#comment:89 + * + * @param array $data An array of slashed post data. + * @param array $postarr An array of sanitized, but otherwise unmodified post + * data. + * + * @return array Filtered post data. + */ +function gutenberg_filter_wp_insert_post_data( $data, $postarr ) { + if ( 'auto-draft' === $postarr['post_status'] ) { + if ( ! empty( $postarr['ID'] ) ) { + $data['post_status'] = 'draft'; + } else { + $data['post_title'] = ''; + } + } + return $data; +} +add_filter( 'wp_insert_post_data', 'gutenberg_filter_wp_insert_post_data', 10, 2 ); From 9169895b9ae72ee416e4ead2193d478289851d02 Mon Sep 17 00:00:00 2001 From: Matias Ventura <mv@matiasventura.com> Date: Thu, 1 Aug 2019 19:46:24 +0200 Subject: [PATCH 585/664] Add purple / violet color to default color palette. (#16833) --- packages/block-editor/src/store/defaults.js | 5 +++++ packages/block-library/src/style.scss | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index dc950c9a426deb..1f91e29c7e2f50 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -71,6 +71,11 @@ export const SETTINGS_DEFAULTS = { slug: 'vivid-cyan-blue', color: '#0693e3', }, + { + name: __( 'Vivid purple' ), + slug: 'vivid-purple', + color: '#9b51e0', + }, { name: __( 'Very light gray' ), slug: 'very-light-gray', diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 540fd4cd99ec51..fd78fcc3b18d11 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -63,6 +63,10 @@ background-color: #0693e3; } + .has-vivid-purple-background-color { + background-color: #9b51e0; + } + .has-very-light-gray-background-color { background-color: #eee; } @@ -109,6 +113,10 @@ color: #0693e3; } + .has-vivid-purple-color { + color: #9b51e0; + } + .has-very-light-gray-color { color: #eee; } From 5b01c97c6943890abb44b2154392ffcccef87b3e Mon Sep 17 00:00:00 2001 From: Andrew Duthie <andrew@andrewduthie.com> Date: Thu, 1 Aug 2019 21:46:21 -0400 Subject: [PATCH 586/664] Components: Add onFocusOutside replacement to Popover onClickOutside (#14851) * Components: Add onFocusOutside alternative to Popover onClickOutside * Components: Refactor Dropdown to use onFocusOutside * Format Library: Refactor inline link to use onFocusOutside * MenuItem: Always use an IconButton component so as to avoid focus loss. Switching between `Button` and `IconButton` causes React to remount the `MenuItem` component. This causes a focus loss as well as E2E failures. There's no need to use a `Button` for `MenuItems` that are unselected, since we can simply pass `icon={ undefined }` to the `IconButton`. --- package-lock.json | 1 + packages/components/CHANGELOG.md | 9 ++++ packages/components/package.json | 1 + .../components/src/dropdown-menu/style.scss | 3 +- packages/components/src/dropdown/index.js | 18 ++++---- packages/components/src/menu-item/index.js | 43 ++++++++---------- .../test/__snapshots__/index.js.snap | 8 ++-- packages/components/src/popover/README.md | 6 ++- .../components/src/popover/detect-outside.js | 17 +++---- packages/components/src/popover/index.js | 45 ++++++++++++++++++- .../popover/test/__snapshots__/index.js.snap | 34 +++++++------- packages/format-library/CHANGELOG.md | 6 +++ packages/format-library/src/link/inline.js | 10 ++--- 13 files changed, 127 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4cdb2340943098..04093ff7f7b4fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3830,6 +3830,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:packages/a11y", "@wordpress/compose": "file:packages/compose", + "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 26c1c2ecdaed68..1ddc0573e2be23 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -9,6 +9,15 @@ - The `Button` component will no longer assign default styling (`is-default` class) when explicitly assigned as primary (the `isPrimary` prop). This should resolve potential conflicts affecting a combination of `isPrimary`, `isDefault`, and `isLarge` / `isSmall`, where the busy animation would appear with incorrect coloring. +### Deprecations + +- The `Popover` component `onClickOutside` prop has been deprecated. Use `onFocusOutside` instead. + +### Internal + +- The `Dropdown` component has been refactored to focus changes using the `Popover` component's `onFocusOutside` prop. +- The `MenuItem` component will now always use an `IconButton`. This prevents a focus loss when clicking a menu item. + ## 8.0.0 (2019-06-12) ### New Feature diff --git a/packages/components/package.json b/packages/components/package.json index bcb29085eeb835..3a1381367b463a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -24,6 +24,7 @@ "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:../a11y", "@wordpress/compose": "file:../compose", + "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 34f58970bf9f40..23fce37ff6ebde 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -99,7 +99,8 @@ border-bottom: $border-width solid $light-gray-500; } - .components-menu-item__button { + .components-menu-item__button, + .components-menu-item__button.components-icon-button { padding-left: 2rem; &.has-icon { diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index bff463f6655f9c..e198b239e32ca2 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -14,7 +14,7 @@ class Dropdown extends Component { this.toggle = this.toggle.bind( this ); this.close = this.close.bind( this ); - this.closeIfClickOutside = this.closeIfClickOutside.bind( this ); + this.closeIfFocusOutside = this.closeIfFocusOutside.bind( this ); this.containerRef = createRef(); @@ -46,15 +46,13 @@ class Dropdown extends Component { } /** - * Closes the dropdown if a click occurs outside the dropdown wrapper. This - * is intentionally distinct from `onClose` in that a click outside the - * popover may occur in the toggling of the dropdown via its toggle button. - * The correct behavior is to keep the dropdown closed. - * - * @param {MouseEvent} event Click event triggering `onClickOutside`. + * Closes the dropdown if a focus leaves the dropdown wrapper. This is + * intentionally distinct from `onClose` since focus loss from the popover + * is expected to occur when using the Dropdown's toggle button, in which + * case the correct behavior is to keep the dropdown closed. */ - closeIfClickOutside( event ) { - if ( ! this.containerRef.current.contains( event.target ) ) { + closeIfFocusOutside() { + if ( ! this.containerRef.current.contains( document.activeElement ) ) { this.close(); } } @@ -87,7 +85,7 @@ class Dropdown extends Component { className={ contentClassName } position={ position } onClose={ this.close } - onClickOutside={ this.closeIfClickOutside } + onFocusOutside={ this.closeIfFocusOutside } expandOnMobile={ expandOnMobile } headerTitle={ headerTitle } focusOnMount={ focusOnMount } diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index de5429ddf525f8..b7cc7c8ca7a0bd 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -7,12 +7,11 @@ import { isString } from 'lodash'; /** * WordPress dependencies */ -import { createElement, cloneElement } from '@wordpress/element'; +import { cloneElement } from '@wordpress/element'; /** * Internal dependencies */ -import Button from '../button'; import Shortcut from '../shortcut'; import IconButton from '../icon-button'; @@ -47,32 +46,26 @@ export function MenuItem( { ); } - let tagName = Button; - - if ( icon ) { - if ( ! isString( icon ) ) { - icon = cloneElement( icon, { - className: 'components-menu-items__item-icon', - height: 20, - width: 20, - } ); - } - - tagName = IconButton; - props.icon = icon; + if ( icon && ! isString( icon ) ) { + icon = cloneElement( icon, { + className: 'components-menu-items__item-icon', + height: 20, + width: 20, + } ); } - return createElement( - tagName, - { + return ( + <IconButton + icon={ icon } // Make sure aria-checked matches spec https://www.w3.org/TR/wai-aria-1.1/#aria-checked - 'aria-checked': ( role === 'menuitemcheckbox' || role === 'menuitemradio' ) ? isSelected : undefined, - role, - className, - ...props, - }, - children, - <Shortcut className="components-menu-item__shortcut" shortcut={ shortcut } /> + aria-checked={ ( role === 'menuitemcheckbox' || role === 'menuitemradio' ) ? isSelected : undefined } + role={ role } + className={ className } + { ...props } + > + { children } + <Shortcut className="components-menu-item__shortcut" shortcut={ shortcut } /> + </IconButton> ); } diff --git a/packages/components/src/menu-item/test/__snapshots__/index.js.snap b/packages/components/src/menu-item/test/__snapshots__/index.js.snap index d029c29c0cb3aa..df61af88f86c88 100644 --- a/packages/components/src/menu-item/test/__snapshots__/index.js.snap +++ b/packages/components/src/menu-item/test/__snapshots__/index.js.snap @@ -17,7 +17,7 @@ exports[`MenuItem should match snapshot when all props provided 1`] = ` `; exports[`MenuItem should match snapshot when info is provided 1`] = ` -<ForwardRef(Button) +<ForwardRef(IconButton) className="components-menu-item__button" role="menuitem" > @@ -34,7 +34,7 @@ exports[`MenuItem should match snapshot when info is provided 1`] = ` <Shortcut className="components-menu-item__shortcut" /> -</ForwardRef(Button)> +</ForwardRef(IconButton)> `; exports[`MenuItem should match snapshot when isSelected and role are optionally provided 1`] = ` @@ -53,7 +53,7 @@ exports[`MenuItem should match snapshot when isSelected and role are optionally `; exports[`MenuItem should match snapshot when only label provided 1`] = ` -<ForwardRef(Button) +<ForwardRef(IconButton) className="components-menu-item__button" role="menuitem" > @@ -61,5 +61,5 @@ exports[`MenuItem should match snapshot when only label provided 1`] = ` <Shortcut className="components-menu-item__shortcut" /> -</ForwardRef(Button)> +</ForwardRef(IconButton)> `; diff --git a/packages/components/src/popover/README.md b/packages/components/src/popover/README.md index 99097e08546b55..c5877205969333 100644 --- a/packages/components/src/popover/README.md +++ b/packages/components/src/popover/README.md @@ -93,9 +93,11 @@ A callback invoked when the popover should be closed. - Type: `Function` - Required: No -### onClickOutside +### onFocusOutside -A callback invoked when the user clicks outside the opened popover, passing the click event. The popover should be closed in response to this interaction. Defaults to `onClose`. +A callback invoked when the focus leaves the opened popover. This should only be provided in advanced use-cases when a Popover should close under specific circumstances; for example, if the new `document.activeElement` is content of or otherwise controlling Popover visibility. + +Defaults to `onClose` when not provided. - Type: `Function` - Required: No diff --git a/packages/components/src/popover/detect-outside.js b/packages/components/src/popover/detect-outside.js index a890eb664fcacc..92a3359d24055f 100644 --- a/packages/components/src/popover/detect-outside.js +++ b/packages/components/src/popover/detect-outside.js @@ -1,19 +1,16 @@ /** - * External dependencies + * WordPress dependencies */ -import clickOutside from 'react-click-outside'; +import { Component } from '@wordpress/element'; /** - * WordPress dependencies + * Internal dependencies */ -import { Component } from '@wordpress/element'; +import withFocusOutside from '../higher-order/with-focus-outside'; class PopoverDetectOutside extends Component { - handleClickOutside( event ) { - const { onClickOutside } = this.props; - if ( onClickOutside ) { - onClickOutside( event ); - } + handleFocusOutside( event ) { + this.props.onFocusOutside( event ); } render() { @@ -21,4 +18,4 @@ class PopoverDetectOutside extends Component { } } -export default clickOutside( PopoverDetectOutside ); +export default withFocusOutside( PopoverDetectOutside ); diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index c303b1c75acb07..1dad7be985dfb5 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -10,6 +10,7 @@ import { useRef, useState, useEffect } from '@wordpress/element'; import { focus } from '@wordpress/dom'; import { ESCAPE } from '@wordpress/keycodes'; import isShallowEqual from '@wordpress/is-shallow-equal'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -251,7 +252,6 @@ const Popover = ( { onKeyDown, children, className, - onClickOutside = onClose, noArrow = false, // Disable reason: We generate the `...contentProps` rest as remainder // of props which aren't explicitly handled by this component. @@ -263,6 +263,8 @@ const Popover = ( { getAnchorRect, expandOnMobile, animate = true, + onClickOutside, + onFocusOutside, /* eslint-enable no-unused-vars */ ...contentProps } ) => { @@ -308,6 +310,45 @@ const Popover = ( { } }; + /** + * Shims an onFocusOutside callback to be compatible with a deprecated + * onClickOutside prop function, if provided. + * + * @param {FocusEvent} event Focus event from onFocusOutside. + */ + function handleOnFocusOutside( event ) { + // Defer to given `onFocusOutside` if specified. Call `onClose` only if + // both `onFocusOutside` and `onClickOutside` are unspecified. Doing so + // assures backwards-compatibility for prior `onClickOutside` default. + if ( onFocusOutside ) { + onFocusOutside( event ); + return; + } else if ( ! onClickOutside ) { + onClose(); + return; + } + + // Simulate MouseEvent using FocusEvent#relatedTarget as emulated click + // target. MouseEvent constructor is unsupported in Internet Explorer. + let clickEvent; + try { + clickEvent = new window.MouseEvent( 'click' ); + } catch ( error ) { + clickEvent = document.createEvent( 'MouseEvent' ); + clickEvent.initMouseEvent( 'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ); + } + + Object.defineProperty( clickEvent, 'target', { + get: () => event.relatedTarget, + } ); + + deprecated( 'Popover onClickOutside prop', { + alternative: 'onFocusOutside', + } ); + + onClickOutside( clickEvent ); + } + // Compute the animation position const yAxisMapping = { top: 'bottom', @@ -339,7 +380,7 @@ const Popover = ( { /* eslint-disable jsx-a11y/no-static-element-interactions */ let content = ( - <PopoverDetectOutside onClickOutside={ onClickOutside }> + <PopoverDetectOutside onFocusOutside={ handleOnFocusOutside }> <Animate type={ animate && isReadyToAnimate ? 'appear' : null } options={ { origin: animateYAxis + ' ' + animateXAxis } } diff --git a/packages/components/src/popover/test/__snapshots__/index.js.snap b/packages/components/src/popover/test/__snapshots__/index.js.snap index 9813681b30734e..614f2dd55e5ee5 100644 --- a/packages/components/src/popover/test/__snapshots__/index.js.snap +++ b/packages/components/src/popover/test/__snapshots__/index.js.snap @@ -6,16 +6,18 @@ exports[`Popover should pass additional props to portaled element 1`] = ` tabindex="-1" > <div> - <div - class="components-popover is-bottom is-center components-animate__appear is-from-top" - role="tooltip" - style="" - > + <div> <div - class="components-popover__content" - tabindex="-1" + class="components-popover is-bottom is-center components-animate__appear is-from-top" + role="tooltip" + style="" > - Hello + <div + class="components-popover__content" + tabindex="-1" + > + Hello + </div> </div> </div> </div> @@ -29,15 +31,17 @@ exports[`Popover should render content 1`] = ` tabindex="-1" > <div> - <div - class="components-popover is-bottom is-center components-animate__appear is-from-top" - style="" - > + <div> <div - class="components-popover__content" - tabindex="-1" + class="components-popover is-bottom is-center components-animate__appear is-from-top" + style="" > - Hello + <div + class="components-popover__content" + tabindex="-1" + > + Hello + </div> </div> </div> </div> diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md index df1eeff7c6dbf6..ebe4074c8b5242 100644 --- a/packages/format-library/CHANGELOG.md +++ b/packages/format-library/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### Internal + +- The inline link component has been refactored to focus changes using the `Popover` component's `onFocusOutside` prop. + ## 1.2.10 (2019-01-03) ## 1.2.9 (2018-12-18) diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index ac927d2bed1fb0..815a8fcaae1d3f 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -74,7 +74,7 @@ class InlineLinkUI extends Component { this.onKeyDown = this.onKeyDown.bind( this ); this.onChangeInputValue = this.onChangeInputValue.bind( this ); this.setLinkTarget = this.setLinkTarget.bind( this ); - this.onClickOutside = this.onClickOutside.bind( this ); + this.onFocusOutside = this.onFocusOutside.bind( this ); this.resetState = this.resetState.bind( this ); this.autocompleteRef = createRef(); @@ -165,13 +165,13 @@ class InlineLinkUI extends Component { } } - onClickOutside( event ) { + onFocusOutside() { // The autocomplete suggestions list renders in a separate popover (in a portal), - // so onClickOutside fails to detect that a click on a suggestion occurred in the + // so onFocusOutside fails to detect that a click on a suggestion occurred in the // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and // return to avoid the popover being closed. const autocompleteElement = this.autocompleteRef.current; - if ( autocompleteElement && autocompleteElement.contains( event.target ) ) { + if ( autocompleteElement && autocompleteElement.contains( document.activeElement ) ) { return; } @@ -198,7 +198,7 @@ class InlineLinkUI extends Component { value={ value } isActive={ isActive } addingLink={ addingLink } - onClickOutside={ this.onClickOutside } + onFocusOutside={ this.onFocusOutside } onClose={ this.resetState } focusOnMount={ showInput ? 'firstElement' : false } renderSettings={ () => ( From 2beadcdb4dc900b51f70231c31fe31e7e308f73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 2 Aug 2019 09:11:41 +0200 Subject: [PATCH 587/664] Fix paste after focus (#16857) * Fix paste after focus * Fixed type in comment --- packages/rich-text/src/component/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index d16598417e2f78..fcbb04c664d864 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -132,6 +132,7 @@ class RichText extends Component { componentWillUnmount() { document.removeEventListener( 'selectionchange', this.onSelectionChange ); + window.cancelAnimationFrame( this.rafId ); } setRef( node ) { @@ -287,7 +288,7 @@ class RichText extends Component { this.recalculateBoundaryStyle(); // We know for certain that on focus, the old selection is invalid. It - // will be recalculated on `selectionchange`. + // will be recalculated on the next animation frame. const index = undefined; const activeFormats = undefined; @@ -300,6 +301,12 @@ class RichText extends Component { this.props.onSelectionChange( index, index ); this.setState( { activeFormats } ); + // Update selection as soon as possible, which is at the next animation + // frame. The event listener for selection changes may be added too late + // at this point, but this focus event is still too early to calculate + // the selection. + this.rafId = window.requestAnimationFrame( this.onSelectionChange ); + document.addEventListener( 'selectionchange', this.onSelectionChange ); } From e73a07ac5fd0dda66ce14f54981e194b80fe6317 Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Fri, 2 Aug 2019 15:26:59 +0800 Subject: [PATCH 588/664] Improve code quality of table e2e tests (#16872) --- .../blocks/__snapshots__/table.test.js.snap | 2 +- packages/e2e-tests/specs/blocks/table.test.js | 44 +++++++------------ 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap index c70080eb6ab2fa..1a7d08c1ff86fc 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap @@ -14,7 +14,7 @@ exports[`Table allows adding and deleting columns across the table header, body exports[`Table allows cells to be selected when the cell area outside of the RichText is clicked 1`] = ` "<!-- wp:table {\\"hasFixedLayout\\":true} --> -<figure class=\\"wp-block-table\\"><table class=\\"has-fixed-layout\\"><tbody><tr><td>Some long text that will wrap onto multiple lines.</td><td>This content is in the second cell.</td></tr><tr><td></td><td></td></tr></tbody></table></figure> +<figure class=\\"wp-block-table\\"><table class=\\"has-fixed-layout\\"><tbody><tr><td><br><br><br><br></td><td>Second cell.</td></tr><tr><td></td><td></td></tr></tbody></table></figure> <!-- /wp:table -->" `; diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/blocks/table.test.js index 107e1c2f3fe85f..15267da90cb185 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/blocks/table.test.js @@ -7,13 +7,14 @@ import { capitalize } from 'lodash'; * WordPress dependencies */ import { + clickButton, clickBlockToolbarButton, createNewPost, getEditedPostContent, insertBlock, } from '@wordpress/e2e-test-utils'; -const createButtonSelector = "//div[@data-type='core/table']//button[text()='Create Table']"; +const createButtonLabel = 'Create Table'; /** * Utility function for changing the selected cell alignment. @@ -22,8 +23,7 @@ const createButtonSelector = "//div[@data-type='core/table']//button[text()='Cre */ async function changeCellAlignment( align ) { await clickBlockToolbarButton( 'Change column alignment' ); - const alignButton = await page.$x( `//button[text()='Align Column ${ capitalize( align ) }']` ); - await alignButton[ 0 ].click(); + await clickButton( `Align Column ${ capitalize( align ) }` ); } describe( 'Table', () => { @@ -57,8 +57,7 @@ describe( 'Table', () => { await page.keyboard.type( '10' ); // Create the table. - const createButton = await page.$x( createButtonSelector ); - await createButton[ 0 ].click(); + await clickButton( createButtonLabel ); // Expect the post content to have a correctly sized table. expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -68,8 +67,7 @@ describe( 'Table', () => { await insertBlock( 'Table' ); // Create the table. - const createButton = await page.$x( createButtonSelector ); - await createButton[ 0 ].click(); + await clickButton( createButtonLabel ); // Click the first cell and add some text. await page.click( '.wp-block-table__cell-content' ); @@ -104,8 +102,7 @@ describe( 'Table', () => { expect( footerSwitch ).toHaveLength( 0 ); // Create the table. - const createButton = await page.$x( createButtonSelector ); - await createButton[ 0 ].click(); + await clickButton( createButtonLabel ); // Expect the header and footer switches to be present now that the table has been created. headerSwitch = await page.$x( headerSwitchSelector ); @@ -141,8 +138,7 @@ describe( 'Table', () => { await insertBlock( 'Table' ); // Create the table. - const createButton = await page.$x( createButtonSelector ); - await createButton[ 0 ].click(); + await clickButton( createButtonLabel ); // Toggle on the switches and add some content. const headerSwitch = await page.$x( "//label[text()='Header section']" ); @@ -154,8 +150,7 @@ describe( 'Table', () => { // Add a column. await clickBlockToolbarButton( 'Edit table' ); - const addColumnAfterButton = await page.$x( "//button[text()='Add Column After']" ); - await addColumnAfterButton[ 0 ].click(); + await clickButton( 'Add Column After' ); // Expect the table to have 3 columns across the header, body and footer. expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -164,8 +159,7 @@ describe( 'Table', () => { // Delete a column. await clickBlockToolbarButton( 'Edit table' ); - const deleteColumnButton = await page.$x( "//button[text()='Delete Column']" ); - await deleteColumnButton[ 0 ].click(); + await clickButton( 'Delete Column' ); // Expect the table to have 2 columns across the header, body and footer. expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -180,8 +174,7 @@ describe( 'Table', () => { await page.keyboard.type( '4' ); // Create the table. - const [ createButton ] = await page.$x( createButtonSelector ); - await createButton.click(); + await clickButton( createButtonLabel ); // Click the first cell and add some text. Don't align. const cells = await page.$$( '.wp-block-table__cell-content' ); @@ -212,23 +205,20 @@ describe( 'Table', () => { await insertBlock( 'Table' ); // Create the table. - const createButton = await page.$x( createButtonSelector ); - await createButton[ 0 ].click(); + await clickButton( createButtonLabel ); // Enable fixed width as it exascerbates the amount of empty space around the RichText. - const fixedWidthSwitch = await page.$x( "//label[text()='Fixed width table cells']" ); - await fixedWidthSwitch[ 0 ].click(); + const [ fixedWidthSwitch ] = await page.$x( "//label[text()='Fixed width table cells']" ); + await fixedWidthSwitch.click(); - // Add lots of text to the first cell. + // Add multiple new lines to the first cell to make it taller. await page.click( '.wp-block-table__cell-content' ); - await page.keyboard.type( - `Some long text that will wrap onto multiple lines.` - ); + await page.keyboard.type( '\n\n\n\n' ); // Get the bounding client rect for the second cell. const { x: secondCellX, y: secondCellY } = await page.evaluate( () => { const secondCell = document.querySelectorAll( '.wp-block-table td' )[ 1 ]; - // Page.evaluate can only return a non-serializable value to the + // Page.evaluate can only return a serializable value to the // parent process, so destructure and restructure the result // into an object. const { x, y } = secondCell.getBoundingClientRect(); @@ -237,7 +227,7 @@ describe( 'Table', () => { // Click in the top left corner of the second cell and type some text. await page.mouse.click( secondCellX, secondCellY ); - await page.keyboard.type( 'This content is in the second cell.' ); + await page.keyboard.type( 'Second cell.' ); // Expect that the snapshot shows the text in the second cell. expect( await getEditedPostContent() ).toMatchSnapshot(); From 905ea9523add9a2519faca9843b1287a50e14837 Mon Sep 17 00:00:00 2001 From: them-es <them-es@users.noreply.github.com> Date: Fri, 2 Aug 2019 10:25:34 +0200 Subject: [PATCH 589/664] i18n: Align "Read more" string with WordPress Core (#16865) https://translate.wordpress.org/projects/wp/dev/en-gb/default/?filters%5Bterm%5D=read+more --- packages/block-library/src/latest-posts/edit.js | 2 +- packages/block-library/src/latest-posts/index.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index c2d26290746d62..0b6714c6fff087 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -220,7 +220,7 @@ class LatestPostsEdit extends Component { key="html" > { excerptLength < excerpt.trim().split( ' ' ).length ? - excerpt.trim().split( ' ', excerptLength ).join( ' ' ) + ' ... <a href=' + post.link + 'target="_blank" rel="noopener noreferrer">' + __( 'Read More' ) + '</a>' : + excerpt.trim().split( ' ', excerptLength ).join( ' ' ) + ' ... <a href=' + post.link + 'target="_blank" rel="noopener noreferrer">' + __( 'Read more' ) + '</a>' : excerpt.trim().split( ' ', excerptLength ).join( ' ' ) } </RawHTML> </div> diff --git a/packages/block-library/src/latest-posts/index.php b/packages/block-library/src/latest-posts/index.php index de5ba210fb137e..81278794f31749 100644 --- a/packages/block-library/src/latest-posts/index.php +++ b/packages/block-library/src/latest-posts/index.php @@ -67,7 +67,7 @@ function render_block_core_latest_posts( $attributes ) { $list_items_markup .= sprintf( '<a href="%1$s">%2$s</a></div>', esc_url( get_permalink( $post ) ), - __( 'Read More' ) + __( 'Read more' ) ); } else { $list_items_markup .= sprintf( From 2d369dca25ff9a2ed9a0c65dc4612ee5ac018be0 Mon Sep 17 00:00:00 2001 From: Andrea Fercia <a.fercia@gmail.com> Date: Fri, 2 Aug 2019 12:26:34 +0200 Subject: [PATCH 590/664] Use a specific CSS class for the CSS reset mixin. (#16856) * Use a specific CSS class for the CSS reset mixin. * List selectors individually instead. * Add selectors for the missing UI parts. --- assets/stylesheets/_mixins.scss | 7 ++----- packages/edit-post/src/style.scss | 9 +++++++-- packages/editor/src/components/editor-notices/style.scss | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/assets/stylesheets/_mixins.scss b/assets/stylesheets/_mixins.scss index ef15e92e46b81f..2acb8c7b74a933 100644 --- a/assets/stylesheets/_mixins.scss +++ b/assets/stylesheets/_mixins.scss @@ -394,11 +394,6 @@ box-sizing: inherit; } - select { - font-size: $default-font-size; - color: $dark-gray-500; - } - .input-control, // Upstream name is `.regular-text`. input[type="text"], input[type="search"], @@ -440,6 +435,8 @@ select { padding: 2px; + font-size: $default-font-size; + color: $dark-gray-500; &:focus { border-color: $blue-medium-600; diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index be47d4c70dc96d..c281f957b1580b 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -61,8 +61,13 @@ body.block-editor-page { @include wp-admin-reset( ".block-editor" ); } -.block-editor, -// The modals are shown outside the .block-editor wrapper, they need these styles +// Target the editor UI excluding the metaboxes and custom fields areas. +.edit-post-header, +.edit-post-visual-editor, +.edit-post-text-editor, +.edit-post-sidebar, +.editor-post-publish-panel, +.components-popover, .components-modal__frame { @include reset; } diff --git a/packages/editor/src/components/editor-notices/style.scss b/packages/editor/src/components/editor-notices/style.scss index e3387e1e653311..4b6d26f226d1dd 100644 --- a/packages/editor/src/components/editor-notices/style.scss +++ b/packages/editor/src/components/editor-notices/style.scss @@ -22,6 +22,7 @@ .components-editor-notices__dismissible, .components-editor-notices__pinned { .components-notice { + box-sizing: border-box; margin: 0 0 5px; padding: 6px 12px; min-height: $panel-header-height; From 56f060097213d205eb7edf092abbaefd42881148 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Fri, 2 Aug 2019 07:24:49 -0400 Subject: [PATCH 591/664] Try adding some additional padding to the modal component. (#16690) * Add additional padding to the modal component. * Update block manager padding to match the new modal padding. * Make ($grid-size * 3) into a variable --- assets/stylesheets/_variables.scss | 1 + packages/components/src/modal/style.scss | 6 +++--- .../src/components/manage-blocks-modal/style.scss | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/assets/stylesheets/_variables.scss b/assets/stylesheets/_variables.scss index 3532dac28d63d7..a1eb8343385d11 100644 --- a/assets/stylesheets/_variables.scss +++ b/assets/stylesheets/_variables.scss @@ -19,6 +19,7 @@ $mobile-text-min-font-size: 16px; // Any font size below 16px will cause Mobile $grid-size-small: 4px; $grid-size: 8px; $grid-size-large: 16px; +$grid-size-xlarge: 24px; // Widths, heights & dimensions $panel-padding: 16px; diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index 6ff34f69bd0ba1..7858e515fa1ef0 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -61,7 +61,7 @@ .components-modal__header { box-sizing: border-box; border-bottom: $border-width solid $light-gray-500; - padding: 0 $grid-size-large; + padding: 0 $grid-size-xlarge; display: flex; flex-direction: row; justify-content: space-between; @@ -71,7 +71,7 @@ position: sticky; top: 0; z-index: z-index(".components-modal__header"); - margin: 0 -#{ $grid-size-large } $grid-size-large; + margin: 0 -#{$grid-size-xlarge} $grid-size-xlarge; // Rules inside this query are only run by Microsoft Edge. // Edge has bugs around position: sticky;, so it needs a separate top rule. @@ -114,7 +114,7 @@ .components-modal__content { box-sizing: border-box; height: 100%; - padding: 0 $grid-size-large $grid-size-large; + padding: 0 $grid-size-xlarge $grid-size-xlarge; // Rules inside this query are only run by Microsoft Edge. // This is a companion top padding to the fixed rule in line 77. diff --git a/packages/edit-post/src/components/manage-blocks-modal/style.scss b/packages/edit-post/src/components/manage-blocks-modal/style.scss index 45569b28c6083f..8fa6693672f755 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/style.scss +++ b/packages/edit-post/src/components/manage-blocks-modal/style.scss @@ -109,9 +109,9 @@ .edit-post-manage-blocks-modal__results { height: 100%; overflow: auto; - margin-left: -1 * $grid-size-large; - margin-right: -1 * $grid-size-large; - padding-left: $grid-size-large; - padding-right: $grid-size-large; + margin-left: -$grid-size-xlarge; + margin-right: -$grid-size-xlarge; + padding-left: $grid-size-xlarge; + padding-right: $grid-size-xlarge; border-top: $border-width solid $light-gray-500; } From 2573a5a23f322903c430adbe0adb4f108a16e0d6 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Fri, 2 Aug 2019 07:51:44 -0400 Subject: [PATCH 592/664] Add extra specificity to toolbar position (#16858) So that it doesn't apply to innerblock toolbars as well. --- packages/block-editor/src/components/block-list/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 44570962df65d3..922521b6ffaff5 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -473,8 +473,9 @@ } // Align block toolbar to floated content. + // Extra specificity is needed to avoid applying this to innerblocks. @include break-mobile() { - .block-editor-block-toolbar { + > .editor-block-list__block-edit > .block-editor-block-contextual-toolbar > .block-editor-block-toolbar { /*!rtl:begin:ignore*/ left: $block-side-ui-width * 3 + ($grid-size-small * 1.5); /*!rtl:end:ignore*/ @@ -482,7 +483,7 @@ } @include break-xlarge() { - .block-editor-block-toolbar { + > .editor-block-list__block-edit > .block-editor-block-contextual-toolbar > .block-editor-block-toolbar { /*!rtl:begin:ignore*/ left: $block-padding; /*!rtl:end:ignore*/ From 2f757fa8012edb5cd684a26adef9cb4e8c2158db Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 2 Aug 2019 14:06:58 +0100 Subject: [PATCH 593/664] Add A11y Navigation Mode (#16500) --- .../developers/data/data-core-block-editor.md | 24 +++++ .../src/components/block-list/block.js | 97 +++++++++++++------ .../src/components/block-list/breadcrumb.js | 86 ++++++---------- .../src/components/block-list/style.scss | 57 +++++++++-- .../src/components/navigable-toolbar/index.js | 37 +------ .../src/components/writing-flow/index.js | 56 +++++++++-- packages/block-editor/src/store/actions.js | 13 +++ packages/block-editor/src/store/reducer.js | 17 ++++ packages/block-editor/src/store/selectors.js | 11 +++ packages/e2e-test-utils/README.md | 8 ++ .../src/click-block-toolbar-button.js | 5 +- .../e2e-test-utils/src/create-new-post.js | 3 + packages/e2e-test-utils/src/index.js | 1 + packages/e2e-test-utils/src/keyboard-mode.js | 12 +++ .../__snapshots__/writing-flow.test.js.snap | 48 ++++----- .../specs/adding-inline-tokens.test.js | 2 +- .../e2e-tests/specs/block-deletion.test.js | 15 ++- packages/e2e-tests/specs/editor-modes.test.js | 20 ++-- packages/e2e-tests/specs/links.test.js | 5 +- .../e2e-tests/specs/navigable-toolbar.test.js | 8 -- packages/e2e-tests/specs/preview.test.js | 2 + packages/e2e-tests/specs/rich-text.test.js | 6 +- packages/e2e-tests/specs/undo.test.js | 2 + packages/e2e-tests/specs/writing-flow.test.js | 2 +- 24 files changed, 348 insertions(+), 189 deletions(-) create mode 100644 packages/e2e-test-utils/src/keyboard-mode.js diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 1b42d76df3e3b4..005c474f570f64 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -761,6 +761,18 @@ _Returns_ - `boolean`: True if multi-selecting, false if not. +<a name="isNavigationMode" href="#isNavigationMode">#</a> **isNavigationMode** + +Returns whether the navigation mode is enabled. + +_Parameters_ + +- _state_ `Object`: Editor state. + +_Returns_ + +- `boolean`: Is navigation mode enabled. + <a name="isSelectionEnabled" href="#isSelectionEnabled">#</a> **isSelectionEnabled** Selector that returns if multi-selection is enabled or not. @@ -1071,6 +1083,18 @@ _Parameters_ - _clientId_ `string`: Block client ID. +<a name="setNavigationMode" href="#setNavigationMode">#</a> **setNavigationMode** + +Returns an action object used to enable or disable the navigation mode. + +_Parameters_ + +- _isNavigationMode_ `string`: Enable/Disable navigation mode. + +_Returns_ + +- `Object`: Action object + <a name="setTemplateValidity" href="#setTemplateValidity">#</a> **setTemplateValidity** Returns an action object resetting the template validity. diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 7d5569502b4569..55495602436a5f 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -8,13 +8,13 @@ import { animated } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { useRef, useEffect, useState } from '@wordpress/element'; +import { useRef, useEffect, useLayoutEffect, useState } from '@wordpress/element'; import { focus, isTextField, placeCaretAtHorizontalEdge, } from '@wordpress/dom'; -import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; +import { BACKSPACE, DELETE, ENTER, ESCAPE } from '@wordpress/keycodes'; import { getBlockType, getSaveElement, @@ -101,6 +101,8 @@ function BlockListBlock( { onSelectionStart, animateOnChange, enableAnimation, + isNavigationMode, + enableNavigationMode, } ) { // Random state used to rerender the component if needed, ideally we don't need this const [ , updateRerenderState ] = useState( {} ); @@ -118,6 +120,8 @@ function BlockListBlock( { // Hovered area of the block const hoverArea = useHoveredArea( wrapper ); + const breadcrumb = useRef(); + // Keep track of touchstart to disable hover on iOS const hadTouchStart = useRef( false ); const onTouchStart = () => { @@ -215,6 +219,11 @@ function BlockListBlock( { return; } + if ( isNavigationMode ) { + breadcrumb.current.focus(); + return; + } + // Find all tabbables within node. const textInputs = focus.tabbable .find( blockNodeRef.current ) @@ -254,6 +263,18 @@ function BlockListBlock( { // Block Reordering animation const animationStyle = useMovingAnimation( wrapper, isSelected || isPartOfMultiSelection, enableAnimation, animateOnChange ); + // Focus the breadcrumb if the wrapper is focused on navigation mode. + // Focus the first editable or the wrapper if edit mode. + useLayoutEffect( () => { + if ( isSelected ) { + if ( isNavigationMode ) { + breadcrumb.current.focus(); + } else { + focusTabbable( true ); + } + } + }, [ isSelected, isNavigationMode ] ); + // Other event handlers /** @@ -275,32 +296,43 @@ function BlockListBlock( { * * @param {KeyboardEvent} event Keydown event. */ - const deleteOrInsertAfterWrapper = ( event ) => { + const onKeyDown = ( event ) => { const { keyCode, target } = event; - // These block shortcuts should only trigger if the wrapper of the block is selected - // And when it's not a multi-selection to avoid conflicting with RichText/Inputs and multiselection. - if ( - ! isSelected || - target !== wrapper.current || - isLocked - ) { - return; - } + // ENTER/BACKSPACE Shortcuts are only available if the wrapper is focused + // and the block is not locked. + const canUseShortcuts = ( + isSelected && + ! isLocked && + ( target === wrapper.current || target === breadcrumb.current ) + ); + const isEditMode = ! isNavigationMode; switch ( keyCode ) { case ENTER: - // Insert default block after current block if enter and event - // not already handled by descendant. - onInsertDefaultBlockAfter(); - event.preventDefault(); + if ( canUseShortcuts && isEditMode ) { + // Insert default block after current block if enter and event + // not already handled by descendant. + onInsertDefaultBlockAfter(); + event.preventDefault(); + } break; - case BACKSPACE: case DELETE: - // Remove block on backspace. - onRemove( clientId ); - event.preventDefault(); + if ( canUseShortcuts ) { + // Remove block on backspace. + onRemove( clientId ); + event.preventDefault(); + } + break; + case ESCAPE: + if ( + isSelected && + isEditMode + ) { + enableNavigationMode(); + wrapper.current.focus(); + } break; } }; @@ -357,8 +389,8 @@ function BlockListBlock( { // If the block is selected and we're typing the block should not appear. // Empty paragraph blocks should always show up as unselected. - const showInserterShortcuts = ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; - const showEmptyBlockSideInserter = ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid; + const showInserterShortcuts = ! isNavigationMode && ( isSelected || isHovered ) && isEmptyDefaultBlock && isValid; + const showEmptyBlockSideInserter = ! isNavigationMode && ( isSelected || isHovered || isLast ) && isEmptyDefaultBlock && isValid; const shouldAppearSelected = ! isFocusMode && ! showEmptyBlockSideInserter && @@ -371,20 +403,23 @@ function BlockListBlock( { ! isEmptyDefaultBlock; // We render block movers and block settings to keep them tabbale even if hidden const shouldRenderMovers = + ! isNavigationMode && ( isSelected || hoverArea === ( isRTL ? 'right' : 'left' ) ) && ! showEmptyBlockSideInserter && ! isPartOfMultiSelection && ! isTypingWithinBlock; const shouldShowBreadcrumb = - ! isFocusMode && isHovered && ! isEmptyDefaultBlock; + ( isSelected && isNavigationMode ) || + ( ! isNavigationMode && ! isFocusMode && isHovered && ! isEmptyDefaultBlock ); const shouldShowContextualToolbar = + ! isNavigationMode && ! hasFixedToolbar && ! showEmptyBlockSideInserter && ( ( isSelected && ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || isFirstMultiSelected ); - const shouldShowMobileToolbar = shouldAppearSelected; + const shouldShowMobileToolbar = ! isNavigationMode && shouldAppearSelected; // Insertion point can only be made visible if the block is at the // the extent of a multi-selection, or not in a multi-selection. @@ -399,6 +434,7 @@ function BlockListBlock( { { 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, 'is-selected': shouldAppearSelected, + 'is-navigate-mode': isNavigationMode, 'is-multi-selected': isPartOfMultiSelection, 'is-hovered': shouldAppearHovered, 'is-reusable': isReusableBlock( blockType ), @@ -464,7 +500,7 @@ function BlockListBlock( { onTouchStart={ onTouchStart } onFocus={ onFocus } onClick={ onTouchStop } - onKeyDown={ deleteOrInsertAfterWrapper } + onKeyDown={ onKeyDown } tabIndex="0" aria-label={ blockLabel } childHandledEvents={ [ 'onDragStart', 'onMouseDown' ] } @@ -509,9 +545,7 @@ function BlockListBlock( { { shouldShowBreadcrumb && ( <BlockBreadcrumb clientId={ clientId } - isHidden={ - ! ( isHovered || isSelected ) || hoverArea !== ( isRTL ? 'right' : 'left' ) - } + ref={ breadcrumb } /> ) } { ( shouldShowContextualToolbar || isForcingContextualToolbar.current ) && ( @@ -522,6 +556,7 @@ function BlockListBlock( { /> ) } { + ! isNavigationMode && ! shouldShowContextualToolbar && isSelected && ! hasFixedToolbar && @@ -604,6 +639,7 @@ const applyWithSelect = withSelect( getBlockIndex, getBlockOrder, __unstableGetBlockWithoutInnerBlocks, + isNavigationMode, } = select( 'core/block-editor' ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const isSelected = isBlockSelected( clientId ); @@ -637,6 +673,7 @@ const applyWithSelect = withSelect( isFocusMode: focusMode && isLargeViewport, hasFixedToolbar: hasFixedToolbar && isLargeViewport, isLast: index === blockOrder.length - 1, + isNavigationMode: isNavigationMode(), isRTL, // Users of the editor.BlockListBlock filter used to be able to access the block prop @@ -664,6 +701,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks, replaceBlocks, toggleSelection, + setNavigationMode, } = dispatch( 'core/block-editor' ); return { @@ -737,6 +775,9 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { toggleSelection( selectionEnabled ) { toggleSelection( selectionEnabled ); }, + enableNavigationMode() { + setNavigationMode( true ); + }, }; } ); diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index a0eb3a385407c6..81f05b40bf7080 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -1,10 +1,9 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { Toolbar } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { Toolbar, Button } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { forwardRef } from '@wordpress/element'; /** * Internal dependencies @@ -17,62 +16,31 @@ import BlockTitle from '../block-title'; * the root block. * * @param {string} props.clientId Client ID of block. - * @param {string} props.rootClientId Client ID of block's root. - * @param {Function} props.selectRootBlock Callback to select root block. + * @return {WPElement} Block Breadcrumb. */ -export class BlockBreadcrumb extends Component { - constructor() { - super( ...arguments ); - this.state = { - isFocused: false, +const BlockBreadcrumb = forwardRef( ( { clientId }, ref ) => { + const { setNavigationMode } = useDispatch( 'core/block-editor' ); + const { rootClientId } = useSelect( ( select ) => { + return { + rootClientId: select( 'core/block-editor' ).getBlockRootClientId( clientId ), }; - this.onFocus = this.onFocus.bind( this ); - this.onBlur = this.onBlur.bind( this ); - } - - onFocus( event ) { - this.setState( { - isFocused: true, - } ); - - // This is used for improved interoperability - // with the block's `onFocus` handler which selects the block, thus conflicting - // with the intention to select the root block. - event.stopPropagation(); - } - - onBlur() { - this.setState( { - isFocused: false, - } ); - } - - render() { - const { clientId, rootClientId } = this.props; - - return ( - <div className={ 'editor-block-list__breadcrumb block-editor-block-list__breadcrumb' }> - <Toolbar> - { rootClientId && ( - <> - <BlockTitle clientId={ rootClientId } /> - <span className="editor-block-list__descendant-arrow block-editor-block-list__descendant-arrow" /> - </> - ) } + } ); + + return ( + <div className="editor-block-list__breadcrumb block-editor-block-list__breadcrumb"> + <Toolbar> + { rootClientId && ( + <> + <BlockTitle clientId={ rootClientId } /> + <span className="editor-block-list__descendant-arrow block-editor-block-list__descendant-arrow" /> + </> + ) } + <Button ref={ ref } onClick={ () => setNavigationMode( false ) }> <BlockTitle clientId={ clientId } /> - </Toolbar> - </div> - ); - } -} - -export default compose( [ - withSelect( ( select, ownProps ) => { - const { getBlockRootClientId } = select( 'core/block-editor' ); - const { clientId } = ownProps; + </Button> + </Toolbar> + </div> + ); +} ); - return { - rootClientId: getBlockRootClientId( clientId ), - }; - } ), -] )( BlockBreadcrumb ); +export default BlockBreadcrumb; diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 922521b6ffaff5..5a3410d7230c47 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -146,10 +146,19 @@ } } } + + &.is-navigate-mode > .block-editor-block-list__block-edit::before { + border-color: $blue-medium-focus; + box-shadow: inset $block-left-border-width 0 0 0 $blue-medium-focus; + + @include break-small() { + box-shadow: -$block-left-border-width 0 0 0 $blue-medium-focus; + } + } } // Hover style. - &.is-hovered > .block-editor-block-list__block-edit::before { + &.is-hovered:not(.is-navigate-mode) > .block-editor-block-list__block-edit::before { box-shadow: -$block-left-border-width 0 0 0 $dark-opacity-light-500; .is-dark-theme & { @@ -1063,17 +1072,19 @@ padding: 4px 4px; background: $light-gray-500; color: $dark-gray-900; + transition: box-shadow 0.1s linear; + @include reduce-motion("transition"); + + .components-button { + font-size: inherit; + line-height: inherit; + padding: 0; + } .is-dark-theme & { background: $dark-gray-600; color: $white; } - - // Animate in - .block-editor-block-list__block:hover & { - opacity: 0; - @include edit-post__fade-in-animation(60ms, 0.5s); - } } // Remove negative left breadcrumb position for left aligned blocks. @@ -1086,6 +1097,38 @@ left: auto; right: 0; } + + // In navigation mode, this should appear similarly to the block toolbar. + .is-navigate-mode & { + + // Position in the top left of the border. + left: -$block-padding; + top: -$block-toolbar-height - $block-padding; + + .components-toolbar { + background: $white; + border: $border-width solid $blue-medium-focus; + border-left: none; + box-shadow: inset $block-left-border-width 0 0 0 $blue-medium-focus; + height: $block-toolbar-height + $border-width; + font-size: $default-font-size; + line-height: $block-toolbar-height - $grid-size; + padding-left: $grid-size; + padding-right: $grid-size; + + .components-button { + box-shadow: none; + } + + .is-dark-theme & { + border-color: $light-opacity-light-800; + } + + @include break-small() { + box-shadow: -$block-left-border-width 0 0 0 $blue-medium-focus; + } + } + } } .block-editor-block-list__descendant-arrow::before { diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 21c6b5dd841e44..26984e217f3d07 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { cond, matchesProperty, omit } from 'lodash'; +import { omit } from 'lodash'; /** * WordPress dependencies @@ -9,24 +9,13 @@ import { cond, matchesProperty, omit } from 'lodash'; import { NavigableMenu, KeyboardShortcuts } from '@wordpress/components'; import { Component, createRef } from '@wordpress/element'; import { focus } from '@wordpress/dom'; -import { ESCAPE } from '@wordpress/keycodes'; - -/** - * Browser dependencies - */ - -const { Node, getSelection } = window; class NavigableToolbar extends Component { constructor() { super( ...arguments ); this.focusToolbar = this.focusToolbar.bind( this ); - this.focusSelection = this.focusSelection.bind( this ); - this.switchOnKeyDown = cond( [ - [ matchesProperty( [ 'keyCode' ], ESCAPE ), this.focusSelection ], - ] ); this.toolbar = createRef(); } @@ -37,30 +26,6 @@ class NavigableToolbar extends Component { } } - /** - * Programmatically shifts focus to the element where the current selection - * exists, if there is a selection. - */ - focusSelection() { - // Ensure that a selection exists. - const selection = getSelection(); - if ( ! selection ) { - return; - } - - // Focus node may be a text node, which cannot be focused directly. - // Find its parent element instead. - const { focusNode } = selection; - let focusElement = focusNode; - if ( focusElement.nodeType !== Node.ELEMENT_NODE ) { - focusElement = focusElement.parentElement; - } - - if ( focusElement ) { - focusElement.focus(); - } - } - componentDidMount() { if ( this.props.focusOnMount ) { this.focusToolbar(); diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index f4c4844f5bf722..cbc531ef754a85 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -6,7 +6,7 @@ import { overEvery, find, findLast, reverse, first, last } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; import { computeCaretRect, focus, @@ -17,7 +17,7 @@ import { placeCaretAtVerticalEdge, isEntirelySelected, } from '@wordpress/dom'; -import { UP, DOWN, LEFT, RIGHT, isKeyboardEvent } from '@wordpress/keycodes'; +import { UP, DOWN, LEFT, RIGHT, TAB, isKeyboardEvent } from '@wordpress/keycodes'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -78,7 +78,7 @@ class WritingFlow extends Component { this.onKeyDown = this.onKeyDown.bind( this ); this.bindContainer = this.bindContainer.bind( this ); - this.clearVerticalRect = this.clearVerticalRect.bind( this ); + this.onMouseDown = this.onMouseDown.bind( this ); this.focusLastTextField = this.focusLastTextField.bind( this ); /** @@ -89,14 +89,28 @@ class WritingFlow extends Component { * @type {?DOMRect} */ this.verticalRect = null; + + /** + * Reference of the writing flow appender element. + * The reference is used to focus the first tabbable element after the block list + * once we hit `tab` on the last block in navigation mode. + */ + this.appender = createRef(); } bindContainer( ref ) { this.container = ref; } - clearVerticalRect() { + onMouseDown() { this.verticalRect = null; + this.disableNavigationMode(); + } + + disableNavigationMode() { + if ( this.props.isNavigationMode ) { + this.props.disableNavigationMode(); + } } /** @@ -224,8 +238,10 @@ class WritingFlow extends Component { hasMultiSelection, onMultiSelect, blocks, + selectedBlockClientId, selectionBeforeEndClientId, selectionAfterEndClientId, + isNavigationMode, } = this.props; const { keyCode, target } = event; @@ -233,6 +249,7 @@ class WritingFlow extends Component { const isDown = keyCode === DOWN; const isLeft = keyCode === LEFT; const isRight = keyCode === RIGHT; + const isTab = keyCode === TAB; const isReverse = isUp || isLeft; const isHorizontal = isLeft || isRight; const isVertical = isUp || isDown; @@ -241,6 +258,28 @@ class WritingFlow extends Component { const hasModifier = isShift || event.ctrlKey || event.altKey || event.metaKey; const isNavEdge = isVertical ? isVerticalEdge : isHorizontalEdge; + // In navigation mode, tab and arrows navigate from block to block. + if ( isNavigationMode ) { + const navigateUp = ( isTab && isShift ) || isUp; + const navigateDown = ( isTab && ! isShift ) || isDown; + const focusedBlockUid = navigateUp ? selectionBeforeEndClientId : selectionAfterEndClientId; + + if ( + ( navigateDown || navigateUp ) && + focusedBlockUid + ) { + event.preventDefault(); + this.props.onSelectBlock( focusedBlockUid ); + } + + // Special case when reaching the end of the blocks (navigate to the next tabbable outside of the writing flow) + if ( navigateDown && selectedBlockClientId && ! selectionAfterEndClientId && [ UP, DOWN ].indexOf( keyCode ) === -1 ) { + this.props.clearSelectedBlock(); + this.appender.current.focus(); + } + return; + } + // When presing any key other than up or down, the initial vertical // position must ALWAYS be reset. The vertical position is saved so it // can be restored as well as possible on sebsequent vertical arrow key @@ -356,11 +395,12 @@ class WritingFlow extends Component { <div ref={ this.bindContainer } onKeyDown={ this.onKeyDown } - onMouseDown={ this.clearVerticalRect } + onMouseDown={ this.onMouseDown } > { children } </div> <div + ref={ this.appender } aria-hidden tabIndex={ -1 } onClick={ this.focusLastTextField } @@ -384,6 +424,7 @@ export default compose( [ getLastMultiSelectedBlockClientId, hasMultiSelection, getBlockOrder, + isNavigationMode, } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); @@ -399,13 +440,16 @@ export default compose( [ selectedLastClientId: getLastMultiSelectedBlockClientId(), hasMultiSelection: hasMultiSelection(), blocks: getBlockOrder(), + isNavigationMode: isNavigationMode(), }; } ), withDispatch( ( dispatch ) => { - const { multiSelect, selectBlock } = dispatch( 'core/block-editor' ); + const { multiSelect, selectBlock, setNavigationMode, clearSelectedBlock } = dispatch( 'core/block-editor' ); return { onMultiSelect: multiSelect, onSelectBlock: selectBlock, + disableNavigationMode: () => setNavigationMode( false ), + clearSelectedBlock, }; } ), ] )( WritingFlow ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 4dafaa291fcf31..c9d02e0b200a24 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -705,3 +705,16 @@ export function __unstableMarkLastChangeAsPersistent() { return { type: 'MARK_LAST_CHANGE_AS_PERSISTENT' }; } +/** + * Returns an action object used to enable or disable the navigation mode. + * + * @param {string} isNavigationMode Enable/Disable navigation mode. + * + * @return {Object} Action object + */ +export function setNavigationMode( isNavigationMode = true ) { + return { + type: 'SET_NAVIGATION_MODE', + isNavigationMode, + }; +} diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index df81bd5df22f0b..3ef595d48c9a2d 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1207,6 +1207,22 @@ export const blockListSettings = ( state = {}, action ) => { return state; }; +/** + * Reducer returning whether the navigation mode is enabled or not. + * + * @param {string} state Current state. + * @param {Object} action Dispatched action. + * + * @return {string} Updated state. + */ +export function isNavigationMode( state = true, action ) { + if ( action.type === 'SET_NAVIGATION_MODE' ) { + return action.isNavigationMode; + } + + return state; +} + /** * Reducer return an updated state representing the most recent block attribute * update. The state is structured as an object where the keys represent the @@ -1247,4 +1263,5 @@ export default combineReducers( { settings, preferences, lastBlockAttributesChange, + isNavigationMode, } ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 5e8771ff94935b..9fbc0c68e01629 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1405,3 +1405,14 @@ export function __experimentalGetLastBlockAttributeChanges( state ) { function getReusableBlocks( state ) { return get( state, [ 'settings', '__experimentalReusableBlocks' ], EMPTY_ARRAY ); } + +/** + * Returns whether the navigation mode is enabled. + * + * @param {Object} state Editor state. + * + * @return {boolean} Is navigation mode enabled. + */ +export function isNavigationMode( state ) { + return state.isNavigationMode; +} diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index d8f14e5e6c343b..d932adca1f1ad8 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -137,6 +137,14 @@ _Parameters_ - _slug_ `string`: Plugin slug. +<a name="disableNavigationMode" href="#disableNavigationMode">#</a> **disableNavigationMode** + +Triggers edit mode if not already active. + +_Returns_ + +- `Promise`: Promise resolving after enabling the keyboard edit mode. + <a name="disablePrePublishChecks" href="#disablePrePublishChecks">#</a> **disablePrePublishChecks** Disables Pre-publish checks. diff --git a/packages/e2e-test-utils/src/click-block-toolbar-button.js b/packages/e2e-test-utils/src/click-block-toolbar-button.js index 679727189f7414..fcdd9d2af91ede 100644 --- a/packages/e2e-test-utils/src/click-block-toolbar-button.js +++ b/packages/e2e-test-utils/src/click-block-toolbar-button.js @@ -7,8 +7,9 @@ export async function clickBlockToolbarButton( buttonAriaLabel ) { const BLOCK_TOOLBAR_SELECTOR = '.block-editor-block-toolbar'; const BUTTON_SELECTOR = `${ BLOCK_TOOLBAR_SELECTOR } button[aria-label="${ buttonAriaLabel }"]`; if ( await page.$( BLOCK_TOOLBAR_SELECTOR ) === null ) { - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); } await page.waitForSelector( BUTTON_SELECTOR ); await page.click( BUTTON_SELECTOR ); diff --git a/packages/e2e-test-utils/src/create-new-post.js b/packages/e2e-test-utils/src/create-new-post.js index 91a95bf5216a45..ddc2ad64ea6c1b 100644 --- a/packages/e2e-test-utils/src/create-new-post.js +++ b/packages/e2e-test-utils/src/create-new-post.js @@ -7,6 +7,7 @@ import { addQueryArgs } from '@wordpress/url'; * Internal dependencies */ import { visitAdminPage } from './visit-admin-page'; +import { disableNavigationMode } from './keyboard-mode'; /** * Creates new post. @@ -36,4 +37,6 @@ export async function createNewPost( { if ( enableTips ) { await page.reload(); } + + await disableNavigationMode(); } diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 64e0a22a585b1e..c6839ac84f1d55 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -42,6 +42,7 @@ export { selectBlockByClientId } from './select-block-by-client-id'; export { setBrowserViewport } from './set-browser-viewport'; export { setPostContent } from './set-post-content'; export { switchEditorModeTo } from './switch-editor-mode-to'; +export { disableNavigationMode } from './keyboard-mode'; export { switchUserToAdmin } from './switch-user-to-admin'; export { switchUserToTest } from './switch-user-to-test'; export { toggleScreenOption } from './toggle-screen-option'; diff --git a/packages/e2e-test-utils/src/keyboard-mode.js b/packages/e2e-test-utils/src/keyboard-mode.js new file mode 100644 index 00000000000000..8928c220b28f63 --- /dev/null +++ b/packages/e2e-test-utils/src/keyboard-mode.js @@ -0,0 +1,12 @@ +/** + * Triggers edit mode if not already active. + * + * @return {Promise} Promise resolving after enabling the keyboard edit mode. + */ +export async function disableNavigationMode() { + const focusedElement = await page.$( ':focus' ); + await page.click( '.editor-post-title' ); + if ( focusedElement ) { + await focusedElement.focus(); + } +} diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 8ebcdd6670e00c..42f1ce81d3b6e9 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = ` +exports[`Writing Flow Should navigate inner blocks with arrow keys 1`] = ` "<!-- wp:paragraph --> <p>First paragraph</p> <!-- /wp:paragraph --> @@ -24,7 +24,7 @@ exports[`adding blocks Should navigate inner blocks with arrow keys 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should create valid paragraph blocks when rapidly pressing Enter 1`] = ` +exports[`Writing Flow should create valid paragraph blocks when rapidly pressing Enter 1`] = ` "<!-- wp:paragraph --> <p></p> <!-- /wp:paragraph --> @@ -70,43 +70,43 @@ exports[`adding blocks should create valid paragraph blocks when rapidly pressin <!-- /wp:paragraph -->" `; -exports[`adding blocks should insert line break at end 1`] = ` +exports[`Writing Flow should insert line break at end 1`] = ` "<!-- wp:paragraph --> <p>a<br></p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should insert line break at end and continue writing 1`] = ` +exports[`Writing Flow should insert line break at end and continue writing 1`] = ` "<!-- wp:paragraph --> <p>a<br>b</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should insert line break at start 1`] = ` +exports[`Writing Flow should insert line break at start 1`] = ` "<!-- wp:paragraph --> <p><br>a</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should insert line break in empty container 1`] = ` +exports[`Writing Flow should insert line break in empty container 1`] = ` "<!-- wp:paragraph --> <p><br></p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should insert line break mid text 1`] = ` +exports[`Writing Flow should insert line break mid text 1`] = ` "<!-- wp:paragraph --> <p>a<br>b</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should merge forwards 1`] = ` +exports[`Writing Flow should merge forwards 1`] = ` "<!-- wp:paragraph --> <p>123</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should navigate around inline boundaries 1`] = ` +exports[`Writing Flow should navigate around inline boundaries 1`] = ` "<!-- wp:paragraph --> <p>FirstAfter</p> <!-- /wp:paragraph --> @@ -120,19 +120,19 @@ exports[`adding blocks should navigate around inline boundaries 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should navigate around nested inline boundaries 1`] = ` +exports[`Writing Flow should navigate around nested inline boundaries 1`] = ` "<!-- wp:paragraph --> <p><strong><em>1</em> <em>2</em></strong></p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should navigate around nested inline boundaries 2`] = ` +exports[`Writing Flow should navigate around nested inline boundaries 2`] = ` "<!-- wp:paragraph --> <p>a<strong>b<em>c1d</em>e f<em>g2h</em>i</strong>j</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should navigate contenteditable with padding 1`] = ` +exports[`Writing Flow should navigate contenteditable with padding 1`] = ` "<!-- wp:paragraph --> <p>1</p> <!-- /wp:paragraph --> @@ -142,7 +142,7 @@ exports[`adding blocks should navigate contenteditable with padding 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should navigate contenteditable with side padding 1`] = ` +exports[`Writing Flow should navigate contenteditable with side padding 1`] = ` "<!-- wp:paragraph --> <p>1</p> <!-- /wp:paragraph --> @@ -156,7 +156,7 @@ exports[`adding blocks should navigate contenteditable with side padding 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should navigate empty paragraph 1`] = ` +exports[`Writing Flow should navigate empty paragraph 1`] = ` "<!-- wp:paragraph --> <p>1</p> <!-- /wp:paragraph --> @@ -166,49 +166,49 @@ exports[`adding blocks should navigate empty paragraph 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should not create extra line breaks in multiline value 1`] = ` +exports[`Writing Flow should not create extra line breaks in multiline value 1`] = ` "<!-- wp:quote --> <blockquote class=\\"wp-block-quote\\"><p></p></blockquote> <!-- /wp:quote -->" `; -exports[`adding blocks should not delete surrounding space when deleting a selected word 1`] = ` +exports[`Writing Flow should not delete surrounding space when deleting a selected word 1`] = ` "<!-- wp:paragraph --> <p>alpha gamma</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should not delete surrounding space when deleting a selected word 2`] = ` +exports[`Writing Flow should not delete surrounding space when deleting a selected word 2`] = ` "<!-- wp:paragraph --> <p>alpha beta gamma</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should not delete surrounding space when deleting a word with Alt+Backspace 1`] = ` +exports[`Writing Flow should not delete surrounding space when deleting a word with Alt+Backspace 1`] = ` "<!-- wp:paragraph --> <p>alpha gamma</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should not delete surrounding space when deleting a word with Alt+Backspace 2`] = ` +exports[`Writing Flow should not delete surrounding space when deleting a word with Alt+Backspace 2`] = ` "<!-- wp:paragraph --> <p>alpha beta gamma</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should not delete surrounding space when deleting a word with Backspace 1`] = ` +exports[`Writing Flow should not delete surrounding space when deleting a word with Backspace 1`] = ` "<!-- wp:paragraph --> <p>1 3</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should not delete surrounding space when deleting a word with Backspace 2`] = ` +exports[`Writing Flow should not delete surrounding space when deleting a word with Backspace 2`] = ` "<!-- wp:paragraph --> <p>1 2 3</p> <!-- /wp:paragraph -->" `; -exports[`adding blocks should not prematurely multi-select 1`] = ` +exports[`Writing Flow should not prematurely multi-select 1`] = ` "<!-- wp:paragraph --> <p>1</p> <!-- /wp:paragraph --> @@ -218,7 +218,7 @@ exports[`adding blocks should not prematurely multi-select 1`] = ` <!-- /wp:paragraph -->" `; -exports[`adding blocks should preserve horizontal position when navigating vertically between blocks 1`] = ` +exports[`Writing Flow should preserve horizontal position when navigating vertically between blocks 1`] = ` "<!-- wp:paragraph --> <p>abc</p> <!-- /wp:paragraph --> @@ -228,7 +228,7 @@ exports[`adding blocks should preserve horizontal position when navigating verti <!-- /wp:paragraph -->" `; -exports[`adding blocks should remember initial vertical position 1`] = ` +exports[`Writing Flow should remember initial vertical position 1`] = ` "<!-- wp:paragraph --> <p>1x</p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/specs/adding-inline-tokens.test.js b/packages/e2e-tests/specs/adding-inline-tokens.test.js index 5bcd55498489c1..387c8e63ab6cb1 100644 --- a/packages/e2e-tests/specs/adding-inline-tokens.test.js +++ b/packages/e2e-tests/specs/adding-inline-tokens.test.js @@ -18,7 +18,7 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'adding inline tokens', () => { - beforeAll( async () => { + beforeEach( async () => { await createNewPost(); } ); diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/block-deletion.test.js index 4466c03783f290..a231977ed171a6 100644 --- a/packages/e2e-tests/specs/block-deletion.test.js +++ b/packages/e2e-tests/specs/block-deletion.test.js @@ -64,8 +64,9 @@ describe( 'block deletion -', () => { // The blocks can't be empty to trigger the toolbar await page.keyboard.type( 'Paragraph to remove' ); - // Press Escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); await clickOnBlockSettingsMenuRemoveBlockButton(); @@ -143,12 +144,17 @@ describe( 'block deletion -', () => { } ); describe( 'deleting all blocks', () => { - it( 'results in the default block getting selected', async () => { + beforeEach( async () => { await createNewPost(); + } ); + + it( 'results in the default block getting selected', async () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph' ); - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); await clickOnBlockSettingsMenuRemoveBlockButton(); @@ -168,7 +174,6 @@ describe( 'deleting all blocks', () => { // // See: https://github.com/WordPress/gutenberg/issues/15458 // See: https://github.com/WordPress/gutenberg/pull/15543 - await createNewPost(); // Unregister default block type. This may happen if the editor is // configured to not allow the default (paragraph) block type, either diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor-modes.test.js index 4096338ce9f64b..9fa866e893e9fd 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor-modes.test.js @@ -20,8 +20,9 @@ describe( 'Editing modes (visual/HTML)', () => { let visualBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-rich-text' ); expect( visualBlock ).toHaveLength( 1 ); - // Press Escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); @@ -32,8 +33,9 @@ describe( 'Editing modes (visual/HTML)', () => { const htmlBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' ); expect( htmlBlock ).toHaveLength( 1 ); - // Press Escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); // Change editing mode from "HTML" back to "Visual". await clickBlockToolbarButton( 'More options' ); @@ -46,8 +48,9 @@ describe( 'Editing modes (visual/HTML)', () => { } ); it( 'should display sidebar in HTML mode', async () => { - // Press Escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); @@ -61,8 +64,9 @@ describe( 'Editing modes (visual/HTML)', () => { } ); it( 'should update HTML in HTML mode when sidebar is used', async () => { - // Press Escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js index a1d1dd3a46934b..d55ec0a02e9b06 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/links.test.js @@ -238,8 +238,9 @@ describe( 'Links', () => { // Make a collapsed selection inside the link await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.press( 'ArrowRight' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); await page.click( 'button[aria-label="Edit"]' ); await waitForAutoFocus(); await page.keyboard.type( '/handbook' ); diff --git a/packages/e2e-tests/specs/navigable-toolbar.test.js b/packages/e2e-tests/specs/navigable-toolbar.test.js index e3fb8841850081..1936fc5064caa1 100644 --- a/packages/e2e-tests/specs/navigable-toolbar.test.js +++ b/packages/e2e-tests/specs/navigable-toolbar.test.js @@ -25,10 +25,6 @@ describe( 'block toolbar', () => { }, isUnifiedToolbar ); } ); - const isInRichTextEditable = () => page.evaluate( () => ( - document.activeElement.contentEditable === 'true' - ) ); - const isInBlockToolbar = () => page.evaluate( () => ( !! document.activeElement.closest( '.block-editor-block-toolbar' ) ) ); @@ -46,10 +42,6 @@ describe( 'block toolbar', () => { // Upward await pressKeyWithModifier( 'alt', 'F10' ); expect( await isInBlockToolbar() ).toBe( true ); - - // Downward - await page.keyboard.press( 'Escape' ); - expect( await isInRichTextEditable() ).toBe( true ); } ); } ); } ); diff --git a/packages/e2e-tests/specs/preview.test.js b/packages/e2e-tests/specs/preview.test.js index 5a1d7f4cc2754a..255f6200d116ad 100644 --- a/packages/e2e-tests/specs/preview.test.js +++ b/packages/e2e-tests/specs/preview.test.js @@ -14,6 +14,7 @@ import { saveDraft, clickOnMoreMenuItem, pressKeyWithModifier, + disableNavigationMode, } from '@wordpress/e2e-test-utils'; async function openPreviewPage( editorPage ) { @@ -203,6 +204,7 @@ describe( 'Preview with Custom Fields enabled', () => { beforeEach( async () => { await createNewPost(); await toggleCustomFieldsOption( true ); + await disableNavigationMode(); } ); afterEach( async () => { diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index 6dce07f78146ca..a7acef1d2aeaf0 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -80,10 +80,12 @@ describe( 'RichText', () => { it( 'should return focus when pressing formatting button', async () => { await clickBlockAppender(); await page.keyboard.type( 'Some ' ); - await page.keyboard.press( 'Escape' ); + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); await page.click( '[aria-label="Bold"]' ); await page.keyboard.type( 'bold' ); - await page.keyboard.press( 'Escape' ); + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); await page.click( '[aria-label="Bold"]' ); await page.keyboard.type( '.' ); diff --git a/packages/e2e-tests/specs/undo.test.js b/packages/e2e-tests/specs/undo.test.js index 60e9ff64bfdebd..4200e31f1ca6bb 100644 --- a/packages/e2e-tests/specs/undo.test.js +++ b/packages/e2e-tests/specs/undo.test.js @@ -9,6 +9,7 @@ import { selectBlockByClientId, getAllBlocks, saveDraft, + disableNavigationMode, } from '@wordpress/e2e-test-utils'; describe( 'undo', () => { @@ -79,6 +80,7 @@ describe( 'undo', () => { await page.keyboard.type( 'original' ); await saveDraft(); await page.reload(); + await disableNavigationMode(); // Issue is demonstrated by forcing state merges (multiple inputs) on // an existing text after a fresh reload. diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index 6528b372584dd3..3c8c4b39e1b36b 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -10,7 +10,7 @@ import { insertBlock, } from '@wordpress/e2e-test-utils'; -describe( 'adding blocks', () => { +describe( 'Writing Flow', () => { beforeEach( async () => { await createNewPost(); } ); From 756dcea9dd6ab4ce675d649d374aae4fb02a8832 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 2 Aug 2019 15:10:31 +0100 Subject: [PATCH 594/664] Tweak Riad's notifications. (#16879) --- .github/CODEOWNERS | 90 +++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 33f40e357faf3d..e2d185b76a068d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,7 @@ # Documentation -/docs @youknowriad @chrisvanpatten @ajitbohra +/docs @chrisvanpatten @ajitbohra /docs/designers-developers/developers @youknowriad @chrisvanpatten @mkaz @ajitbohra -/docs/designers-developers/designers @youknowriad @chrisvanpatten @mkaz @ajitbohra +/docs/designers-developers/designers @chrisvanpatten @mkaz @ajitbohra # Data /packages/api-fetch @youknowriad @nerrad @mmtr @@ -10,41 +10,41 @@ /packages/redux-routine @youknowriad @nerrad # Blocks -/packages/block-library @youknowriad @Soean @ajitbohra @jorgefilipecosta @talldan +/packages/block-library @Soean @ajitbohra @jorgefilipecosta @talldan # Editor -/packages/annotations @youknowriad @atimmer @ellatrix -/packages/autop @youknowriad @aduth +/packages/annotations @atimmer @ellatrix +/packages/autop @aduth /packages/block-editor @youknowriad @talldan @ellatrix -/packages/block-serialization-spec-parser @youknowriad @dmsnell -/packages/block-serialization-default-parser @youknowriad @dmsnell +/packages/block-serialization-spec-parser @dmsnell +/packages/block-serialization-default-parser @dmsnell /packages/blocks @youknowriad @gziolo @ellatrix -/packages/edit-post @youknowriad @talldan -/packages/editor @youknowriad @talldan +/packages/edit-post @talldan +/packages/editor @talldan /packages/list-reusable-blocks @youknowriad @noisysocks -/packages/shortcode @youknowriad @aduth +/packages/shortcode @aduth # Widgets /packages/edit-widgets @youknowriad # Tooling -/bin @youknowriad @ntwb @nerrad @ajitbohra -/bin/update-readmes.js @youknowriad @ntwb @nerrad @ajitbohra @nosolosw +/bin @ntwb @nerrad @ajitbohra +/bin/update-readmes.js @ntwb @nerrad @ajitbohra @nosolosw /docs/tool @youknowriad @chrisvanpatten @ajitbohra @nosolosw -/packages/babel-plugin-import-jsx-pragma @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/babel-plugin-makepot @youknowriad @ntwb @nerrad @ajitbohra +/packages/babel-plugin-import-jsx-pragma @gziolo @ntwb @nerrad @ajitbohra +/packages/babel-plugin-makepot @ntwb @nerrad @ajitbohra /packages/babel-preset-default @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/browserslist-config @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/custom-templated-path-webpack-plugin @youknowriad @ntwb @nerrad @ajitbohra +/packages/browserslist-config @gziolo @ntwb @nerrad @ajitbohra +/packages/custom-templated-path-webpack-plugin @ntwb @nerrad @ajitbohra /packages/docgen @nosolosw -/packages/e2e-test-utils @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/e2e-tests @youknowriad @gziolo @ntwb @nerrad @ajitbohra @talldan -/packages/eslint-plugin @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/jest-console @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/jest-preset-default @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/jest-puppeteer-axe @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/library-export-default-webpack-plugin @youknowriad @gziolo @ntwb @nerrad @ajitbohra -/packages/npm-package-json-lint-config @youknowriad @gziolo @ntwb @nerrad @ajitbohra +/packages/e2e-test-utils @gziolo @ntwb @nerrad @ajitbohra +/packages/e2e-tests @gziolo @ntwb @nerrad @ajitbohra @talldan +/packages/eslint-plugin @gziolo @ntwb @nerrad @ajitbohra +/packages/jest-console @gziolo @ntwb @nerrad @ajitbohra +/packages/jest-preset-default @gziolo @ntwb @nerrad @ajitbohra +/packages/jest-puppeteer-axe @gziolo @ntwb @nerrad @ajitbohra +/packages/library-export-default-webpack-plugin @gziolo @ntwb @nerrad @ajitbohra +/packages/npm-package-json-lint-config @gziolo @ntwb @nerrad @ajitbohra /packages/postcss-themes @youknowriad @ntwb @nerrad @ajitbohra /packages/scripts @youknowriad @gziolo @ntwb @nerrad @ajitbohra @nosolosw @@ -52,38 +52,38 @@ /packages/components @youknowriad @gziolo @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @chrisvanpatten /packages/compose @youknowriad @gziolo @ajitbohra @jaymanpandya @jorgefilipecosta @talldan /packages/element @youknowriad @gziolo @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/notices @youknowriad @ajitbohra @jaymanpandya @jorgefilipecosta @talldan -/packages/nux @youknowriad @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks +/packages/notices @ajitbohra @jaymanpandya @jorgefilipecosta @talldan +/packages/nux @ajitbohra @jaymanpandya @jorgefilipecosta @talldan @noisysocks /packages/viewport @youknowriad @ajitbohra @jaymanpandya @jorgefilipecosta @talldan # Utilities /packages/a11y @youknowriad @aduth -/packages/blob @youknowriad @aduth -/packages/date @youknowriad @aduth -/packages/deprecated @youknowriad @aduth -/packages/dom @youknowriad @ellatrix -/packages/dom-ready @youknowriad @aduth -/packages/escape-html @youknowriad @aduth -/packages/html-entities @youknowriad @aduth -/packages/i18n @youknowriad @swissspidy -/packages/is-shallow-equal @youknowriad @aduth -/packages/keycodes @youknowriad @talldan @ellatrix +/packages/blob @aduth +/packages/date @aduth +/packages/deprecated @aduth +/packages/dom @ellatrix +/packages/dom-ready @aduth +/packages/escape-html @aduth +/packages/html-entities @aduth +/packages/i18n @swissspidy +/packages/is-shallow-equal @aduth +/packages/keycodes @talldan @ellatrix /packages/priority-queue @youknowriad @aduth -/packages/token-list @youknowriad @aduth -/packages/url @youknowriad @talldan -/packages/wordcount @youknowriad @aduth +/packages/token-list @aduth +/packages/url @talldan +/packages/wordcount @aduth # Extensibility -/packages/hooks @youknowriad @gziolo @adamsilverstein -/packages/plugins @youknowriad @gziolo @adamsilverstein +/packages/hooks @gziolo @adamsilverstein +/packages/plugins @gziolo @adamsilverstein # Rich Text -/packages/format-library @youknowriad @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom -/packages/rich-text @youknowriad @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom -/packages/block-editor/src/components/rich-text @youknowriad @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/format-library @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/rich-text @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom +/packages/block-editor/src/components/rich-text @ellatrix @jorgefilipecosta @daniloercoli @sergioestevao @etoledom # PHP -/lib @youknowriad @timothybjacobs +/lib @timothybjacobs # Native (Unowned) *.native.js @ghost From 132252328d9ebf908de24df823fd3cda5137c3f6 Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Fri, 2 Aug 2019 08:44:14 -0700 Subject: [PATCH 595/664] Unify BlockPreview & BlockPreviewContent into one component (#16801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * export BlockPreview * Update reusable Blocks preview to use the new single blocks prop * Remove export. This is being handled in another PR See https://github.com/WordPress/gutenberg/pull/16801 * mark all usages with red for easier visual tracking * make a new unified component * use new component in style thumbnail * update usage in block switcher, add isScaled & className support * revert initial state in block switcher * use in block inserter * use the new unified component as default, migrate usages * drop word 'unified' from classname * remove extra import. h/t @retrofox * remove border around previews * fix styling of scaled previews * make abstract component for styling previews inside dropdowns * move block switcher & inserter to use common component for styling * drop the old editor-block-preview class * revert dropdown component abstraction This reverts commit 5bc673e2473d9c8d175b836d8e4704efae229b80. This reverts commit 3006c01f9d728086a8a89a43a0e51664a996a6c0. --- .../src/components/block-preview/index.js | 28 +++++----- .../src/components/block-preview/style.scss | 54 ++++++++----------- .../src/components/block-styles/index.js | 5 +- .../src/components/block-switcher/index.js | 19 ++++--- .../src/components/block-switcher/style.scss | 35 +++++++----- .../src/components/inserter/menu.js | 8 ++- .../src/components/inserter/style.scss | 34 ++++++++---- 7 files changed, 103 insertions(+), 80 deletions(-) diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index b4d0850f3b8108..2037bc6a0f2ea2 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -2,11 +2,11 @@ * External dependencies */ import { castArray } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; import { Disabled } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; @@ -23,22 +23,24 @@ import BlockList from '../block-list'; * * @return {WPElement} Rendered element. */ -function BlockPreview( props ) { - return ( - <div className="editor-block-preview block-editor-block-preview"> - <div className="editor-block-preview__title block-editor-block-preview__title">{ __( 'Preview' ) }</div> - <BlockPreviewContent { ...props } /> - </div> - ); -} - -export function BlockPreviewContent( { blocks, settings } ) { +function BlockPreview( { blocks, settings, className, isScaled } ) { if ( ! blocks ) { return null; } - return ( - <Disabled className="editor-block-preview__content block-editor-block-preview__content editor-styles-wrapper" aria-hidden> + <Disabled + aria-hidden + className={ + classnames( + className, + 'block-editor-block-preview', + 'editor-styles-wrapper', + { + 'is-scaled': isScaled, + } + ) + } + > <BlockEditorProvider value={ castArray( blocks ) } settings={ settings } diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index b96e181aa67d36..ff42e7b3b4dccd 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -1,35 +1,9 @@ .block-editor-block-preview { - pointer-events: none; - padding: 10px; + padding: $block-padding; + font-family: $editor-font; overflow: hidden; - display: none; + width: 100%; - @include break-medium { - display: block; - } - - .block-editor-block-preview__content { - padding: $block-padding; - border: $border-width solid $light-gray-500; - font-family: $editor-font; - - > div { - transform: scale(0.9); - transform-origin: center top; - font-family: $editor-font; - } - - > div section { - height: auto; - } - - > .reusable-block-indicator { - display: none; - } - } -} - -.block-editor-block-preview__content { // Resetting the block editor paddings and margins .block-editor-block-list__layout, .block-editor-block-list__block { @@ -38,9 +12,23 @@ .editor-block-list__block-edit [data-block] { margin-top: 0; } -} -.block-editor-block-preview__title { - margin-bottom: 10px; - color: $dark-gray-300; + > div section { + height: auto; + } + + > .reusable-block-indicator { + display: none; + } + + .block-list-appender { + display: none; + } + + &.is-scaled { + > div { + transform: scale(0.9); + transform-origin: center top; + } + } } diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index 2485c73e9a0a44..f9270094e47082 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -17,7 +17,7 @@ import { getBlockType, cloneBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import { BlockPreviewContent } from '../block-preview'; +import BlockPreview from '../block-preview'; /** * Returns the active style from the given className. @@ -122,7 +122,8 @@ function BlockStyles( { aria-label={ style.label || style.name } > <div className="editor-block-styles__item-preview block-editor-block-styles__item-preview"> - <BlockPreviewContent + <BlockPreview + isScaled blocks={ cloneBlock( block, { className: styleClassName, } ) } diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 4101092db6d4d1..bd7009f18190dc 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -160,14 +160,17 @@ export class BlockSwitcher extends Component { } { ( hoveredClassName !== null ) && - <BlockPreview - blocks={ - cloneBlock( blocks[ 0 ], { - className: hoveredClassName, - } ) - } - - /> + <div className="block-editor-block-switcher__preview"> + <div className="block-editor-block-switcher__preview-title">{ __( 'Preview' ) }</div> + <BlockPreview + className="block-editor-block-switcher__preview-content" + blocks={ + cloneBlock( blocks[ 0 ], { + className: hoveredClassName, + } ) + } + /> + </div> } </> ) } diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index b80a586023045a..2ef182aca140ee 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -97,18 +97,6 @@ .block-editor-block-switcher__popover .components-popover__content { @include break-medium { position: relative; - - .block-editor-block-preview { - border: $border-width solid $light-gray-500; - box-shadow: $shadow-popover; - background: $white; - position: absolute; - left: 100%; - top: -1px; - bottom: -1px; - width: 300px; - height: auto; - } } // Hide the bottom border on the last panel so it stacks with the popover. @@ -137,3 +125,26 @@ .block-editor-block-switcher__popover .block-editor-block-types-list { margin: 8px -8px -8px; } + +.block-editor-block-switcher__preview { + border: $border-width solid $light-gray-500; + box-shadow: $shadow-popover; + background: $white; + position: absolute; + left: 100%; + top: -1px; + bottom: -1px; + width: 300px; + height: auto; + padding: 10px; + display: none; + + @include break-medium { + display: block; + } +} + +.block-editor-block-switcher__preview-title { + margin-bottom: 10px; + color: $dark-gray-300; +} diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 12d119ce4ab519..8fa9a61ea30fda 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -351,7 +351,13 @@ export class InserterMenu extends Component { </div> { hoveredItem && isReusableBlock( hoveredItem ) && - <BlockPreview blocks={ createBlock( hoveredItem.name, hoveredItem.initialAttributes ) } /> + <div className="block-editor-inserter__preview"> + <div className="block-editor-inserter__preview-title">{ __( 'Preview' ) }</div> + <BlockPreview + className="block-editor-inserter__preview-content" + blocks={ createBlock( hoveredItem.name, hoveredItem.initialAttributes ) } + /> + </div> } </div> ); diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 87b9a79f12fa40..4cc764ed0861a6 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -43,17 +43,6 @@ $block-inserter-search-height: 38px; @include break-medium { width: 400px; position: relative; - - .block-editor-block-preview { - border: $border-width solid $light-gray-500; - box-shadow: $shadow-popover; - background: $white; - position: absolute; - left: 100%; - top: -1px; - bottom: -1px; - width: 300px; - } } } @@ -140,3 +129,26 @@ $block-inserter-search-height: 38px; margin-right: $grid-size; } } + +.block-editor-inserter__preview { + border: $border-width solid $light-gray-500; + box-shadow: $shadow-popover; + background: $white; + position: absolute; + left: 100%; + top: -1px; + bottom: -1px; + width: 300px; + height: auto; + padding: 10px; + display: none; + + @include break-medium { + display: block; + } +} + +.block-editor-inserter__preview-title { + margin-bottom: 10px; + color: $dark-gray-300; +} From ca800564218cf6915fda7cf55cdf2cb96563e807 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Fri, 2 Aug 2019 11:49:42 -0400 Subject: [PATCH 596/664] Go back to using 1em margins for notices in the notice list. (#16861) --- packages/components/src/notice/style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index bdb82370eaa6a0..ad96505a1dae3b 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -65,7 +65,7 @@ z-index: z-index(".components-notice-list"); .components-notice__content { - margin-top: $grid-size; - margin-bottom: $grid-size; + margin-top: 1em; + margin-bottom: 1em; } } From ac569506703a41e250bd7debbde8bb475fbfb9dd Mon Sep 17 00:00:00 2001 From: Dave Smith <getdavemail@gmail.com> Date: Fri, 2 Aug 2019 17:30:50 +0100 Subject: [PATCH 597/664] Hide appender on BlockPreviews via checking for null component in BlockListAppender (#16887) --- .../src/components/block-list-appender/index.js | 8 +++++++- .../block-editor/src/components/block-preview/index.js | 2 +- .../block-editor/src/components/inner-blocks/README.md | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index 35bf09bb89e669..ddc22b3b928bbf 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -27,7 +27,8 @@ function BlockListAppender( { return null; } - // A render prop has been provided, use it to render the appender. + // If a render prop has been provided + // use it to render the appender. if ( CustomAppender ) { return ( <div className="block-list-appender"> @@ -36,6 +37,11 @@ function BlockListAppender( { ); } + // a false value means, don't render any appender. + if ( CustomAppender === false ) { + return null; + } + // Render the default block appender when renderAppender has not been // provided and the context supports use of the default appender. if ( canInsertDefaultBlock ) { diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 2037bc6a0f2ea2..3dad6054d64829 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -45,7 +45,7 @@ function BlockPreview( { blocks, settings, className, isScaled } ) { value={ castArray( blocks ) } settings={ settings } > - <BlockList /> + <BlockList renderAppender={ false } /> </BlockEditorProvider> </Disabled> ); diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index 303e24730f7fe3..5bed06f7a80473 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -177,8 +177,8 @@ If locking is not set in an `InnerBlocks` area: the locking of the parent `Inner If the block is a top level block: the locking of the Custom Post Type is used. ### `renderAppender` -* **Type:** `Function` -* **Default:** - `undefined`. When `renderAppender` is not specific the `<DefaultBlockAppender>` component is as a default. It automatically inserts whichever block is configured as the default block via `wp.blocks.setDefaultBlockName` (typically `paragraph`). +* **Type:** `Function|false` +* **Default:** - `undefined`. When `renderAppender` is not specific the `<DefaultBlockAppender>` component is as a default. It automatically inserts whichever block is configured as the default block via `wp.blocks.setDefaultBlockName` (typically `paragraph`). If a `false` value is provider, no appender is rendered. A 'render prop' function that can be used to customize the block's appender. From 87da09e96550ca87a35daae401fd21d5261812e9 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Fri, 2 Aug 2019 13:37:48 -0400 Subject: [PATCH 598/664] Try making the hover state of nested blocks more visible (#16820) * Try using a solid border for the hover state of nested blocks. * Try using slightly lighter dashed borders. This allows for us to match the existing hover color on hover. * Add dark mode styles. * Expand hover styles to cover all children of a parent block. This ensures that they apply correctly in a 3+ level deep scenario. --- .../src/components/block-list/style.scss | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 5a3410d7230c47..afdbcf72977144 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -110,7 +110,7 @@ border-left: none; box-shadow: none; pointer-events: none; - transition: border-color 0.1s linear, box-shadow 0.1s linear; + transition: border-color 0.1s linear, border-style 0.1s linear, box-shadow 0.1s linear; @include reduce-motion("transition"); // Include a transparent outline for Windows High Contrast mode. @@ -182,21 +182,64 @@ &.has-child-selected { > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-800; + border: $border-width dashed $dark-opacity-light-600; + + .is-dark-theme & { + border-color: $light-opacity-light-500; + } } > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-800; + border: $border-width dashed $dark-opacity-light-600; + + .is-dark-theme & { + border-color: $light-opacity-light-500; + } + } + + &.is-hovered > .block-editor-block-list__block-edit::before, + > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block.is-hovered:not(.is-selected) > .block-editor-block-list__block-edit::before, + > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block.is-hovered:not(.is-selected) > .block-editor-block-list__block-edit::before, + > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block.is-hovered:not(.is-selected) > .block-editor-block-list__block-edit::before { + border-style: solid; + border-color: $dark-opacity-light-500; + border-left-color: transparent; + + .is-dark-theme & { + border-color: $light-opacity-light-400; + border-left-color: transparent; + } } } // Add extra border to child blocks when they are selected. - &.is-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, - &.is-selected > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, - &.is-selected > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-800; + &.is-selected { + + > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected), + > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected), + > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) { + + > .block-editor-block-list__block-edit::before { + border: $border-width dashed $dark-opacity-light-600; + + .is-dark-theme & { + border-color: $light-opacity-light-500; + } + } + + &.is-hovered > .block-editor-block-list__block-edit::before { + border-style: solid; + border-color: $dark-opacity-light-500; + border-left-color: transparent; + + .is-dark-theme & { + border-color: $light-opacity-light-400; + border-left-color: transparent; + } + } + } } } From fecd83835bb7950431420ae696ae83075b7ff451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= <rdsuarez@gmail.com> Date: Fri, 2 Aug 2019 17:23:37 -0300 Subject: [PATCH 599/664] BlockEditor: expose BlockPreview component (#16834) * block-editor: expose BlockPreview block * block-preview: add README file. props to @jasmussen and @getdate * match readme with current functionality * move doc comment so it is picked up by the generator --- packages/block-editor/README.md | 12 ++++++++ .../src/components/block-preview/README.md | 29 +++++++++++++++++++ .../src/components/block-preview/index.js | 16 +++++----- packages/block-editor/src/components/index.js | 1 + 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 packages/block-editor/src/components/block-preview/README.md diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 63718286e9bb87..e777495d699e7b 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -122,6 +122,18 @@ Undocumented declaration. Undocumented declaration. +<a name="BlockPreview" href="#BlockPreview">#</a> **BlockPreview** + +BlockPreview renders a preview given an array of blocks. + +_Parameters_ + +- _props_ `Object`: Component props. + +_Returns_ + +- `WPElement`: Rendered element. + <a name="BlockSelectionClearer" href="#BlockSelectionClearer">#</a> **BlockSelectionClearer** Undocumented declaration. diff --git a/packages/block-editor/src/components/block-preview/README.md b/packages/block-editor/src/components/block-preview/README.md new file mode 100644 index 00000000000000..10aaec2dd4001c --- /dev/null +++ b/packages/block-editor/src/components/block-preview/README.md @@ -0,0 +1,29 @@ +BlockPreview +============ + +`<BlockPreview />` allows you to preview blocks. + +## Usage + +Render the component passing in the required props: + +```jsx +<BlockPreview + blocks={ blocks } + isScaled={ false } +/> +``` + +## Props + +### `blocks` +* **Type:** `array|object` +* **Default:** `undefined` + +A block instance (object) or a blocks array you would like to render a preview. + +### `isScaled` +* **Type:** `Boolean` +* **Default:** `false` + +Use this if you need to render previews in smaller areas, like block thumbnails. diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 3dad6054d64829..b63fd4abbf7dd5 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -16,14 +16,7 @@ import { withSelect } from '@wordpress/data'; import BlockEditorProvider from '../provider'; import BlockList from '../block-list'; -/** - * Block Preview Component: It renders a preview given a block name and attributes. - * - * @param {Object} props Component props. - * - * @return {WPElement} Rendered element. - */ -function BlockPreview( { blocks, settings, className, isScaled } ) { +export function BlockPreview( { blocks, settings, className, isScaled } ) { if ( ! blocks ) { return null; } @@ -51,6 +44,13 @@ function BlockPreview( { blocks, settings, className, isScaled } ) { ); } +/** + * BlockPreview renders a preview given an array of blocks. + * + * @param {Object} props Component props. + * + * @return {WPElement} Rendered element. + */ export default withSelect( ( select ) => { return { settings: select( 'core/block-editor' ).getSettings(), diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index dc38643a6519b9..9ad37a8929b433 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -45,6 +45,7 @@ export { default as BlockEditorKeyboardShortcuts } from './block-editor-keyboard export { default as BlockInspector } from './block-inspector'; export { default as BlockList } from './block-list'; export { default as BlockMover } from './block-mover'; +export { default as BlockPreview } from './block-preview'; export { default as BlockSelectionClearer } from './block-selection-clearer'; export { default as BlockSettingsMenu } from './block-settings-menu'; export { default as BlockTitle } from './block-title'; From 5920ab8620e0a5ea6ed810e399dc42fd94d99abd Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Sat, 3 Aug 2019 15:58:54 -0400 Subject: [PATCH 600/664] fix all JSDoc eslint errors/warnings across the entire repository (#16870) * chore: fix all JSDoc eslint errors/warnings across the entire repository * fix: address code review comments --- bin/commander.js | 2 +- .../developers/data/data-core-block-editor.md | 4 +- packages/annotations/src/format/annotation.js | 9 +- packages/annotations/src/store/actions.js | 19 ++- packages/autop/src/index.js | 2 +- packages/block-editor/README.md | 4 +- .../src/components/block-list/index.js | 6 - .../src/components/block-title/index.js | 1 + .../src/components/colors/utils.js | 30 ++-- .../src/components/colors/with-colors.js | 14 +- .../components/font-sizes/with-font-sizes.js | 5 +- .../components/ignore-nested-events/index.js | 2 - .../src/components/inserter/index.js | 2 + .../multi-select-scroll-into-view/index.js | 2 - packages/block-editor/src/hooks/align.js | 4 +- packages/block-editor/src/hooks/anchor.js | 2 +- .../src/hooks/custom-class-name.js | 2 +- packages/block-editor/src/store/actions.js | 6 +- packages/block-editor/src/store/reducer.js | 2 +- packages/block-editor/src/store/selectors.js | 5 +- .../src/utils/transform-styles/ast/parse.js | 131 +++++++++--------- .../ast/stringify/compiler.js | 4 - .../transform-styles/ast/stringify/index.js | 3 +- .../transforms/url-rewrite.js | 1 - .../utils/transform-styles/transforms/wrap.js | 2 +- packages/block-library/src/columns/edit.js | 4 +- packages/block-library/src/embed/util.js | 8 +- packages/block-library/src/table/edit.js | 1 + packages/block-library/src/table/state.js | 5 + .../src/index.js | 6 +- packages/blocks/README.md | 2 + .../raw-handling/image-corrector.native.js | 2 +- packages/blocks/src/api/raw-handling/index.js | 1 + .../src/api/raw-handling/paste-handler.js | 1 + packages/blocks/src/api/registration.js | 36 ++--- packages/blocks/src/store/reducer.js | 2 +- packages/components/src/autocomplete/index.js | 26 ++-- packages/components/src/color-picker/index.js | 2 +- packages/components/src/draggable/index.js | 2 - .../src/higher-order/with-notices/index.js | 30 ++-- packages/components/src/notice/list.js | 18 +-- packages/components/src/placeholder/index.js | 10 +- packages/components/src/popover/index.js | 4 +- packages/components/src/popover/utils.js | 2 - packages/components/src/snackbar/list.js | 18 +-- packages/components/src/toolbar/index.js | 9 +- packages/components/src/tooltip/index.js | 2 +- packages/core-data/src/controls.js | 1 + .../src/queried-data/get-query-parts.js | 2 +- packages/data-controls/src/index.js | 9 +- packages/data/README.md | 8 +- packages/data/src/factory.js | 8 +- packages/data/src/index.js | 10 +- .../src/namespace-store/metadata/reducer.js | 2 +- .../data/src/plugins/persistence/index.js | 3 +- packages/data/src/promise-middleware.js | 2 +- packages/data/src/registry.js | 8 +- packages/date/src/index.js | 6 +- .../util.js | 10 +- packages/docgen/src/engine.js | 8 +- packages/e2e-test-utils/README.md | 8 +- .../src/are-pre-publish-checks-enabled.js | 1 + .../e2e-test-utils/src/get-block-setting.js | 1 + .../src/mocks/create-embedding-matcher.js | 4 +- .../src/mocks/create-url-matcher.js | 2 +- .../src/mocks/mock-or-transform.js | 4 +- .../e2e-test-utils/src/set-post-content.js | 1 + packages/e2e-test-utils/src/wp-data-select.js | 1 + .../components/editor-initialization/utils.js | 6 +- packages/edit-post/src/store/actions.js | 2 +- packages/edit-post/src/store/constants.js | 3 + packages/edit-widgets/src/store/actions.js | 4 +- packages/edit-widgets/src/store/constants.js | 1 + .../src/components/autocompleters/user.js | 8 +- .../components/post-preview-button/index.js | 2 +- .../post-type-support-check/index.js | 3 +- packages/editor/src/store/actions.native.js | 2 +- packages/editor/src/store/constants.js | 1 + packages/editor/src/store/reducer.js | 2 +- packages/element/README.md | 2 + packages/element/src/raw-html.js | 2 + .../eslint-plugin/rules/gutenberg-phase.js | 2 +- packages/format-library/src/link/utils.js | 7 +- .../src/components/media-upload/index.js | 2 +- packages/notices/src/store/selectors.js | 6 +- packages/redux-routine/src/runtime.js | 4 +- packages/rich-text/README.md | 8 +- packages/rich-text/src/component/index.js | 10 +- .../rich-text/src/component/index.native.js | 2 +- packages/rich-text/src/split.js | 8 +- packages/wordcount/README.md | 6 +- packages/wordcount/src/index.js | 6 +- test/native/__mocks__/styleMock.js | 4 - test/native/jest.config.js | 3 - 94 files changed, 335 insertions(+), 325 deletions(-) diff --git a/bin/commander.js b/bin/commander.js index bb5f508ca56965..091e8156611bdb 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -57,7 +57,7 @@ async function askForConfirmationToContinue( message, isDefault = true, abortMes * * @param {string} name Step name. * @param {string} abortMessage Abort message. - * @param {function} handler Step logic. + * @param {Function} handler Step logic. */ async function runStep( name, abortMessage, handler ) { try { diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 005c474f570f64..f4a9d048f5e5bb 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -203,7 +203,7 @@ on each call _Parameters_ - _state_ `Object`: Editor state. -- _rootClientId_ `?String`: Optional root client ID of block list. +- _rootClientId_ `?string`: Optional root client ID of block list. _Returns_ @@ -299,7 +299,7 @@ The number returned includes nested blocks. _Parameters_ - _state_ `Object`: Global application state. -- _blockName_ `?String`: Optional block name, if specified only blocks of that type will be counted. +- _blockName_ `?string`: Optional block name, if specified only blocks of that type will be counted. _Returns_ diff --git a/packages/annotations/src/format/annotation.js b/packages/annotations/src/format/annotation.js index a2f6f2973c7268..46dd6e279fa3ba 100644 --- a/packages/annotations/src/format/annotation.js +++ b/packages/annotations/src/format/annotation.js @@ -92,10 +92,11 @@ function retrieveAnnotationPositions( formats ) { /** * Updates annotations in the state based on positions retrieved from RichText. * - * @param {Array} annotations The annotations that are currently applied. - * @param {Array} positions The current positions of the given annotations. - * @param {Function} removeAnnotation Function to remove an annotation from the state. - * @param {Function} updateAnnotationRange Function to update an annotation range in the state. + * @param {Array} annotations The annotations that are currently applied. + * @param {Array} positions The current positions of the given annotations. + * @param {Object} actions + * @param {Function} actions.removeAnnotation Function to remove an annotation from the state. + * @param {Function} actions.updateAnnotationRange Function to update an annotation range in the state. */ function updateAnnotationsWithPositions( annotations, positions, { removeAnnotation, updateAnnotationRange } ) { annotations.forEach( ( currentAnnotation ) => { diff --git a/packages/annotations/src/store/actions.js b/packages/annotations/src/store/actions.js index 96598022d5a7c1..8135ca3e32066b 100644 --- a/packages/annotations/src/store/actions.js +++ b/packages/annotations/src/store/actions.js @@ -13,16 +13,15 @@ import uuid from 'uuid/v4'; * * The `range` property is only relevant if the selector is 'range'. * - * @param {Object} annotation The annotation to add. - * @param {string} blockClientId The blockClientId to add the annotation to. - * @param {string} richTextIdentifier Identifier for the RichText instance the annotation applies to. - * @param {Object} range The range at which to apply this annotation. - * @param {number} range.start The offset where the annotation should start. - * @param {number} range.end The offset where the annotation should end. - * @param {string} [selector="range"] The way to apply this annotation. - * @param {string} [source="default"] The source that added the annotation. - * @param {string} [id=uuid()] The ID the annotation should have. - * Generates a UUID by default. + * @param {Object} annotation The annotation to add. + * @param {string} annotation.blockClientId The blockClientId to add the annotation to. + * @param {string} annotation.richTextIdentifier Identifier for the RichText instance the annotation applies to. + * @param {Object} annotation.range The range at which to apply this annotation. + * @param {number} annotation.range.start The offset where the annotation should start. + * @param {number} annotation.range.end The offset where the annotation should end. + * @param {string} annotation.[selector="range"] The way to apply this annotation. + * @param {string} annotation.[source="default"] The source that added the annotation. + * @param {string} annotation.[id] The ID the annotation should have. Generates a UUID by default. * * @return {Object} Action object. */ diff --git a/packages/autop/src/index.js b/packages/autop/src/index.js index 3e3e2fedc36b8c..1225d5c6637d8c 100644 --- a/packages/autop/src/index.js +++ b/packages/autop/src/index.js @@ -1,7 +1,7 @@ /** * The regular expression for an HTML element. * - * @type {String} + * @type {string} */ const htmlSplitRegex = ( () => { /* eslint-disable no-multi-spaces */ diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index e777495d699e7b..36a256f93cb685 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -456,7 +456,7 @@ export default compose( _Parameters_ -- _colorTypes_ `...(object|string)`: The arguments can be strings or objects. If the argument is an object, it should contain the color attribute name as key and the color context as value. If the argument is a string the value should be the color attribute name, the color context is computed by applying a kebab case transform to the value. Color context represents the context/place where the color is going to be used. The class name of the color is generated using 'has' followed by the color name and ending with the color context all in kebab case e.g: has-green-background-color. +- _colorTypes_ `...(Object|string)`: The arguments can be strings or objects. If the argument is an object, it should contain the color attribute name as key and the color context as value. If the argument is a string the value should be the color attribute name, the color context is computed by applying a kebab case transform to the value. Color context represents the context/place where the color is going to be used. The class name of the color is generated using 'has' followed by the color name and ending with the color context all in kebab case e.g: has-green-background-color. _Returns_ @@ -469,7 +469,7 @@ font size value retrieval, and font size change handling. _Parameters_ -- _args_ `...(object|string)`: The arguments should all be strings Each string contains the font size attribute name e.g: 'fontSize'. +- _fontSizeNames_ `...(Object|string)`: The arguments should all be strings. Each string contains the font size attribute name e.g: 'fontSize'. _Returns_ diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 3e65a52fd3b2ec..e3f68637dbb91d 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -87,8 +87,6 @@ class BlockList extends Component { * multi-selection. * * @param {MouseEvent} event A mousemove event object. - * - * @return {void} */ onPointerMove( { clientY } ) { // We don't start multi-selection until the mouse starts moving, so as @@ -116,8 +114,6 @@ class BlockList extends Component { * in response to a mousedown event occurring in a rendered block. * * @param {string} clientId Client ID of block where mousedown occurred. - * - * @return {void} */ onSelectionStart( clientId ) { if ( ! this.props.isSelectionEnabled ) { @@ -173,8 +169,6 @@ class BlockList extends Component { /** * Handles a mouseup event to end the current cursor multi-selection. - * - * @return {void} */ onSelectionEnd() { // Cancel throttled calls. diff --git a/packages/block-editor/src/components/block-title/index.js b/packages/block-editor/src/components/block-title/index.js index 99519655419e12..6834df11498922 100644 --- a/packages/block-editor/src/components/block-title/index.js +++ b/packages/block-editor/src/components/block-title/index.js @@ -14,6 +14,7 @@ import { getBlockType } from '@wordpress/blocks'; * <BlockTitle clientId="afd1cb17-2c08-4e7a-91be-007ba7ddc3a1" /> * ``` * + * @param {Object} props * @param {?string} props.name Block name. * * @return {?string} Block title. diff --git a/packages/block-editor/src/components/colors/utils.js b/packages/block-editor/src/components/colors/utils.js index 67be0eed5a2517..764b02acde2822 100644 --- a/packages/block-editor/src/components/colors/utils.js +++ b/packages/block-editor/src/components/colors/utils.js @@ -30,14 +30,14 @@ export const getColorObjectByAttributeValues = ( colors, definedColor, customCol }; /** -* Provided an array of color objects as set by the theme or by the editor defaults, and a color value returns the color object matching that value or undefined. -* -* @param {Array} colors Array of color objects as set by the theme or by the editor defaults. -* @param {?string} colorValue A string containing the color value. -* -* @return {?Object} Color object included in the colors array whose color property equals colorValue. -* Returns undefined if no color object matches this requirement. -*/ + * Provided an array of color objects as set by the theme or by the editor defaults, and a color value returns the color object matching that value or undefined. + * + * @param {Array} colors Array of color objects as set by the theme or by the editor defaults. + * @param {?string} colorValue A string containing the color value. + * + * @return {?Object} Color object included in the colors array whose color property equals colorValue. + * Returns undefined if no color object matches this requirement. + */ export const getColorObjectByColorValue = ( colors, colorValue ) => { return find( colors, { color: colorValue } ); }; @@ -60,13 +60,13 @@ export function getColorClassName( colorContextName, colorSlug ) { } /** -* Given an array of color objects and a color value returns the color value of the most readable color in the array. -* -* @param {Array} colors Array of color objects as set by the theme or by the editor defaults. -* @param {?string} colorValue A string containing the color value. -* -* @return {string} String with the color value of the most readable color. -*/ + * Given an array of color objects and a color value returns the color value of the most readable color in the array. + * + * @param {Array} colors Array of color objects as set by the theme or by the editor defaults. + * @param {?string} colorValue A string containing the color value. + * + * @return {string} String with the color value of the most readable color. + */ export function getMostReadableColor( colors, colorValue ) { return tinycolor.mostReadable( colorValue, diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index 516a5a023414fd..b6f33b1b8590f7 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -23,7 +23,7 @@ const DEFAULT_COLORS = []; * * @param {Array} colorsArray An array of color objects. * - * @return {function} The higher order component. + * @return {Function} The higher order component. */ const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( WrappedComponent ) => ( props ) => ( <WrappedComponent { ...props } colors={ colorsArray } /> @@ -33,7 +33,7 @@ const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( * Higher order component factory for injecting the editor colors as the * `colors` prop in the `withColors` HOC. * - * @return {function} The higher order component. + * @return {Function} The higher order component. */ const withEditorColorPalette = () => withSelect( ( select ) => { const settings = select( 'core/block-editor' ).getSettings(); @@ -110,10 +110,10 @@ function createColorHOC( colorTypes, withColorPalette ) { const previousColorObject = previousState[ colorAttributeName ]; const previousColor = get( previousColorObject, [ 'color' ] ); /** - * The "and previousColorObject" condition checks that a previous color object was already computed. - * At the start previousColorObject and colorValue are both equal to undefined - * bus as previousColorObject does not exist we should compute the object. - */ + * The "and previousColorObject" condition checks that a previous color object was already computed. + * At the start previousColorObject and colorValue are both equal to undefined + * bus as previousColorObject does not exist we should compute the object. + */ if ( previousColor === colorObject.color && previousColorObject ) { newState[ colorAttributeName ] = previousColorObject; } else { @@ -187,7 +187,7 @@ export function createCustomColorsHOC( colorsArray ) { * ); * ``` * - * @param {...(object|string)} colorTypes The arguments can be strings or objects. If the argument is an object, + * @param {...(Object|string)} colorTypes The arguments can be strings or objects. If the argument is an object, * it should contain the color attribute name as key and the color context as value. * If the argument is a string the value should be the color attribute name, * the color context is computed by applying a kebab case transform to the value. diff --git a/packages/block-editor/src/components/font-sizes/with-font-sizes.js b/packages/block-editor/src/components/font-sizes/with-font-sizes.js index 147d66df20c388..f64fe8d4f76006 100644 --- a/packages/block-editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/block-editor/src/components/font-sizes/with-font-sizes.js @@ -19,8 +19,9 @@ import { getFontSize, getFontSizeClass } from './utils'; * Higher-order component, which handles font size logic for class generation, * font size value retrieval, and font size change handling. * - * @param {...(object|string)} args The arguments should all be strings - * Each string contains the font size attribute name e.g: 'fontSize'. + * @param {...(Object|string)} fontSizeNames The arguments should all be strings. + * Each string contains the font size + * attribute name e.g: 'fontSize'. * * @return {Function} Higher-order component. */ diff --git a/packages/block-editor/src/components/ignore-nested-events/index.js b/packages/block-editor/src/components/ignore-nested-events/index.js index 42f902b3e61c95..2592ba3abd0527 100644 --- a/packages/block-editor/src/components/ignore-nested-events/index.js +++ b/packages/block-editor/src/components/ignore-nested-events/index.js @@ -39,8 +39,6 @@ export class IgnoreNestedEvents extends Component { * it has not already been handled by a descendant IgnoreNestedEvents. * * @param {Event} event Event object. - * - * @return {void} */ proxyEvent( event ) { const isHandled = !! event.nativeEvent._blockHandled; diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 4b842b4b7b3c59..98430b9345d088 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -46,6 +46,7 @@ class Inserter extends Component { /** * Render callback to display Dropdown toggle element. * + * @param {Object} options * @param {Function} options.onToggle Callback to invoke when toggle is * pressed. * @param {boolean} options.isOpen Whether dropdown is currently open. @@ -64,6 +65,7 @@ class Inserter extends Component { /** * Render callback to display Dropdown content element. * + * @param {Object} options * @param {Function} options.onClose Callback to invoke when dropdown is * closed. * diff --git a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js index e49a90a520cbee..240a6dfaadc3ef 100644 --- a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js +++ b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js @@ -25,8 +25,6 @@ class MultiSelectScrollIntoView extends Component { /** * Ensures that if a multi-selection exists, the extent of the selection is * visible within the nearest scrollable container. - * - * @return {void} */ scrollIntoView() { const { extentClientId } = this.props; diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index ca0a019d5cc9e6..635db6b65f68e3 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -23,7 +23,7 @@ import { BlockControls, BlockAlignmentToolbar } from '../components'; * * @constant * @type {string[]} -*/ + */ const ALL_ALIGNMENTS = [ 'left', 'center', 'right', 'wide', 'full' ]; /** @@ -33,7 +33,7 @@ const ALL_ALIGNMENTS = [ 'left', 'center', 'right', 'wide', 'full' ]; * * @constant * @type {string[]} -*/ + */ const WIDE_ALIGNMENTS = [ 'wide', 'full' ]; /** diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 3651346a96caf1..9b78c39bbfeb02 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -56,7 +56,7 @@ export function addAttribute( settings ) { * Override the default edit UI to include a new block inspector control for * assigning the anchor ID, if block supports anchor. * - * @param {function|Component} BlockEdit Original component. + * @param {Function|Component} BlockEdit Original component. * * @return {string} Wrapped component. */ diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index d7147a3693c30a..bac77a37ea42a2 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -47,7 +47,7 @@ export function addAttribute( settings ) { * Override the default edit UI to include a new block inspector control for * assigning the custom class name, if block supports custom class name. * - * @param {function|Component} BlockEdit Original component. + * @param {Function|Component} BlockEdit Original component. * * @return {string} Wrapped component. */ diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index c9d02e0b200a24..0c0e1e3fa94603 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -208,7 +208,7 @@ export function clearSelectedBlock() { * * @param {boolean} [isSelectionEnabled=true] Whether block selection should * be enabled. - + * * @return {Object} Action object. */ export function toggleSelection( isSelectionEnabled = true ) { @@ -227,7 +227,7 @@ export function toggleSelection( isSelectionEnabled = true ) { * @param {number} indexToSelect Index of replacement block to * select. * - * @yields {Object} Action object. + * @yield {Object} Action object. */ export function* replaceBlocks( clientIds, blocks, indexToSelect ) { clientIds = castArray( clientIds ); @@ -303,7 +303,7 @@ export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); * @param {?string} toRootClientId Root client ID destination. * @param {number} index The index to move the block into. * - * @yields {Object} Action object. + * @yield {Object} Action object. */ export function* moveBlockToPosition( clientId, fromRootClientId = '', toRootClientId = '', index ) { const templateLock = yield select( diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 3ef595d48c9a2d..ca277cb1e43dd9 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -581,7 +581,7 @@ const withSaveReusableBlock = ( reducer ) => ( state, action ) => { * @param {Object} state Current state. * @param {Object} action Dispatched action. * - * @returns {Object} Updated state. + * @return {Object} Updated state. */ export const blocks = flow( combineReducers, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 9fbc0c68e01629..462cf8cda1b8c9 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -169,7 +169,7 @@ export const __unstableGetBlockWithoutInnerBlocks = createSelector( * on each call * * @param {Object} state Editor state. - * @param {?String} rootClientId Optional root client ID of block list. + * @param {?string} rootClientId Optional root client ID of block list. * * @return {Object[]} Post blocks. */ @@ -224,7 +224,7 @@ export const getClientIdsWithDescendants = createSelector( * The number returned includes nested blocks. * * @param {Object} state Global application state. - * @param {?String} blockName Optional block name, if specified only blocks of that type will be counted. + * @param {?string} blockName Optional block name, if specified only blocks of that type will be counted. * * @return {number} Number of blocks in the post, or number of blocks with name equal to blockName. */ @@ -1297,6 +1297,7 @@ export const getInserterItems = createSelector( /** * Determines whether there are items to show in the inserter. + * * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * diff --git a/packages/block-editor/src/utils/transform-styles/ast/parse.js b/packages/block-editor/src/utils/transform-styles/ast/parse.js index 4f7e925c9937e1..b7c033fc3e72fe 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/parse.js +++ b/packages/block-editor/src/utils/transform-styles/ast/parse.js @@ -11,15 +11,15 @@ export default function( css, options ) { options = options || {}; /** - * Positional. - */ + * Positional. + */ let lineno = 1; let column = 1; /** - * Update lineno and column based on `str`. - */ + * Update lineno and column based on `str`. + */ function updatePosition( str ) { const lines = str.match( /\n/g ); @@ -32,8 +32,8 @@ export default function( css, options ) { } /** - * Mark position and patch `node.position`. - */ + * Mark position and patch `node.position`. + */ function position() { const start = { line: lineno, column }; @@ -45,8 +45,8 @@ export default function( css, options ) { } /** - * Store position information for a node - */ + * Store position information for a node + */ function Position( start ) { this.start = start; @@ -55,14 +55,14 @@ export default function( css, options ) { } /** - * Non-enumerable source string - */ + * Non-enumerable source string + */ Position.prototype.content = css; /** - * Error `msg`. - */ + * Error `msg`. + */ const errorsList = []; @@ -82,8 +82,8 @@ export default function( css, options ) { } /** - * Parse stylesheet. - */ + * Parse stylesheet. + */ function stylesheet() { const rulesList = rules(); @@ -99,24 +99,24 @@ export default function( css, options ) { } /** - * Opening brace. - */ + * Opening brace. + */ function open() { return match( /^{\s*/ ); } /** - * Closing brace. - */ + * Closing brace. + */ function close() { return match( /^}/ ); } /** - * Parse ruleset. - */ + * Parse ruleset. + */ function rules() { let node; @@ -133,8 +133,8 @@ export default function( css, options ) { } /** - * Match `re` and return captures. - */ + * Match `re` and return captures. + */ function match( re ) { const m = re.exec( css ); @@ -148,16 +148,16 @@ export default function( css, options ) { } /** - * Parse whitespace. - */ + * Parse whitespace. + */ function whitespace() { match( /^\s*/ ); } /** - * Parse comments; - */ + * Parse comments; + */ function comments( accumulator ) { let c; @@ -172,8 +172,8 @@ export default function( css, options ) { } /** - * Parse comment. - */ + * Parse comment. + */ function comment() { const pos = position(); @@ -204,16 +204,15 @@ export default function( css, options ) { } /** - * Parse selector. - */ + * Parse selector. + */ function selector() { const m = match( /^([^{]+)/ ); if ( ! m ) { return; } - /* @fix Remove all comments from selectors - * http://ostermiller.org/findcomment.html */ + // FIXME: Remove all comments from selectors http://ostermiller.org/findcomment.html return trim( m[ 0 ] ) .replace( /\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '' ) .replace( /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function( matched ) { @@ -226,8 +225,8 @@ export default function( css, options ) { } /** - * Parse declaration. - */ + * Parse declaration. + */ function declaration() { const pos = position(); @@ -260,8 +259,8 @@ export default function( css, options ) { } /** - * Parse declarations. - */ + * Parse declarations. + */ function declarations() { const decls = []; @@ -288,8 +287,8 @@ export default function( css, options ) { } /** - * Parse keyframe. - */ + * Parse keyframe. + */ function keyframe() { let m; @@ -314,8 +313,8 @@ export default function( css, options ) { } /** - * Parse keyframes. - */ + * Parse keyframes. + */ function atkeyframes() { const pos = position(); @@ -358,8 +357,8 @@ export default function( css, options ) { } /** - * Parse supports. - */ + * Parse supports. + */ function atsupports() { const pos = position(); @@ -388,8 +387,8 @@ export default function( css, options ) { } /** - * Parse host. - */ + * Parse host. + */ function athost() { const pos = position(); @@ -416,8 +415,8 @@ export default function( css, options ) { } /** - * Parse media. - */ + * Parse media. + */ function atmedia() { const pos = position(); @@ -446,8 +445,8 @@ export default function( css, options ) { } /** - * Parse custom-media. - */ + * Parse custom-media. + */ function atcustommedia() { const pos = position(); @@ -464,8 +463,8 @@ export default function( css, options ) { } /** - * Parse paged media. - */ + * Parse paged media. + */ function atpage() { const pos = position(); @@ -501,8 +500,8 @@ export default function( css, options ) { } /** - * Parse document. - */ + * Parse document. + */ function atdocument() { const pos = position(); @@ -533,8 +532,8 @@ export default function( css, options ) { } /** - * Parse font-face. - */ + * Parse font-face. + */ function atfontface() { const pos = position(); @@ -567,26 +566,26 @@ export default function( css, options ) { } /** - * Parse import - */ + * Parse import + */ const atimport = _compileAtrule( 'import' ); /** - * Parse charset - */ + * Parse charset + */ const atcharset = _compileAtrule( 'charset' ); /** - * Parse namespace - */ + * Parse namespace + */ const atnamespace = _compileAtrule( 'namespace' ); /** - * Parse non-block at-rules - */ + * Parse non-block at-rules + */ function _compileAtrule( name ) { const re = new RegExp( '^@' + name + '\\s*([^;]+);' ); @@ -603,8 +602,8 @@ export default function( css, options ) { } /** - * Parse at rule. - */ + * Parse at rule. + */ function atrule() { if ( css[ 0 ] !== '@' ) { @@ -625,8 +624,8 @@ export default function( css, options ) { } /** - * Parse rule. - */ + * Parse rule. + */ function rule() { const pos = position(); diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js index fb17a2f15e9ddb..1835dd6392583f 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js +++ b/packages/block-editor/src/utils/transform-styles/ast/stringify/compiler.js @@ -9,10 +9,6 @@ export default Compiler; /** * Initialize a compiler. - * - * @param {Type} name - * @return {Type} - * @api public */ function Compiler( opts ) { diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js index 9a5b995f059b0a..2dcc4d4475f02b 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js +++ b/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js @@ -17,8 +17,7 @@ import Identity from './identity'; * * @param {Object} node * @param {Object} [options] - * @return {String} - * @api public + * @return {string} */ export default function( node, options ) { diff --git a/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js b/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js index 5f454bd29ef3d4..4516fffa2196dc 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js +++ b/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js @@ -55,7 +55,6 @@ function isValidURL( meta ) { * * @param {string} str the url * @param {string} baseURL base URL - * @param {string} absolutePath the absolute path * * @return {string} the full path to the file */ diff --git a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js index 6c89562d78a9ae..d7fe709138fa83 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js +++ b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js @@ -4,7 +4,7 @@ import { includes } from 'lodash'; /** - * @const string IS_ROOT_TAG Regex to check if the selector is a root tag selector. + * @constant string IS_ROOT_TAG Regex to check if the selector is a root tag selector. */ const IS_ROOT_TAG = /^(body|html|:root).*$/; diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index d798f9f2115cb6..48f35e90b2d9ce 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -43,7 +43,7 @@ import { * * @constant * @type {string[]} -*/ + */ const ALLOWED_BLOCKS = [ 'core/column' ]; /** @@ -101,7 +101,7 @@ const TEMPLATE_OPTIONS = [ * Number of columns to assume for template in case the user opts to skip * template option selection. * - * @type {Number} + * @type {number} */ const DEFAULT_COLUMNS = 2; diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 80bcbed310e9a0..0096f1bc3cd98a 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -58,7 +58,7 @@ export const getPhotoHtml = ( photo ) => { return renderToString( photoPreview ); }; -/*** +/** * Creates a more suitable embed block based on the passed in props * and attributes generated from an embed block's preview. * @@ -68,8 +68,8 @@ export const getPhotoHtml = ( photo ) => { * versions, so we require that these are generated separately. * See `getAttributesFromPreview` in the generated embed edit component. * - * @param {Object} props The block's props. - * @param {Object} attributesFromPreview Attributes generated from the block's most up to date preview. + * @param {Object} props The block's props. + * @param {Object} attributesFromPreview Attributes generated from the block's most up to date preview. * @return {Object|undefined} A more suitable embed block if one exists. */ export const createUpgradedEmbedBlock = ( props, attributesFromPreview ) => { @@ -175,7 +175,7 @@ export function getClassNames( html, existingClassNames = '', allowResponsive = * Creates a paragraph block containing a link to the URL, and calls `onReplace`. * * @param {string} url The URL that could not be embedded. - * @param {function} onReplace Function to call with the created fallback block. + * @param {Function} onReplace Function to call with the created fallback block. */ export function fallback( url, onReplace ) { const link = <a href={ url }>{ url }</a>; diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 6c44a305a9375a..b86c0e282d7f9b 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -423,6 +423,7 @@ export class TableEdit extends Component { /** * Renders a table section. * + * @param {Object} options * @param {string} options.type Section type: head, body, or foot. * @param {Array} options.rows The rows to render. * diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 89fd1c0227ef35..f8ee18d7f2390b 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -8,6 +8,7 @@ const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; /** * Creates a table state. * + * @param {Object} options * @param {number} options.rowCount Row count for the table to create. * @param {number} options.columnCount Column count for the table to create. * @@ -141,6 +142,7 @@ export function isCellSelected( cellLocation, selection ) { * Inserts a row in the table state. * * @param {Object} state Current table state. + * @param {Object} options * @param {string} options.sectionName Section in which to insert the row. * @param {number} options.rowIndex Row index at which to insert the row. * @@ -183,6 +185,7 @@ export function insertRow( state, { * Deletes a row from the table state. * * @param {Object} state Current table state. + * @param {Object} options * @param {string} options.sectionName Section in which to delete the row. * @param {number} options.rowIndex Row index to delete. * @@ -201,6 +204,7 @@ export function deleteRow( state, { * Inserts a column in the table state. * * @param {Object} state Current table state. + * @param {Object} options * @param {number} options.columnIndex Column index at which to insert the column. * * @return {Object} New table state. @@ -241,6 +245,7 @@ export function insertColumn( state, { * Deletes a column from the table state. * * @param {Object} state Current table state. + * @param {Object} options * @param {number} options.columnIndex Column index to delete. * * @return {Object} New table state. diff --git a/packages/block-serialization-default-parser/src/index.js b/packages/block-serialization-default-parser/src/index.js index e885e57af6269e..d92fcfc5fc18bb 100644 --- a/packages/block-serialization-default-parser/src/index.js +++ b/packages/block-serialization-default-parser/src/index.js @@ -32,9 +32,9 @@ let stack; * not captured. thus, we find the long string of `a`s and remember it, then * reference it as a whole unit inside our pattern * - * @cite http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead - * @cite http://blog.stevenlevithan.com/archives/mimic-atomic-groups - * @cite https://javascript.info/regexp-infinite-backtracking-problem + * @see http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead + * @see http://blog.stevenlevithan.com/archives/mimic-atomic-groups + * @see https://javascript.info/regexp-infinite-backtracking-problem * * once browsers reliably support atomic grouping or possessive * quantifiers natively we should remove this trick and simplify diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 331be71c3a15f4..26e4ad5624edff 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -586,6 +586,7 @@ Converts an HTML string to known blocks. Strips everything else. _Parameters_ +- _options_ `Object`: - _options.HTML_ `[string]`: The HTML to convert. - _options.plainText_ `[string]`: Plain text version. - _options.mode_ `[string]`: Handle content as blocks or inline content. _ 'AUTO': Decide based on the content passed. _ 'INLINE': Always handle as inline content, and return string. \* 'BLOCKS': Always handle as blocks, and return array of blocks. @@ -602,6 +603,7 @@ Converts an HTML string to known blocks. _Parameters_ +- _$1_ `Object`: - _$1.HTML_ `string`: The HTML to convert. _Returns_ diff --git a/packages/blocks/src/api/raw-handling/image-corrector.native.js b/packages/blocks/src/api/raw-handling/image-corrector.native.js index d450ac7441730c..f25f6d3c8bc903 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.native.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.native.js @@ -5,7 +5,7 @@ * @param {Node} node The node to check. * * @return {void} -*/ + */ export default function( node ) { if ( node.nodeName !== 'IMG' ) { return; diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index 58044375d9dc31..37908f96d7d76a 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -81,6 +81,7 @@ function htmlToBlocks( { html, rawTransforms } ) { /** * Converts an HTML string to known blocks. * + * @param {Object} $1 * @param {string} $1.HTML The HTML to convert. * * @return {Array} A list of blocks. diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index c99b4ea5c5d0a2..fdea17bd0eba2f 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -113,6 +113,7 @@ function htmlToBlocks( { html, rawTransforms } ) { /** * Converts an HTML string to known blocks. Strips everything else. * + * @param {Object} options * @param {string} [options.HTML] The HTML to convert. * @param {string} [options.plainText] Plain text version. * @param {string} [options.mode] Handle content as blocks or inline content. diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index b88eec96ecac7e..024f9e9d55a3e3 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -36,7 +36,7 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; /** * An object describing a normalized block type icon. * - * @typedef {WPBlockTypeIconDescriptor} + * @typedef {Object} WPBlockTypeIconDescriptor * * @property {WPBlockTypeIconRender} src Render behavior of the icon, * one of a Dashicon slug, an @@ -60,23 +60,23 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; /** * Defined behavior of a block type. * - * @typedef {WPBlockType} - * - * @property {string} name Block type's namespaced name. - * @property {string} title Human-readable block type label. - * @property {string} category Block type category classification, - * used in search interfaces to arrange - * block types by category. - * @property {?WPBlockTypeIcon} icon Block type icon. - * @property {?string[]} keywords Additional keywords to produce block - * type as result in search interfaces. - * @property {?Object} attributes Block type attributes. - * @property {?WPComponent} save Optional component describing - * serialized markup structure of a - * block type. - * @property {WPComponent} edit Component rendering an element to - * manipulate the attributes of a block - * in the context of an editor. + * @typedef {Object} WPBlockType + * + * @property {string} name Block type's namespaced name. + * @property {string} title Human-readable block type label. + * @property {string} category Block type category classification, + * used in search interfaces to arrange + * block types by category. + * @property {WPBlockTypeIcon} [icon] Block type icon. + * @property {string[]} [keywords] Additional keywords to produce block + * type as result in search interfaces. + * @property {Object} [attributes] Block type attributes. + * @property {WPComponent} [save] Optional component describing + * serialized markup structure of a + * block type. + * @property {WPComponent} edit Component rendering an element to + * manipulate the attributes of a block + * in the context of an editor. */ /** diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 4e3131ca7b62e0..405e60109adda3 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -102,7 +102,7 @@ export function blockStyles( state = {}, action ) { * * @param {string} setActionType Action type. * - * @return {function} Reducer. + * @return {Function} Reducer. */ export function createBlockNameSetterReducer( setActionType ) { return ( state = null, action ) => { diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index 1226ec3bbfd7fe..0ae8e2b80bdb90 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -30,34 +30,35 @@ import withSpokenMessages from '../higher-order/with-spoken-messages'; /** * A raw completer option. + * * @typedef {*} CompleterOption */ /** * @callback FnGetOptions * - * @returns {(CompleterOption[]|Promise.<CompleterOption[]>)} The completer options or a promise for them. + * @return {(CompleterOption[]|Promise.<CompleterOption[]>)} The completer options or a promise for them. */ /** * @callback FnGetOptionKeywords * @param {CompleterOption} option a completer option. * - * @returns {string[]} list of key words to search. + * @return {string[]} list of key words to search. */ /** * @callback FnIsOptionDisabled * @param {CompleterOption} option a completer option. * - * @returns {string[]} whether or not the given option is disabled. + * @return {string[]} whether or not the given option is disabled. */ /** * @callback FnGetOptionLabel * @param {CompleterOption} option a completer option. * - * @returns {(string|Array.<(string|Component)>)} list of react components to render. + * @return {(string|Array.<(string|Component)>)} list of react components to render. */ /** @@ -65,35 +66,36 @@ import withSpokenMessages from '../higher-order/with-spoken-messages'; * @param {string} before the string before the auto complete trigger and query. * @param {string} after the string after the autocomplete trigger and query. * - * @returns {boolean} true if the completer can handle. + * @return {boolean} true if the completer can handle. */ /** * @typedef {Object} OptionCompletion - * @property {('insert-at-caret', 'replace')} action the intended placement of the completion. + * @property {'insert-at-caret'|'replace'} action the intended placement of the completion. * @property {OptionCompletionValue} value the completion value. */ /** * A completion value. - * @typedef {(String|WPElement|Object)} OptionCompletionValue + * + * @typedef {(string|WPElement|Object)} OptionCompletionValue */ /** * @callback FnGetOptionCompletion * @param {CompleterOption} value the value of the completer option. - * @param {String} query the text value of the autocomplete query. + * @param {string} query the text value of the autocomplete query. * - * @returns {(OptionCompletion|OptionCompletionValue)} the completion for the given option. If an + * @return {(OptionCompletion|OptionCompletionValue)} the completion for the given option. If an * OptionCompletionValue is returned, the * completion action defaults to `insert-at-caret`. */ /** * @typedef {Object} Completer - * @property {String} name a way to identify a completer, useful for selective overriding. - * @property {?String} className A class to apply to the popup menu. - * @property {String} triggerPrefix the prefix that will display the menu. + * @property {string} name a way to identify a completer, useful for selective overriding. + * @property {?string} className A class to apply to the popup menu. + * @property {string} triggerPrefix the prefix that will display the menu. * @property {(CompleterOption[]|FnGetOptions)} options the completer options or a function to get them. * @property {?FnGetOptionKeywords} getOptionKeywords get the keywords for a given option. * @property {?FnIsOptionDisabled} isOptionDisabled get whether or not the given option is disabled. diff --git a/packages/components/src/color-picker/index.js b/packages/components/src/color-picker/index.js index 3db7096c2e3d96..74c42f3cee8f8e 100644 --- a/packages/components/src/color-picker/index.js +++ b/packages/components/src/color-picker/index.js @@ -85,7 +85,7 @@ const isValidColor = ( colors ) => colors.hex ? * @param {Object} oldColors.draftHsl Same format as oldColors.hsl * @param {Object} data Data containing the new value to update. * @param {Object} data.source One of `hex`, `rgb`, `hsl`. - * @param {string\number} data.value Value to update. + * @param {string|number} data.value Value to update. * @param {string} data.valueKey Depends on `data.source` values: * - when source = `rgb`, valuKey can be `r`, `g`, `b`, or `a`. * - when source = `hsl`, valuKey can be `h`, `s`, `l`, or `a`. diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js index f0bc9bbc71a40e..ac077468d83777 100644 --- a/packages/components/src/draggable/index.js +++ b/packages/components/src/draggable/index.js @@ -66,8 +66,6 @@ class Draggable extends Component { * - Adds dragover listener. * * @param {Object} event The non-custom DragEvent. - * @param {string} elementId The HTML id of the element to be dragged. - * @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic. */ onDragStart( event ) { const { elementId, transferData, onDragStart = noop } = this.props; diff --git a/packages/components/src/higher-order/with-notices/index.js b/packages/components/src/higher-order/with-notices/index.js index bcdb43b1a2e876..49a44608039ce1 100644 --- a/packages/components/src/higher-order/with-notices/index.js +++ b/packages/components/src/higher-order/with-notices/index.js @@ -17,7 +17,7 @@ import NoticeList from '../../notice/list'; /** * Override the default edit UI to include notices if supported. * - * @param {function|Component} OriginalComponent Original component. + * @param {Function|Component} OriginalComponent Original component. * @return {Component} Wrapped component. */ export default createHigherOrderComponent( ( OriginalComponent ) => { @@ -43,10 +43,10 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { } /** - * Function passed down as a prop that adds a new notice. - * - * @param {Object} notice Notice to add. - */ + * Function passed down as a prop that adds a new notice. + * + * @param {Object} notice Notice to add. + */ createNotice( notice ) { const noticeToAdd = notice.id ? notice : { ...notice, id: uuid() }; this.setState( ( state ) => ( { @@ -55,19 +55,19 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { } /** - * Function passed as a prop that adds a new error notice. - * - * @param {string} msg Error message of the notice. - */ + * Function passed as a prop that adds a new error notice. + * + * @param {string} msg Error message of the notice. + */ createErrorNotice( msg ) { this.createNotice( { status: 'error', content: msg } ); } /** - * Removes a notice by id. - * - * @param {string} id Id of the notice to remove. - */ + * Removes a notice by id. + * + * @param {string} id Id of the notice to remove. + */ removeNotice( id ) { this.setState( ( state ) => ( { noticeList: state.noticeList.filter( ( notice ) => notice.id !== id ), @@ -75,8 +75,8 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { } /** - * Removes all notices - */ + * Removes all notices + */ removeAllNotices() { this.setState( { noticeList: [], diff --git a/packages/components/src/notice/list.js b/packages/components/src/notice/list.js index dcb39dc49b5828..64c68cbaede0e3 100644 --- a/packages/components/src/notice/list.js +++ b/packages/components/src/notice/list.js @@ -10,15 +10,15 @@ import { noop, omit } from 'lodash'; import Notice from './'; /** -* Renders a list of notices. -* -* @param {Object} $0 Props passed to the component. -* @param {Array} $0.notices Array of notices to render. -* @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. -* @param {Object} $0.className Name of the class used by the component. -* @param {Object} $0.children Array of children to be rendered inside the notice list. -* @return {Object} The rendered notices list. -*/ + * Renders a list of notices. + * + * @param {Object} $0 Props passed to the component. + * @param {Array} $0.notices Array of notices to render. + * @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. + * @param {Object} $0.className Name of the class used by the component. + * @param {Object} $0.children Array of children to be rendered inside the notice list. + * @return {Object} The rendered notices list. + */ function NoticeList( { notices, onRemove = noop, className, children } ) { const removeNotice = ( id ) => () => onRemove( id ); diff --git a/packages/components/src/placeholder/index.js b/packages/components/src/placeholder/index.js index 8e49b70d5226ec..3a7c40c993001f 100644 --- a/packages/components/src/placeholder/index.js +++ b/packages/components/src/placeholder/index.js @@ -10,11 +10,11 @@ import { isString } from 'lodash'; import Dashicon from '../dashicon'; /** -* Renders a placeholder. Normally used by blocks to render their empty state. -* -* @param {Object} props The component props. -* @return {Object} The rendered placeholder. -*/ + * Renders a placeholder. Normally used by blocks to render their empty state. + * + * @param {Object} props The component props. + * @return {Object} The rendered placeholder. + */ function Placeholder( { icon, children, label, instructions, className, notices, preview, isColumnLayout, ...additionalProps } ) { const classes = classnames( 'components-placeholder', className ); const fieldsetClasses = classnames( 'components-placeholder__fieldset', { 'is-column-layout': isColumnLayout } ); diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 1dad7be985dfb5..a6aff279f0b151 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -30,14 +30,14 @@ const FocusManaged = withConstrainedTabbing( withFocusReturn( ( { children } ) = /** * Name of slot in which popover should fill. * - * @type {String} + * @type {string} */ const SLOT_NAME = 'Popover'; /** * Hook used trigger an event handler once the window is resized or scrolled. * - * @param {function} handler Event handler. + * @param {Function} handler Event handler. * @param {Object} ignoredScrollalbeRef scroll events inside this element are ignored. */ function useThrottledWindowScrollOrResize( handler, ignoredScrollalbeRef ) { diff --git a/packages/components/src/popover/utils.js b/packages/components/src/popover/utils.js index 292f7ab3276775..1b71fab09aa574 100644 --- a/packages/components/src/popover/utils.js +++ b/packages/components/src/popover/utils.js @@ -13,7 +13,6 @@ const isRTL = () => document.documentElement.dir === 'rtl'; * @param {Object} contentSize Content Size. * @param {string} xAxis Desired xAxis. * @param {string} chosenYAxis yAxis to be used. - * @param {boolean} expandOnMobile Whether to expand the popover on mobile or not. * * @return {Object} Popover xAxis position and constraints. */ @@ -83,7 +82,6 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cho * @param {Object} anchorRect Anchor Rect. * @param {Object} contentSize Content Size. * @param {string} yAxis Desired yAxis. - * @param {boolean} expandOnMobile Whether to expand the popover on mobile or not. * * @return {Object} Popover xAxis position and constraints. */ diff --git a/packages/components/src/snackbar/list.js b/packages/components/src/snackbar/list.js index ca9060560ad0a2..f3cb5d544fcad5 100644 --- a/packages/components/src/snackbar/list.js +++ b/packages/components/src/snackbar/list.js @@ -17,15 +17,15 @@ import { useState } from '@wordpress/element'; import Snackbar from './'; /** -* Renders a list of notices. -* -* @param {Object} $0 Props passed to the component. -* @param {Array} $0.notices Array of notices to render. -* @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. -* @param {Object} $0.className Name of the class used by the component. -* @param {Object} $0.children Array of children to be rendered inside the notice list. -* @return {Object} The rendered notices list. -*/ + * Renders a list of notices. + * + * @param {Object} $0 Props passed to the component. + * @param {Array} $0.notices Array of notices to render. + * @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. + * @param {Object} $0.className Name of the class used by the component. + * @param {Object} $0.children Array of children to be rendered inside the notice list. + * @return {Object} The rendered notices list. + */ function SnackbarList( { notices, className, children, onRemove = noop } ) { const isReducedMotion = useReducedMotion(); const [ refMap ] = useState( () => new WeakMap() ); diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.js index 3dc1f3c1d4451a..ca41d32bb6e1b9 100644 --- a/packages/components/src/toolbar/index.js +++ b/packages/components/src/toolbar/index.js @@ -34,10 +34,11 @@ import ToolbarContainer from './toolbar-container'; * Either `controls` or `children` is required, otherwise this components * renders nothing. * - * @param {?Array} controls The controls to render in this toolbar. - * @param {?ReactElement} children Any other things to render inside the - * toolbar besides the controls. - * @param {?string} className Class to set on the container div. + * @param {Object} props + * @param {Array} [props.controls] The controls to render in this toolbar. + * @param {ReactElement} [props.children] Any other things to render inside the + * toolbar besides the controls. + * @param {string} [props.className] Class to set on the container div. * * @return {ReactElement} The rendered toolbar. */ diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index 9c7d0acb800ad6..fd52b17ca487df 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -22,7 +22,7 @@ import Shortcut from '../shortcut'; /** * Time over children to wait before showing tooltip * - * @type {Number} + * @type {number} */ const TOOLTIP_DELAY = 700; diff --git a/packages/core-data/src/controls.js b/packages/core-data/src/controls.js index 6df3d092094f8b..84644e76541ac9 100644 --- a/packages/core-data/src/controls.js +++ b/packages/core-data/src/controls.js @@ -19,6 +19,7 @@ export function apiFetch( request ) { /** * Calls a selector using the current state. + * * @param {string} selectorName Selector name. * @param {Array} args Selector arguments. * diff --git a/packages/core-data/src/queried-data/get-query-parts.js b/packages/core-data/src/queried-data/get-query-parts.js index d13490e42a72f5..de8a5311619ddd 100644 --- a/packages/core-data/src/queried-data/get-query-parts.js +++ b/packages/core-data/src/queried-data/get-query-parts.js @@ -11,7 +11,7 @@ import { withWeakMapCache } from '../utils'; /** * An object of properties describing a specific query. * - * @typedef {WPQueriedDataQueryParts} + * @typedef {Object} WPQueriedDataQueryParts * * @property {number} page The query page (1-based index, default 1). * @property {number} perPage Items per page for query (default 10). diff --git a/packages/data-controls/src/index.js b/packages/data-controls/src/index.js index c209b3397b4fad..27dc772545bbca 100644 --- a/packages/data-controls/src/index.js +++ b/packages/data-controls/src/index.js @@ -95,10 +95,11 @@ export function dispatch( storeKey, actionName, ...args ) { /** * Utility for returning a promise that handles a selector with a resolver. * - * @param {Object} registry The data registry. - * @param {string} storeKey The store the selector belongs to - * @param {string} selectorName The selector name - * @param {Array} args The arguments fed to the selector + * @param {Object} registry The data registry. + * @param {Object} options + * @param {string} options.storeKey The store the selector belongs to + * @param {string} options.selectorName The selector name + * @param {Array} options.args The arguments fed to the selector * * @return {Promise} A promise for resolving the given selector. */ diff --git a/packages/data/README.md b/packages/data/README.md index 0e117eca77d876..a76019371e725a 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -312,11 +312,11 @@ Mark a control as a registry control. _Parameters_ -- _registryControl_ `function`: Function receiving a registry object and returning a control. +- _registryControl_ `Function`: Function receiving a registry object and returning a control. _Returns_ -- `function`: marked registry control. +- `Function`: marked registry control. <a name="createRegistrySelector" href="#createRegistrySelector">#</a> **createRegistrySelector** @@ -324,11 +324,11 @@ Mark a selector as a registry selector. _Parameters_ -- _registrySelector_ `function`: Function receiving a registry object and returning a state selector. +- _registrySelector_ `Function`: Function receiving a registry object and returning a state selector. _Returns_ -- `function`: marked registry selector. +- `Function`: marked registry selector. <a name="dispatch" href="#dispatch">#</a> **dispatch** diff --git a/packages/data/src/factory.js b/packages/data/src/factory.js index 26fceb9bd456d5..2a10c7a8e7afc1 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.js @@ -10,9 +10,9 @@ import defaultRegistry from './default-registry'; /** * Mark a selector as a registry selector. * - * @param {function} registrySelector Function receiving a registry object and returning a state selector. + * @param {Function} registrySelector Function receiving a registry object and returning a state selector. * - * @return {function} marked registry selector. + * @return {Function} marked registry selector. */ export function createRegistrySelector( registrySelector ) { const selector = ( ...args ) => registrySelector( selector.registry.select )( ...args ); @@ -39,9 +39,9 @@ export function createRegistrySelector( registrySelector ) { /** * Mark a control as a registry control. * - * @param {function} registryControl Function receiving a registry object and returning a control. + * @param {Function} registryControl Function receiving a registry object and returning a control. * - * @return {function} marked registry control. + * @return {Function} marked registry control. */ export function createRegistryControl( registryControl ) { registryControl.isRegistryControl = true; diff --git a/packages/data/src/index.js b/packages/data/src/index.js index da00faec053d41..8dd6893c5d9e01 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -133,11 +133,11 @@ export const dispatch = defaultRegistry.dispatch; export const subscribe = defaultRegistry.subscribe; /** -* Registers a generic store. -* -* @param {string} key Store registry key. -* @param {Object} config Configuration (getSelectors, getActions, subscribe). -*/ + * Registers a generic store. + * + * @param {string} key Store registry key. + * @param {Object} config Configuration (getSelectors, getActions, subscribe). + */ export const registerGenericStore = defaultRegistry.registerGenericStore; /** diff --git a/packages/data/src/namespace-store/metadata/reducer.js b/packages/data/src/namespace-store/metadata/reducer.js index 46ddc183563ae0..17d271ec9d365d 100644 --- a/packages/data/src/namespace-store/metadata/reducer.js +++ b/packages/data/src/namespace-store/metadata/reducer.js @@ -18,7 +18,7 @@ import { onSubKey } from './utils'; * @param {Object} state Current state. * @param {Object} action Dispatched action. * - * @returns {Object} Next state. + * @return {Object} Next state. */ const subKeysIsResolved = flowRight( [ onSubKey( 'selectorName' ), diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index a21fcb9b442fe2..a0c910f8a73bca 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -10,14 +10,13 @@ import defaultStorage from './storage/default'; import { combineReducers } from '../../'; /** - * Persistence plugin options. + * @typedef {Object} WPDataPersistencePluginOptions Persistence plugin options. * * @property {Storage} storage Persistent storage implementation. This must * at least implement `getItem` and `setItem` of * the Web Storage API. * @property {string} storageKey Key on which to set in persistent storage. * - * @typedef {WPDataPersistencePluginOptions} */ /** diff --git a/packages/data/src/promise-middleware.js b/packages/data/src/promise-middleware.js index 832ecbaefdf187..16cd4ccf097443 100644 --- a/packages/data/src/promise-middleware.js +++ b/packages/data/src/promise-middleware.js @@ -6,7 +6,7 @@ import isPromise from 'is-promise'; /** * Simplest possible promise redux middleware. * - * @return {function} middleware. + * @return {Function} middleware. */ const promiseMiddleware = () => ( next ) => ( action ) => { if ( isPromise( action ) ) { diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index 97e5212f2a24ab..ecaaa94c8e64d5 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -13,9 +13,7 @@ import createNamespace from './namespace-store'; import createCoreDataStore from './store'; /** - * An isolated orchestrator of store registrations. - * - * @typedef {Object} WPDataRegistry + * @typedef {Object} WPDataRegistry An isolated orchestrator of store registrations. * * @property {Function} registerGenericStore Given a namespace key and settings * object, registers a new generic @@ -35,9 +33,7 @@ import createCoreDataStore from './store'; */ /** - * An object of registry function overrides. - * - * @typedef {WPDataPlugin} + * @typedef {Object} WPDataPlugin An object of registry function overrides. */ /** diff --git a/packages/date/src/index.js b/packages/date/src/index.js index 26501b394200b6..14aa677d2e4218 100644 --- a/packages/date/src/index.js +++ b/packages/date/src/index.js @@ -105,19 +105,19 @@ function setupWPTimezone() { /** * Number of seconds in one minute. * - * @type {Number} + * @type {number} */ const MINUTE_IN_SECONDS = 60; /** * Number of minutes in one hour. * - * @type {Number} + * @type {number} */ const HOUR_IN_MINUTES = 60; /** * Number of seconds in one hour. * - * @type {Number} + * @type {number} */ const HOUR_IN_SECONDS = 60 * MINUTE_IN_SECONDS; diff --git a/packages/dependency-extraction-webpack-plugin/util.js b/packages/dependency-extraction-webpack-plugin/util.js index b8b7238fe2a9a1..88db5088e3b88f 100644 --- a/packages/dependency-extraction-webpack-plugin/util.js +++ b/packages/dependency-extraction-webpack-plugin/util.js @@ -4,8 +4,9 @@ const WORDPRESS_NAMESPACE = '@wordpress/'; * Default request to global transformation * * Transform @wordpress dependencies: - * @wordpress/api-fetch -> wp.apiFetch - * @wordpress/i18n -> wp.i18n + * + * request `@wordpress/api-fetch` becomes `wp.apiFetch` + * request `@wordpress/i18n` becomes `wp.i18n` * * @param {string} request Requested module * @@ -42,8 +43,9 @@ function defaultRequestToExternal( request ) { * Default request to WordPress script handle transformation * * Transform @wordpress dependencies: - * @wordpress/i18n -> wp-i18n - * @wordpress/escape-html -> wp-escape-html + * + * request `@wordpress/i18n` becomes `wp-i18n` + * request `@wordpress/escape-html` becomes `wp-escape-html` * * @param {string} request Requested module * diff --git a/packages/docgen/src/engine.js b/packages/docgen/src/engine.js index 0c01f94ef9b505..e2510af0517dad 100644 --- a/packages/docgen/src/engine.js +++ b/packages/docgen/src/engine.js @@ -1,12 +1,12 @@ /** -* External dependencies. -*/ + * External dependencies. + */ const espree = require( 'espree' ); const { flatten } = require( 'lodash' ); /** -* Internal dependencies. -*/ + * Internal dependencies. + */ const getIntermediateRepresentation = require( './get-intermediate-representation' ); const getAST = ( source ) => espree.parse( source, { diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index d932adca1f1ad8..71c4380f8a9387 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -82,7 +82,7 @@ _Parameters_ _Returns_ -- `function`: Function that determines if a request is for the embed API, embedding a specific URL. +- `Function`: Function that determines if a request is for the embed API, embedding a specific URL. <a name="createJSONResponse" href="#createJSONResponse">#</a> **createJSONResponse** @@ -127,7 +127,7 @@ _Parameters_ _Returns_ -- `function`: Function that determines if a request's URL contains substring. +- `Function`: Function that determines if a request's URL contains substring. <a name="deactivatePlugin" href="#deactivatePlugin">#</a> **deactivatePlugin** @@ -315,9 +315,9 @@ deserialised JSON response for the request. _Parameters_ -- _mockCheck_ `function`: function that returns true if the request should be mocked. +- _mockCheck_ `Function`: function that returns true if the request should be mocked. - _mock_ `Object`: A mock object to wrap in a JSON response, if the request should be mocked. -- _responseObjectTransform_ `(function|undefined)`: An optional function that transforms the response's object before the response is used. +- _responseObjectTransform_ `(Function|undefined)`: An optional function that transforms the response's object before the response is used. _Returns_ diff --git a/packages/e2e-test-utils/src/are-pre-publish-checks-enabled.js b/packages/e2e-test-utils/src/are-pre-publish-checks-enabled.js index 3aab4928850edd..70c17503ceb5b2 100644 --- a/packages/e2e-test-utils/src/are-pre-publish-checks-enabled.js +++ b/packages/e2e-test-utils/src/are-pre-publish-checks-enabled.js @@ -5,6 +5,7 @@ import { wpDataSelect } from './wp-data-select'; /** * Verifies if publish checks are enabled. + * * @return {boolean} Boolean which represents the state of prepublish checks. */ export function arePrePublishChecksEnabled() { diff --git a/packages/e2e-test-utils/src/get-block-setting.js b/packages/e2e-test-utils/src/get-block-setting.js index 0b4bff9dd7e14a..e0adf27eaebedf 100644 --- a/packages/e2e-test-utils/src/get-block-setting.js +++ b/packages/e2e-test-utils/src/get-block-setting.js @@ -1,5 +1,6 @@ /** * Returns a string containing the block title associated with the provided block name. + * * @param {string} blockName Block name. * @param {string} setting Block setting e.g: title, attributes.... * diff --git a/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js b/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js index 66e1b301cbe0f5..9673ea04706d4a 100644 --- a/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js +++ b/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js @@ -8,7 +8,7 @@ import { createURLMatcher } from './create-url-matcher'; * * @param {string} parameterName The query parameter to check. * @param {string} value The value to check for. - * @return {function} Function that determines if a request's query parameter is the specified value. + * @return {Function} Function that determines if a request's query parameter is the specified value. */ function parameterEquals( parameterName, value ) { return ( request ) => { @@ -25,7 +25,7 @@ function parameterEquals( parameterName, value ) { * Creates a function to determine if a request is embedding a certain URL. * * @param {string} url The URL to check against a request. - * @return {function} Function that determines if a request is for the embed API, embedding a specific URL. + * @return {Function} Function that determines if a request is for the embed API, embedding a specific URL. */ export function createEmbeddingMatcher( url ) { return ( request ) => diff --git a/packages/e2e-test-utils/src/mocks/create-url-matcher.js b/packages/e2e-test-utils/src/mocks/create-url-matcher.js index db299a78641330..77c28d129b298f 100644 --- a/packages/e2e-test-utils/src/mocks/create-url-matcher.js +++ b/packages/e2e-test-utils/src/mocks/create-url-matcher.js @@ -2,7 +2,7 @@ * Creates a function to determine if a request is calling a URL with the substring present. * * @param {string} substring The substring to check for. - * @return {function} Function that determines if a request's URL contains substring. + * @return {Function} Function that determines if a request's URL contains substring. */ export function createURLMatcher( substring ) { return ( request ) => -1 !== request.url().indexOf( substring ); diff --git a/packages/e2e-test-utils/src/mocks/mock-or-transform.js b/packages/e2e-test-utils/src/mocks/mock-or-transform.js index 0f858aabe56ca5..12cf8af07b24d2 100644 --- a/packages/e2e-test-utils/src/mocks/mock-or-transform.js +++ b/packages/e2e-test-utils/src/mocks/mock-or-transform.js @@ -12,9 +12,9 @@ import { getJSONResponse } from '../shared/get-json-response'; * Mocks a request with the supplied mock object, or allows it to run with an optional transform, based on the * deserialised JSON response for the request. * - * @param {function} mockCheck function that returns true if the request should be mocked. + * @param {Function} mockCheck function that returns true if the request should be mocked. * @param {Object} mock A mock object to wrap in a JSON response, if the request should be mocked. - * @param {function|undefined} responseObjectTransform An optional function that transforms the response's object before the response is used. + * @param {Function|undefined} responseObjectTransform An optional function that transforms the response's object before the response is used. * @return {Promise} Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. */ export function mockOrTransform( diff --git a/packages/e2e-test-utils/src/set-post-content.js b/packages/e2e-test-utils/src/set-post-content.js index 18e47c55820033..3e7611ba16c5af 100644 --- a/packages/e2e-test-utils/src/set-post-content.js +++ b/packages/e2e-test-utils/src/set-post-content.js @@ -1,5 +1,6 @@ /** * Sets code editor content + * * @param {string} content New code editor content. * * @return {Promise} Promise resolving with an array containing all blocks in the document. diff --git a/packages/e2e-test-utils/src/wp-data-select.js b/packages/e2e-test-utils/src/wp-data-select.js index fac2bebcb3c1cb..05b92b0a6dbbec 100644 --- a/packages/e2e-test-utils/src/wp-data-select.js +++ b/packages/e2e-test-utils/src/wp-data-select.js @@ -1,5 +1,6 @@ /** * Queries the WordPress data module. + * * @param {string} store Store to query e.g: core/editor, core/blocks... * @param {string} selector Selector to exectute e.g: getBlocks. * @param {...Object} parameters Parameters to pass to the selector. diff --git a/packages/edit-post/src/components/editor-initialization/utils.js b/packages/edit-post/src/components/editor-initialization/utils.js index 7dfd1ea08ed892..11935236c83440 100644 --- a/packages/edit-post/src/components/editor-initialization/utils.js +++ b/packages/edit-post/src/components/editor-initialization/utils.js @@ -2,11 +2,11 @@ * Given a selector returns a functions that returns the listener only * if the returned value from the selector changes. * - * @param {function} selector Selector. - * @param {function} listener Listener. + * @param {Function} selector Selector. + * @param {Function} listener Listener. * @param {boolean} initial Flags whether listener should be invoked on * initial call. - * @return {function} Listener creator. + * @return {Function} Listener creator. */ export const onChangeListener = ( selector, listener, initial = false ) => { let previousValue = selector(); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index d771dc840d8049..c96343ccae6750 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -108,7 +108,7 @@ export function toggleEditorPanelEnabled( panelName ) { * @param {string} panelName A string that identifies the panel to open or close. * * @return {Object} Action object. -*/ + */ export function toggleEditorPanelOpened( panelName ) { return { type: 'TOGGLE_PANEL_OPENED', diff --git a/packages/edit-post/src/store/constants.js b/packages/edit-post/src/store/constants.js index 35acac0c5633a8..d3c02c71f312ee 100644 --- a/packages/edit-post/src/store/constants.js +++ b/packages/edit-post/src/store/constants.js @@ -1,17 +1,20 @@ /** * The identifier for the data store. + * * @type {string} */ export const STORE_KEY = 'core/edit-post'; /** * CSS selector string for the admin bar view post link anchor tag. + * * @type {string} */ export const VIEW_AS_LINK_SELECTOR = '#wp-admin-bar-view a'; /** * CSS selector string for the admin bar preview post link anchor tag. + * * @type {string} */ export const VIEW_AS_PREVIEW_LINK_SELECTOR = '#wp-admin-bar-preview a'; diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index e2bcfe6f69094c..bff01d883b4d82 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -15,7 +15,7 @@ const WIDGET_AREAS_SAVE_NOTICE_ID = 'WIDGET_AREAS_SAVE_NOTICE_ID'; /** * Yields an action object that setups the widget areas. * - * @yields {Object} Action object. + * @yield {Object} Action object. */ export function* setupWidgetAreas() { const widgetAreas = yield select( @@ -54,7 +54,7 @@ export function updateBlocksInWidgetArea( widgetAreaId, blocks = [] ) { /** * Action that performs the logic to save widget areas. * - * @yields {Object} Action object. + * @yield {Object} Action object. */ export function* saveWidgetAreas() { const widgetAreas = yield select( diff --git a/packages/edit-widgets/src/store/constants.js b/packages/edit-widgets/src/store/constants.js index 4968386ea38ea1..6850993d9863db 100644 --- a/packages/edit-widgets/src/store/constants.js +++ b/packages/edit-widgets/src/store/constants.js @@ -1,5 +1,6 @@ /** * Constant for the store module (or reducer) key. + * * @type {string} */ export const STORE_KEY = 'core/edit-widgets'; diff --git a/packages/editor/src/components/autocompleters/user.js b/packages/editor/src/components/autocompleters/user.js index 0ca90dad349675..e3965a33f28d6f 100644 --- a/packages/editor/src/components/autocompleters/user.js +++ b/packages/editor/src/components/autocompleters/user.js @@ -4,10 +4,10 @@ import apiFetch from '@wordpress/api-fetch'; /** -* A user mentions completer. -* -* @type {Completer} -*/ + * A user mentions completer. + * + * @type {Completer} + */ export default { name: 'users', className: 'editor-autocompleters__user', diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index b09f50b431269d..a715c0fd3da5aa 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -83,7 +83,7 @@ function writeInterstitialMessage( targetDocument ) { /** * Filters the interstitial message shown when generating previews. * - * @param {String} markup The preview interstitial markup. + * @param {string} markup The preview interstitial markup. */ markup = applyFilters( 'editor.PostPreview.interstitialMarkup', markup ); diff --git a/packages/editor/src/components/post-type-support-check/index.js b/packages/editor/src/components/post-type-support-check/index.js index 9083ae0035c6a4..8096e4ccf7f8d9 100644 --- a/packages/editor/src/components/post-type-support-check/index.js +++ b/packages/editor/src/components/post-type-support-check/index.js @@ -12,7 +12,8 @@ import { withSelect } from '@wordpress/data'; * A component which renders its own children only if the current editor post * type supports one of the given `supportKeys` prop. * - * @param {?Object} props.postType Current post type. + * @param {Object} props + * @param {string} [props.postType] Current post type. * @param {WPElement} props.children Children to be rendered if post * type supports. * @param {(string|string[])} props.supportKeys String or string array of keys diff --git a/packages/editor/src/store/actions.native.js b/packages/editor/src/store/actions.native.js index 3d638cbc2be2cf..51741ac9bc3b44 100644 --- a/packages/editor/src/store/actions.native.js +++ b/packages/editor/src/store/actions.native.js @@ -5,7 +5,7 @@ export * from './actions.js'; * Returns an action object that enables or disables post title selection. * * @param {boolean} [isSelected=true] Whether post title is currently selected. - + * * @return {Object} Action object. */ export function togglePostTitleSelection( isSelected = true ) { diff --git a/packages/editor/src/store/constants.js b/packages/editor/src/store/constants.js index 8b9645c8d83099..6d12c68cc2af66 100644 --- a/packages/editor/src/store/constants.js +++ b/packages/editor/src/store/constants.js @@ -10,6 +10,7 @@ export const EDIT_MERGE_PROPERTIES = new Set( [ /** * Constant for the store module (or reducer) key. + * * @type {string} */ export const STORE_KEY = 'core/editor'; diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 6676b74dcbc395..ef6ad6fd798c07 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -126,7 +126,7 @@ export function shouldOverwriteState( action, previousAction ) { * @param {Object} state Current state. * @param {Object} action Dispatched action. * - * @returns {Object} Updated state. + * @return {Object} Updated state. */ export const editor = flow( [ combineReducers, diff --git a/packages/element/README.md b/packages/element/README.md index 052ea244540560..0b96812eacb539 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -234,7 +234,9 @@ aside from `children` are passed. _Parameters_ +- _props_ `Object`: - _props.children_ `string`: HTML to render. +- _props.props_ `Object`: Any additonal props to be set on the containing div. _Returns_ diff --git a/packages/element/src/raw-html.js b/packages/element/src/raw-html.js index d985c9376d4a26..2fa3618c417d88 100644 --- a/packages/element/src/raw-html.js +++ b/packages/element/src/raw-html.js @@ -9,7 +9,9 @@ import { createElement } from './react'; * To preserve additional props, a `div` wrapper _will_ be created if any props * aside from `children` are passed. * + * @param {Object} props * @param {string} props.children HTML to render. + * @param {Object} props.props Any additonal props to be set on the containing div. * * @return {WPElement} Dangerously-rendering element. */ diff --git a/packages/eslint-plugin/rules/gutenberg-phase.js b/packages/eslint-plugin/rules/gutenberg-phase.js index 98c7a4216a4f73..168ce1ff782d03 100644 --- a/packages/eslint-plugin/rules/gutenberg-phase.js +++ b/packages/eslint-plugin/rules/gutenberg-phase.js @@ -3,7 +3,7 @@ * the predicate returns a truthy value for. * * @param {Object} sourceNode The AST node to search from. - * @param {function} predicate A predicate invoked for each parent. + * @param {Function} predicate A predicate invoked for each parent. * * @return {?Object } The first encountered parent node where the predicate * returns a truthy value. diff --git a/packages/format-library/src/link/utils.js b/packages/format-library/src/link/utils.js index d9115cbcfe0996..58f64ab71ce41b 100644 --- a/packages/format-library/src/link/utils.js +++ b/packages/format-library/src/link/utils.js @@ -83,9 +83,10 @@ export function isValidHref( href ) { /** * Generates the format object that will be applied to the link text. * - * @param {string} url The href of the link. - * @param {boolean} opensInNewWindow Whether this link will open in a new window. - * @param {Object} text The text that is being hyperlinked. + * @param {Object} options + * @param {string} options.url The href of the link. + * @param {boolean} options.opensInNewWindow Whether this link will open in a new window. + * @param {Object} options.text The text that is being hyperlinked. * * @return {Object} The final format object. */ diff --git a/packages/media-utils/src/components/media-upload/index.js b/packages/media-utils/src/components/media-upload/index.js index f97b6b1f432985..7dfd0d9812932d 100644 --- a/packages/media-utils/src/components/media-upload/index.js +++ b/packages/media-utils/src/components/media-upload/index.js @@ -18,7 +18,7 @@ const getGalleryDetailsMediaFrame = () => { * * @see https://github.com/xwp/wp-core-media-widgets/blob/905edbccfc2a623b73a93dac803c5335519d7837/wp-admin/js/widgets/media-gallery-widget.js * @class GalleryDetailsMediaFrame - * @constructor + * @class */ return wp.media.view.MediaFrame.Post.extend( { diff --git a/packages/notices/src/store/selectors.js b/packages/notices/src/store/selectors.js index 9ba3cec0e63a06..b5daf3e024ccde 100644 --- a/packages/notices/src/store/selectors.js +++ b/packages/notices/src/store/selectors.js @@ -15,7 +15,7 @@ import { DEFAULT_CONTEXT } from './constants'; const DEFAULT_NOTICES = []; /** - * Notice object. + * @typedef {Object} WPNotice Notice object. * * @property {string} id Unique identifier of notice. * @property {string} status Status of notice, one of `success`, @@ -31,11 +31,10 @@ const DEFAULT_NOTICES = []; * user. Defaults to `true`. * @property {WPNoticeAction[]} actions User actions to present with notice. * - * @typedef {WPNotice} */ /** - * Object describing a user action option associated with a notice. + * @typedef {Object} WPNoticeAction Object describing a user action option associated with a notice. * * @property {string} label Message to use as action label. * @property {?string} url Optional URL of resource if action incurs @@ -43,7 +42,6 @@ const DEFAULT_NOTICES = []; * @property {?Function} callback Optional function to invoke when action is * triggered by user. * - * @typedef {WPNoticeAction} */ /** diff --git a/packages/redux-routine/src/runtime.js b/packages/redux-routine/src/runtime.js index ec984a148435e2..204f4c10eb7ffd 100644 --- a/packages/redux-routine/src/runtime.js +++ b/packages/redux-routine/src/runtime.js @@ -14,9 +14,9 @@ import { isActionOfType, isAction } from './is-action'; * Create a co-routine runtime. * * @param {Object} controls Object of control handlers. - * @param {function} dispatch Unhandled action dispatch. + * @param {Function} dispatch Unhandled action dispatch. * - * @return {function} co-routine runtime + * @return {Function} co-routine runtime */ export default function createRuntime( controls = {}, dispatch ) { const rungenControls = map( controls, ( control, actionType ) => ( value, next, iterate, yieldNext, yieldError ) => { diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index a29d9d3f0dac3f..0b9187685ed7ee 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -302,9 +302,13 @@ Indices are retrieved from the selection if none are provided. _Parameters_ -- _value_ `Object`: Value to modify. +- _value_ `Object`: +- _value.formats_ `Array<Object>`: +- _value.replacements_ `Array<Object>`: +- _value.text_ `string`: +- _value.start_ `number`: +- _value.end_ `number`: - _string_ `[(number|string)]`: Start index, or string at which to split. -- _endStr_ `[number]`: End index. _Returns_ diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index fcbb04c664d864..513cb480279049 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -846,11 +846,11 @@ class RichText extends Component { } /** - * Converts the internal value to the external data format. - * - * @param {Object} value The internal rich-text value. - * @return {*} The external data format, data type depends on props. - */ + * Converts the internal value to the external data format. + * + * @param {Object} value The internal rich-text value. + * @return {*} The external data format, data type depends on props. + */ valueToFormat( value ) { value = this.removeEditorOnlyFormats( value ); diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 441095f59d39fd..1cf45d08548bf0 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -47,7 +47,7 @@ const unescapeSpaces = ( text ) => { * Calls {@link pasteHandler} with a fallback to plain text when HTML processing * results in errors * - * @param {function} originalPasteHandler The original handler function + * @param {Function} originalPasteHandler The original handler function * @param {Object} [options] The options to pass to {@link pasteHandler} * * @return {Array|string} A list of blocks or a string, depending on diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js index f79a675b1e1f4f..05ef87fbe0c4f5 100644 --- a/packages/rich-text/src/split.js +++ b/packages/rich-text/src/split.js @@ -9,9 +9,13 @@ import { replace } from './replace'; * split at the given separator. This is similar to `String.prototype.split`. * Indices are retrieved from the selection if none are provided. * - * @param {Object} value Value to modify. + * @param {Object} value + * @param {Object[]} value.formats + * @param {Object[]} value.replacements + * @param {string} value.text + * @param {number} value.start + * @param {number} value.end * @param {number|string} [string] Start index, or string at which to split. - * @param {number} [endStr] End index. * * @return {Array} An array of new values. */ diff --git a/packages/wordcount/README.md b/packages/wordcount/README.md index d987ddfeccd130..edc6ece7433695 100644 --- a/packages/wordcount/README.md +++ b/packages/wordcount/README.md @@ -29,13 +29,13 @@ const numberOfWords = count( 'Words to count', 'words', {} ) _Parameters_ -- _text_ `String`: The text being processed -- _type_ `String`: The type of count. Accepts ;words', 'characters_excluding_spaces', or 'characters_including_spaces'. +- _text_ `string`: The text being processed +- _type_ `string`: The type of count. Accepts ;words', 'characters_excluding_spaces', or 'characters_including_spaces'. - _userSettings_ `Object`: Custom settings object. _Returns_ -- `Number`: The word or character count. +- `number`: The word or character count. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/wordcount/src/index.js b/packages/wordcount/src/index.js index 9099e523574cea..676f7ac2564b53 100644 --- a/packages/wordcount/src/index.js +++ b/packages/wordcount/src/index.js @@ -91,8 +91,8 @@ function matchCharacters( text, regex, settings ) { /** * Count some words. * - * @param {String} text The text being processed - * @param {String} type The type of count. Accepts ;words', 'characters_excluding_spaces', or 'characters_including_spaces'. + * @param {string} text The text being processed + * @param {string} type The type of count. Accepts ;words', 'characters_excluding_spaces', or 'characters_including_spaces'. * @param {Object} userSettings Custom settings object. * * @example @@ -101,7 +101,7 @@ function matchCharacters( text, regex, settings ) { * const numberOfWords = count( 'Words to count', 'words', {} ) * ``` * - * @return {Number} The word or character count. + * @return {number} The word or character count. */ export function count( text, type, userSettings ) { diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index 67e15440ddff34..182af41388dc0a 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -1,8 +1,4 @@ -/** @flow - * @format */ - module.exports = { - //Mock block paragraph style with minimum height blockText: { minHeight: 50, diff --git a/test/native/jest.config.js b/test/native/jest.config.js index 58a6f324cd6c62..e049d9416052c2 100644 --- a/test/native/jest.config.js +++ b/test/native/jest.config.js @@ -1,6 +1,3 @@ -/** @flow - * @format */ - /** * External dependencies */ From eb844677849841de5a79c6a7506f0e012a319757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 5 Aug 2019 04:35:43 +0200 Subject: [PATCH 601/664] Docs: Add section about adding new dependencies to WordPress packages (#16876) * Docs: Add section about adding new dependencies to WordPress packages * Apply suggestions from code review Co-Authored-By: Chris Van Patten <chris@vanpattenmedia.com> --- packages/README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/README.md b/packages/README.md index 5733bded205edd..b5296fa094b3de 100644 --- a/packages/README.md +++ b/packages/README.md @@ -1,6 +1,6 @@ ## Managing Packages -This repository uses [lerna] to manage Gutenberg modules and publish them as packages to [npm]. +This repository uses [lerna] to manage WordPress modules and publish them as packages to [npm]. ### Creating a New Package @@ -48,6 +48,34 @@ When creating a new package, you need to provide at least the following: - Usage example - `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`) +### Adding New Dependencies + +There are two types of dependencies that you might want to add to one of the existing WordPress packages. + +#### Production Dependencies + +Production dependencies are stored in the `dependencies` section of the package’s `package.json` file. The simplest way to add such a dependency to one of the packages is to run a very convenient [lerna add](https://github.com/lerna/lerna/tree/master/commands/add#readme) command from the root of the project. + +_Example:_ + +```bash +lerna add lodash packages/a11y +``` + +This command adds the latest version of `lodash` as a dependency to the `@wordpress/a11y` package, which is located in `packages/a11y` folder. + +#### Development Dependencies + +In contrast to production dependencies, development dependencies shouldn't be stored in individual WordPress packages. Instead they should be installed in the project's `package.json` file using the usual `npm install` command. In effect, all development tools are configured to work with every package at the same time to ensure they share the same characteristics and integrate correctly with each other. + +_Example:_ + +```bash +npm install glob --save-dev +``` + +This commands adds the latest version of `glob` as a development dependency to the `package.json` file. It has to be executed from the root of the project. + ### Maintaining Changelogs In maintaining dozens of npm packages, it can be tough to keep track of changes. To simplify the release process, each package includes a `CHANGELOG.md` file which details all published releases and the unreleased ("Master") changes, if any exist. From ac1466be19aa38f8e52273d98ff70b52c5c746a5 Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Mon, 5 Aug 2019 06:31:10 -0400 Subject: [PATCH 602/664] Push the modal close button over to the right. (#16883) --- packages/components/src/modal/style.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/components/src/modal/style.scss b/packages/components/src/modal/style.scss index 7858e515fa1ef0..82826987752074 100644 --- a/packages/components/src/modal/style.scss +++ b/packages/components/src/modal/style.scss @@ -90,6 +90,11 @@ line-height: 1; margin: 0; } + + .components-icon-button { + position: relative; + left: $grid-size; + } } .components-modal__header-heading-container { From 0ec20b4a112607ea6f02c5d92af5ec23b93c8cbc Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 5 Aug 2019 12:06:21 +0100 Subject: [PATCH 603/664] Fix: Gallery does not link to full size images (#16011) --- packages/block-library/src/gallery/block.json | 5 +++++ packages/block-library/src/gallery/save.js | 13 +++++++++++-- packages/block-library/src/gallery/shared.js | 4 ++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/gallery/block.json b/packages/block-library/src/gallery/block.json index c089b6c9ffe92b..6fa43d279a80e4 100644 --- a/packages/block-library/src/gallery/block.json +++ b/packages/block-library/src/gallery/block.json @@ -13,6 +13,11 @@ "selector": "img", "attribute": "src" }, + "fullUrl": { + "source": "attribute", + "selector": "img", + "attribute": "data-full-url" + }, "link": { "source": "attribute", "selector": "img", diff --git a/packages/block-library/src/gallery/save.js b/packages/block-library/src/gallery/save.js index 49ff711aa6d637..638523c6554464 100644 --- a/packages/block-library/src/gallery/save.js +++ b/packages/block-library/src/gallery/save.js @@ -17,14 +17,23 @@ export default function save( { attributes } ) { switch ( linkTo ) { case 'media': - href = image.url; + href = image.fullUrl || image.url; break; case 'attachment': href = image.link; break; } - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } data-link={ image.link } className={ image.id ? `wp-image-${ image.id }` : null } />; + const img = ( + <img + src={ image.url } + alt={ image.alt } + data-id={ image.id } + data-full-url={ image.fullUrl } + data-link={ image.link } + className={ image.id ? `wp-image-${ image.id }` : null } + /> + ); return ( <li key={ image.id || image.url } className="blocks-gallery-item"> diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 15affe5c620394..7d40f3b90d62d3 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -10,5 +10,9 @@ export function defaultColumnsNumber( attributes ) { export const pickRelevantMediaFiles = ( image ) => { const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url; + const fullUrl = get( image, [ 'sizes', 'full', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'full', 'source_url' ] ); + if ( fullUrl ) { + imageProps.fullUrl = fullUrl; + } return imageProps; }; From 659b32723f8e7c4e401f97705a4e7a133ca663da Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 5 Aug 2019 12:07:25 +0100 Subject: [PATCH 604/664] Fix move block to position bug; Add test cases; (#14924) --- packages/block-editor/src/store/actions.js | 6 + .../block-editor/src/store/test/actions.js | 179 ++++++++++++++++++ 2 files changed, 185 insertions(+) diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 0c0e1e3fa94603..fd7d3dbbe03295 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -331,6 +331,12 @@ export function* moveBlockToPosition( clientId, fromRootClientId = '', toRootCli return; } + // If templateLock is insert we can not remove the block from the parent. + // Given that here we know that we are moving the block to a different parent, the move should not be possible if the condition is true. + if ( templateLock === 'insert' ) { + return; + } + const blockName = yield select( 'core/block-editor', 'getBlockName', diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 1fb97cd38d9bde..8550d81fb518cd 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -9,6 +9,7 @@ import { insertBlock, insertBlocks, mergeBlocks, + moveBlockToPosition, multiSelect, removeBlock, removeBlocks, @@ -468,6 +469,184 @@ describe( 'actions', () => { } ); } ); + describe( 'moveBlockToPosition', () => { + it( 'should yield MOVE_BLOCK_TO_POSITION action if locking is insert and move is not changing the root block', () => { + const moveBlockToPositionGenerator = moveBlockToPosition( + 'chicken', + 'ribs', + 'ribs', + 5 + ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'ribs' ], + selectorName: 'getTemplateLock', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( 'insert' ).value + ).toEqual( { + type: 'MOVE_BLOCK_TO_POSITION', + fromRootClientId: 'ribs', + toRootClientId: 'ribs', + clientId: 'chicken', + index: 5, + } ); + + expect( + moveBlockToPositionGenerator.next().done + ).toBe( true ); + } ); + + it( 'should not yield MOVE_BLOCK_TO_POSITION action if locking is all', () => { + const moveBlockToPositionGenerator = moveBlockToPosition( + 'chicken', + 'ribs', + 'ribs', + 5 + ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'ribs' ], + selectorName: 'getTemplateLock', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( 'all' ) + ).toEqual( { + done: true, + value: undefined, + } ); + } ); + + it( 'should not yield MOVE_BLOCK_TO_POSITION action if locking is insert and move is changing the root block', () => { + const moveBlockToPositionGenerator = moveBlockToPosition( + 'chicken', + 'ribs', + 'chicken-ribs', + 5 + ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'ribs' ], + selectorName: 'getTemplateLock', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( 'insert' ) + ).toEqual( { + done: true, + value: undefined, + } ); + } ); + + it( 'should yield MOVE_BLOCK_TO_POSITION action if there is not locking in the original root block and block can be inserted in the destination', () => { + const moveBlockToPositionGenerator = moveBlockToPosition( 'chicken', + 'ribs', + 'chicken-ribs', + 5 + ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'ribs' ], + selectorName: 'getTemplateLock', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'chicken' ], + selectorName: 'getBlockName', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( 'myblock/chicken-block' ).value + ).toEqual( { + args: [ 'myblock/chicken-block', 'chicken-ribs' ], + selectorName: 'canInsertBlockType', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( true ).value + ).toEqual( { + type: 'MOVE_BLOCK_TO_POSITION', + fromRootClientId: 'ribs', + toRootClientId: 'chicken-ribs', + clientId: 'chicken', + index: 5, + } ); + + expect( + moveBlockToPositionGenerator.next() + ).toEqual( { + done: true, + value: undefined, + } ); + } ); + + it( 'should not yield MOVE_BLOCK_TO_POSITION action if there is not locking in the original root block and block can be inserted in the destination', () => { + const moveBlockToPositionGenerator = moveBlockToPosition( 'chicken', + 'ribs', + 'chicken-ribs', + 5 + ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'ribs' ], + selectorName: 'getTemplateLock', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next().value + ).toEqual( { + args: [ 'chicken' ], + selectorName: 'getBlockName', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( 'myblock/chicken-block' ).value + ).toEqual( { + args: [ 'myblock/chicken-block', 'chicken-ribs' ], + selectorName: 'canInsertBlockType', + storeName: 'core/block-editor', + type: 'SELECT', + } ); + + expect( + moveBlockToPositionGenerator.next( false ) + ).toEqual( { + done: true, + value: undefined, + } ); + } ); + } ); + describe( 'removeBlock', () => { it( 'should return REMOVE_BLOCKS action', () => { const clientId = 'myclientid'; From 509da5c5a8788be7ae106fee94d9e9b0bb4fc4e7 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Mon, 5 Aug 2019 13:48:23 +0200 Subject: [PATCH 605/664] Refactor BlockToolbar out of BlockList (#16677) * Move BlockToolbar from BlockList to Layout * Remove BlockEditorProvider from BlockList and add native version of EditorProvider to Editor. Plus support InsertionPoint and BlockListAppender * Revert BlockListAppender and InsertionPoint additions * Fix dismissing block picker * Add missing function in BlockList * Disable add block in HTML mode and show hide keyboard button only when keyboard is shown * Fix bringing back finishInsertingOrReplacingBlock * Fix inserting block in first position when post title is selected * Show insertion point before block if its replaceable * Fix missing shouldPreventAutomaticScroll props for iOS * Fix native tests * Add back bottom View to push block list up * Improve defining toolbar height * Make html view a flexbax * Quickly hide the modal to let the keyboard show up after inserting a new text based block * Let's unmount the modal instead to make sure we don't have timing errors * revert to defining the toolbar height in the component itsef * Revert "Make html view a flexbax" This reverts commit 59f0431520407282188482534d742f8a74ff9765. * Simplify layout * Fix dismiss keyboard on iOS --- .../src/components/block-list/block.native.js | 3 - .../src/components/block-list/index.native.js | 149 ++--------- .../components/block-list/style.native.scss | 8 - .../components/block-toolbar/index.native.js | 122 +++------ .../default-block-appender/index.native.js | 11 +- .../src/components/inserter/index.native.js | 127 ++++++---- .../src/components/inserter/menu.native.js | 217 ++++++++++++++++ .../src/components/inserter/style.native.scss | 7 + .../components/src/dropdown/index.native.js | 62 +++++ packages/components/src/index.native.js | 1 + .../keyboard-aware-flat-list/index.ios.js | 7 +- .../header/header-toolbar/index.native.js | 102 ++++++++ .../header/header-toolbar}/style.native.scss | 8 +- .../src/components/header/index.native.js | 51 ++++ .../src/components/layout/index.native.js | 42 +++- .../src/components/layout/style.native.scss | 7 + .../components/visual-editor/index.native.js | 37 +-- packages/edit-post/src/editor.native.js | 232 +++++++----------- packages/edit-post/src/index.native.js | 1 + .../edit-post/src/store/defaults.native.js | 14 ++ packages/edit-post/src/test/editor.native.js | 2 +- .../convert-to-group-buttons/index.native.js | 2 + .../editor/src/components/index.native.js | 3 + .../src/components/provider/index.native.js | 182 ++++++++++++++ .../reusable-blocks-buttons/index.native.js | 2 + packages/viewport/src/index.native.js | 7 + 26 files changed, 920 insertions(+), 486 deletions(-) create mode 100644 packages/block-editor/src/components/inserter/menu.native.js create mode 100644 packages/components/src/dropdown/index.native.js create mode 100644 packages/edit-post/src/components/header/header-toolbar/index.native.js rename packages/{block-editor/src/components/block-toolbar => edit-post/src/components/header/header-toolbar}/style.native.scss (75%) create mode 100644 packages/edit-post/src/components/header/index.native.js create mode 100644 packages/edit-post/src/store/defaults.native.js create mode 100644 packages/editor/src/components/convert-to-group-buttons/index.native.js create mode 100644 packages/editor/src/components/provider/index.native.js create mode 100644 packages/editor/src/components/reusable-blocks-buttons/index.native.js diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index d8366273376351..b87f95445e11f2 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -118,8 +118,6 @@ class BlockListBlock extends Component { const accessibilityLabel = this.getAccessibilityLabel(); return ( - // accessible prop needs to be false to access children - // https://facebook.github.io/react-native/docs/accessibility#accessible-ios-android <TouchableWithoutFeedback onPress={ this.onFocus } accessible={ ! isSelected } @@ -138,7 +136,6 @@ class BlockListBlock extends Component { </View> { isSelected && <BlockMobileToolbar clientId={ clientId } /> } </View> - </TouchableWithoutFeedback> ); } diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 5227bce63ecc67..3a193a9d02a9b1 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -2,30 +2,26 @@ * External dependencies */ import { identity } from 'lodash'; -import { Text, View, Keyboard, SafeAreaView, Platform, TouchableWithoutFeedback } from 'react-native'; -import { subscribeMediaAppend } from 'react-native-gutenberg-bridge'; +import { Text, View, Platform, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies */ -import { Component, Fragment } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; -import { HTMLTextInput, KeyboardAvoidingView, KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components'; +import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components'; /** * Internal dependencies */ import styles from './style.scss'; import BlockListBlock from './block'; -import BlockToolbar from '../block-toolbar'; import DefaultBlockAppender from '../default-block-appender'; -import Inserter from '../inserter'; -const blockMobileToolbarHeight = 44; -const toolbarHeight = 44; +const innerToolbarHeight = 44; export class BlockList extends Component { constructor() { @@ -34,34 +30,11 @@ export class BlockList extends Component { this.renderItem = this.renderItem.bind( this ); this.renderAddBlockSeparator = this.renderAddBlockSeparator.bind( this ); this.renderBlockListFooter = this.renderBlockListFooter.bind( this ); - this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); - this.onBlockTypeSelected = this.onBlockTypeSelected.bind( this ); - this.keyboardDidShow = this.keyboardDidShow.bind( this ); - this.keyboardDidHide = this.keyboardDidHide.bind( this ); this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( this ); this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this ); this.getNewBlockInsertionIndex = this.getNewBlockInsertionIndex.bind( this ); - - this.state = { - blockTypePickerVisible: false, - isKeyboardVisible: false, - }; - } - - // TODO: in the near future this will likely be changed to onShowBlockTypePicker and bound to this.props - // once we move the action to the toolbar - showBlockTypePicker( show ) { - this.setState( { blockTypePickerVisible: show } ); - } - - onBlockTypeSelected( itemValue ) { - this.setState( { blockTypePickerVisible: false } ); - - // create an empty block of the selected type - const newBlock = createBlock( itemValue ); - - this.finishBlockAppendingOrReplacing( newBlock ); + this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); } finishBlockAppendingOrReplacing( newBlock ) { @@ -88,48 +61,7 @@ export class BlockList extends Component { } blockHolderBorderStyle() { - return this.state.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; - } - - componentDidMount() { - this._isMounted = true; - Keyboard.addListener( 'keyboardDidShow', this.keyboardDidShow ); - Keyboard.addListener( 'keyboardDidHide', this.keyboardDidHide ); - - this.subscriptionParentMediaAppend = subscribeMediaAppend( ( payload ) => { - // create an empty media block - const newMediaBlock = createBlock( 'core/' + payload.mediaType ); - - // now set the url and id - if ( payload.mediaType === 'image' ) { - newMediaBlock.attributes.url = payload.mediaUrl; - } else if ( payload.mediaType === 'video' ) { - newMediaBlock.attributes.src = payload.mediaUrl; - } - - newMediaBlock.attributes.id = payload.mediaId; - - // finally append or replace as appropriate - this.finishBlockAppendingOrReplacing( newMediaBlock ); - } ); - } - - componentWillUnmount() { - Keyboard.removeListener( 'keyboardDidShow', this.keyboardDidShow ); - Keyboard.removeListener( 'keyboardDidHide', this.keyboardDidHide ); - - if ( this.subscriptionParentMediaAppend ) { - this.subscriptionParentMediaAppend.remove(); - } - this._isMounted = false; - } - - keyboardDidShow() { - this.setState( { isKeyboardVisible: true } ); - } - - keyboardDidHide() { - this.setState( { isKeyboardVisible: false } ); + return this.props.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; } onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { @@ -141,7 +73,7 @@ export class BlockList extends Component { } shouldFlatListPreventAutomaticScroll() { - return this.state.blockTypePickerVisible; + return this.props.isBlockInsertionPointVisible; } renderDefaultBlockAppender() { @@ -159,7 +91,7 @@ export class BlockList extends Component { ); } - renderList() { + render() { return ( <View style={ { flex: 1 } } @@ -169,10 +101,7 @@ export class BlockList extends Component { { ...( Platform.OS === 'android' ? { removeClippedSubviews: false } : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 accessibilityLabel="block-list" innerRef={ this.scrollViewInnerRef } - blockToolbarHeight={ toolbarHeight } - innerToolbarHeight={ blockMobileToolbarHeight } - safeAreaBottomInset={ this.props.safeAreaBottomInset } - parentHeight={ this.props.rootViewHeight } + extraScrollHeight={ innerToolbarHeight + 10 } keyboardShouldPersistTaps="always" style={ styles.list } data={ this.props.blockClientIds } @@ -185,40 +114,10 @@ export class BlockList extends Component { ListEmptyComponent={ this.renderDefaultBlockAppender } ListFooterComponent={ this.renderBlockListFooter } /> - <SafeAreaView> - <View style={ { height: toolbarHeight } } /> - </SafeAreaView> - <KeyboardAvoidingView - style={ styles.blockToolbarKeyboardAvoidingView } - parentHeight={ this.props.rootViewHeight } - > - <BlockToolbar - onInsertClick={ () => { - this.showBlockTypePicker( true ); - } } - showKeyboardHideButton={ this.state.isKeyboardVisible } - /> - </KeyboardAvoidingView> </View> ); } - render() { - return ( - <Fragment> - { this.renderList() } - { this.state.blockTypePickerVisible && ( - <Inserter - onDismiss={ () => this.showBlockTypePicker( false ) } - onValueSelected={ this.onBlockTypeSelected } - isReplacement={ this.isReplaceable( this.props.selectedBlock ) } - addExtraBottomPadding={ this.props.safeAreaBottomInset === 0 } - /> - ) } - </Fragment> - ); - } - isReplaceable( block ) { if ( ! block ) { return false; @@ -226,12 +125,10 @@ export class BlockList extends Component { return isUnmodifiedDefaultBlock( block ); } - renderItem( { item: clientId, index } ) { - const shouldShowAddBlockSeparator = this.state.blockTypePickerVisible && ( this.props.isBlockSelected( clientId ) || ( index === 0 && this.props.isPostTitleSelected ) ); - const shouldPutAddBlockSeparatorAboveBlock = this.isReplaceable( this.props.selectedBlock ) || this.props.isPostTitleSelected; - + renderItem( { item: clientId } ) { return ( - <ReadableContentView reversed={ shouldPutAddBlockSeparatorAboveBlock }> + <ReadableContentView> + { this.props.shouldShowInsertionPoint( clientId ) && this.renderAddBlockSeparator() } <BlockListBlock key={ clientId } showTitle={ false } @@ -241,7 +138,6 @@ export class BlockList extends Component { borderStyle={ this.blockHolderBorderStyle() } focusedBorderColor={ styles.blockHolderFocused.borderColor } /> - { shouldShowAddBlockSeparator && this.renderAddBlockSeparator() } </ReadableContentView> ); } @@ -266,12 +162,6 @@ export class BlockList extends Component { </TouchableWithoutFeedback> ); } - - renderHTML() { - return ( - <HTMLTextInput { ...this.props } parentHeight={ this.props.rootViewHeight } /> - ); - } } export default compose( [ @@ -284,15 +174,28 @@ export default compose( [ getSelectedBlock, getSelectedBlockClientId, isBlockSelected, + getBlockInsertionPoint, + isBlockInsertionPointVisible, } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); + const blockClientIds = getBlockOrder( rootClientId ); + const insertionPoint = getBlockInsertionPoint(); + const shouldShowInsertionPoint = ( clientId ) => { + return ( + isBlockInsertionPointVisible() && + insertionPoint.rootClientId === rootClientId && + blockClientIds[ insertionPoint.index ] === clientId + ); + }; return { - blockClientIds: getBlockOrder( rootClientId ), + blockClientIds, blockCount: getBlockCount( rootClientId ), getBlockName, isBlockSelected, + isBlockInsertionPointVisible: isBlockInsertionPointVisible(), + shouldShowInsertionPoint, selectedBlock: getSelectedBlock(), selectedBlockClientId, selectedBlockOrder: getBlockIndex( selectedBlockClientId ), diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index 884c0612f05867..5185e2099bf93e 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -40,13 +40,6 @@ background-color: $white; } -.blockToolbarKeyboardAvoidingView { - position: absolute; - bottom: 0; - right: 0; - left: 0; -} - .blockHolderSemiBordered { border-top-width: 1px; border-bottom-width: 1px; @@ -61,7 +54,6 @@ border-right-width: 1px; } - .blockContainerFocused { background-color: $white; padding-left: 16; diff --git a/packages/block-editor/src/components/block-toolbar/index.native.js b/packages/block-editor/src/components/block-toolbar/index.native.js index 3d12312673bf87..5413e4e1f9cded 100644 --- a/packages/block-editor/src/components/block-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-toolbar/index.native.js @@ -1,105 +1,41 @@ /** - * External dependencies + * WordPress dependencies */ -import { View, ScrollView, Keyboard, Platform } from 'react-native'; +import { withSelect } from '@wordpress/data'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; -import { Toolbar, ToolbarButton, Dashicon } from '@wordpress/components'; import { BlockFormatControls, BlockControls } from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import styles from './style.scss'; - -export class BlockToolbar extends Component { - constructor() { - super( ...arguments ); - this.onKeyboardHide = this.onKeyboardHide.bind( this ); +export const BlockToolbar = ( { blockClientIds, isValid, mode } ) => { + if ( blockClientIds.length === 0 ) { + return null; } - onKeyboardHide() { - this.props.clearSelectedBlock(); - if ( Platform.OS === 'android' ) { - // Avoiding extra blur calls on iOS but still needed for android. - Keyboard.dismiss(); - } - } - - render() { - const { - hasRedo, - hasUndo, - redo, - undo, - onInsertClick, - showKeyboardHideButton, - } = this.props; - - return ( - <View style={ styles.container } > - <ScrollView - horizontal={ true } - showsHorizontalScrollIndicator={ false } - keyboardShouldPersistTaps="always" - alwaysBounceHorizontal={ false } - contentContainerStyle={ styles.scrollableContent } - > - <Toolbar accessible={ false }> - <ToolbarButton - title={ __( 'Add block' ) } - icon={ ( <Dashicon icon="plus-alt" style={ styles.addBlockButton } color={ styles.addBlockButton.color } /> ) } - onClick={ onInsertClick } - extraProps={ { hint: __( 'Double tap to add a block' ) } } - /> - <ToolbarButton - title={ __( 'Undo' ) } - icon="undo" - isDisabled={ ! hasUndo } - onClick={ undo } - extraProps={ { hint: __( 'Double tap to undo last change' ) } } - /> - <ToolbarButton - title={ __( 'Redo' ) } - icon="redo" - isDisabled={ ! hasRedo } - onClick={ redo } - extraProps={ { hint: __( 'Double tap to redo last change' ) } } - /> - </Toolbar> + return ( + <> + { mode === 'visual' && isValid && ( + <> <BlockControls.Slot /> <BlockFormatControls.Slot /> - </ScrollView> - { showKeyboardHideButton && - <Toolbar passedStyle={ styles.keyboardHideContainer }> - <ToolbarButton - title={ __( 'Hide keyboard' ) } - icon="keyboard-hide" - onClick={ this.onKeyboardHide } - extraProps={ { hint: __( 'Tap to hide the keyboard' ) } } - /> - </Toolbar> - } - </View> - ); - } -} - -export default compose( [ - withSelect( ( select ) => ( { - hasRedo: select( 'core/editor' ).hasEditorRedo(), - hasUndo: select( 'core/editor' ).hasEditorUndo(), - } ) ), - withDispatch( ( dispatch ) => ( { - redo: dispatch( 'core/editor' ).redo, - undo: dispatch( 'core/editor' ).undo, - clearSelectedBlock: dispatch( 'core/editor' ).clearSelectedBlock, - } ) ), -] )( BlockToolbar ); + </> + ) } + </> + ); +}; + +export default withSelect( ( select ) => { + const { + getBlockMode, + getSelectedBlockClientIds, + isBlockValid, + } = select( 'core/block-editor' ); + const blockClientIds = getSelectedBlockClientIds(); + + return { + blockClientIds, + isValid: blockClientIds.length === 1 ? isBlockValid( blockClientIds[ 0 ] ) : null, + mode: blockClientIds.length === 1 ? getBlockMode( blockClientIds[ 0 ] ) : null, + }; +} )( BlockToolbar ); diff --git a/packages/block-editor/src/components/default-block-appender/index.native.js b/packages/block-editor/src/components/default-block-appender/index.native.js index 1319fd3fd2ce9f..4f361376f6d581 100644 --- a/packages/block-editor/src/components/default-block-appender/index.native.js +++ b/packages/block-editor/src/components/default-block-appender/index.native.js @@ -11,6 +11,7 @@ import { RichText } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; import { withSelect, withDispatch } from '@wordpress/data'; +import { getDefaultBlockName } from '@wordpress/blocks'; /** * Internal dependencies @@ -28,7 +29,7 @@ export function DefaultBlockAppender( { return null; } - const value = decodeEntities( placeholder ) || __( 'Start writing…' ); + const value = typeof placeholder === 'string' ? decodeEntities( placeholder ) : __( 'Start writing…' ); return ( <TouchableWithoutFeedback @@ -46,15 +47,15 @@ export function DefaultBlockAppender( { export default compose( withSelect( ( select, ownProps ) => { - const { getBlockCount, getSettings, getTemplateLock } = select( 'core/block-editor' ); + const { getBlockCount, getBlockName, isBlockValid, getTemplateLock } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); - const { bodyPlaceholder } = getSettings(); + const isLastBlockDefault = getBlockName( ownProps.lastBlockClientId ) === getDefaultBlockName(); + const isLastBlockValid = isBlockValid( ownProps.lastBlockClientId ); return { - isVisible: isEmpty, + isVisible: isEmpty || ! isLastBlockDefault || ! isLastBlockValid, isLocked: !! getTemplateLock( ownProps.rootClientId ), - placeholder: bodyPlaceholder, }; } ), withDispatch( ( dispatch, ownProps ) => { diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index 2985d61c7811c4..90009022f0bd35 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -1,12 +1,8 @@ -/** - * External dependencies - */ -import { FlatList, Text, TouchableHighlight, View } from 'react-native'; - /** * WordPress dependencies */ -import { BottomSheet, Icon } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { Dropdown, ToolbarButton, Dashicon } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -16,58 +12,85 @@ import { getUnregisteredTypeHandlerName } from '@wordpress/blocks'; * Internal dependencies */ import styles from './style.scss'; +import InserterMenu from './menu'; + +const defaultRenderToggle = ( { onToggle, disabled } ) => ( + <ToolbarButton + title={ __( 'Add block' ) } + icon={ ( <Dashicon icon="plus-alt" style={ styles.addBlockButton } color={ styles.addBlockButton.color } /> ) } + onClick={ onToggle } + extraProps={ { hint: __( 'Double tap to add a block' ) } } + isDisabled={ disabled } + /> +); class Inserter extends Component { - calculateNumberOfColumns() { - const bottomSheetWidth = BottomSheet.getWidth(); - const { paddingLeft: itemPaddingLeft, paddingRight: itemPaddingRight } = styles.modalItem; - const { paddingLeft: containerPaddingLeft, paddingRight: containerPaddingRight } = styles.content; - const { width: itemWidth } = styles.modalIconWrapper; - const itemTotalWidth = itemWidth + itemPaddingLeft + itemPaddingRight; - const containerTotalWidth = bottomSheetWidth - ( containerPaddingLeft + containerPaddingRight ); - return Math.floor( containerTotalWidth / itemTotalWidth ); + constructor() { + super( ...arguments ); + + this.onToggle = this.onToggle.bind( this ); + this.renderToggle = this.renderToggle.bind( this ); + this.renderContent = this.renderContent.bind( this ); } - render() { - const numberOfColumns = this.calculateNumberOfColumns(); - const bottomPadding = this.props.addExtraBottomPadding && styles.contentBottomPadding; + onToggle( isOpen ) { + const { onToggle } = this.props; + // Surface toggle callback to parent component + if ( onToggle ) { + onToggle( isOpen ); + } + } + + /** + * Render callback to display Dropdown toggle element. + * + * @param {Function} options.onToggle Callback to invoke when toggle is + * pressed. + * @param {boolean} options.isOpen Whether dropdown is currently open. + * + * @return {WPElement} Dropdown toggle element. + */ + renderToggle( { onToggle, isOpen } ) { + const { + disabled, + renderToggle = defaultRenderToggle, + } = this.props; + + return renderToggle( { onToggle, isOpen, disabled } ); + } + + /** + * Render callback to display Dropdown content element. + * + * @param {Function} options.onClose Callback to invoke when dropdown is + * closed. + * + * @return {WPElement} Dropdown content element. + */ + renderContent( { onClose, isOpen } ) { + const { rootClientId, clientId, isAppender } = this.props; + + return ( + <InserterMenu + isOpen={ isOpen } + onSelect={ onClose } + onDismiss={ onClose } + rootClientId={ rootClientId } + clientId={ clientId } + isAppender={ isAppender } + /> + ); + } + + render() { return ( - <BottomSheet - isVisible={ true } - onClose={ this.props.onDismiss } - contentStyle={ [ styles.content, bottomPadding ] } - hideHeader - > - <FlatList - scrollEnabled={ false } - key={ `InserterUI-${ numberOfColumns }` } //re-render when numberOfColumns changes - keyboardShouldPersistTaps="always" - numColumns={ numberOfColumns } - data={ this.props.items } - ItemSeparatorComponent={ () => - <View style={ styles.rowSeparator } /> - } - keyExtractor={ ( item ) => item.name } - renderItem={ ( { item } ) => - <TouchableHighlight - style={ styles.touchableArea } - underlayColor="transparent" - activeOpacity={ .5 } - accessibilityLabel={ item.title } - onPress={ () => this.props.onValueSelected( item.name ) }> - <View style={ styles.modalItem }> - <View style={ styles.modalIconWrapper }> - <View style={ styles.modalIcon }> - <Icon icon={ item.icon.src } fill={ styles.modalIcon.fill } size={ styles.modalIcon.width } /> - </View> - </View> - <Text style={ styles.modalItemLabel }>{ item.title }</Text> - </View> - </TouchableHighlight> - } - /> - </BottomSheet> + <Dropdown + onToggle={ this.onToggle } + headerTitle={ __( 'Add a block' ) } + renderToggle={ this.renderToggle } + renderContent={ this.renderContent } + /> ); } } diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js new file mode 100644 index 00000000000000..bf2dd780841f9c --- /dev/null +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -0,0 +1,217 @@ +/** + * External dependencies + */ +import { FlatList, View, Text, TouchableHighlight } from 'react-native'; +import { subscribeMediaAppend } from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { + createBlock, + isUnmodifiedDefaultBlock, +} from '@wordpress/blocks'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { withInstanceId, compose } from '@wordpress/compose'; +import { BottomSheet, Icon } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +export class InserterMenu extends Component { + componentDidMount() { + this.subscriptionParentMediaAppend = subscribeMediaAppend( ( payload ) => { + this.props.onSelect( { + name: 'core/' + payload.mediaType, + initialAttributes: { + id: payload.mediaId, + [ payload.mediaType === 'image' ? 'url' : 'src' ]: payload.mediaUrl, + }, + } ); + } ); + this.onOpen(); + } + + componentWillUnmount() { + if ( this.subscriptionParentMediaAppend ) { + this.subscriptionParentMediaAppend.remove(); + } + this.onClose(); + } + + calculateNumberOfColumns() { + const bottomSheetWidth = BottomSheet.getWidth(); + const { paddingLeft: itemPaddingLeft, paddingRight: itemPaddingRight } = styles.modalItem; + const { paddingLeft: containerPaddingLeft, paddingRight: containerPaddingRight } = styles.content; + const { width: itemWidth } = styles.modalIconWrapper; + const itemTotalWidth = itemWidth + itemPaddingLeft + itemPaddingRight; + const containerTotalWidth = bottomSheetWidth - ( containerPaddingLeft + containerPaddingRight ); + return Math.floor( containerTotalWidth / itemTotalWidth ); + } + + onOpen() { + this.props.showInsertionPoint(); + } + + onClose() { + this.props.hideInsertionPoint(); + } + + render() { + const numberOfColumns = this.calculateNumberOfColumns(); + const bottomPadding = styles.contentBottomPadding; + + return ( + <BottomSheet + isVisible={ true } + onClose={ this.props.onDismiss } + contentStyle={ [ styles.content, bottomPadding ] } + hideHeader + > + <FlatList + scrollEnabled={ false } + key={ `InserterUI-${ numberOfColumns }` } //re-render when numberOfColumns changes + keyboardShouldPersistTaps="always" + numColumns={ numberOfColumns } + data={ this.props.items } + ItemSeparatorComponent={ () => + <View style={ styles.rowSeparator } /> + } + keyExtractor={ ( item ) => item.name } + renderItem={ ( { item } ) => + <TouchableHighlight + style={ styles.touchableArea } + underlayColor="transparent" + activeOpacity={ .5 } + accessibilityLabel={ item.title } + onPress={ () => this.props.onSelect( item ) }> + <View style={ styles.modalItem }> + <View style={ styles.modalIconWrapper }> + <View style={ styles.modalIcon }> + <Icon icon={ item.icon.src } fill={ styles.modalIcon.fill } size={ styles.modalIcon.width } /> + </View> + </View> + <Text style={ styles.modalItemLabel }>{ item.title }</Text> + </View> + </TouchableHighlight> + } + /> + </BottomSheet> + ); + /* eslint-enable jsx-a11y/no-autofocus, jsx-a11y/no-noninteractive-element-interactions */ + } +} + +export default compose( + withSelect( ( select, { clientId, isAppender, rootClientId } ) => { + const { + getInserterItems, + getBlockName, + getBlockRootClientId, + getBlockSelectionEnd, + } = select( 'core/block-editor' ); + const { + getChildBlockNames, + } = select( 'core/blocks' ); + + let destinationRootClientId = rootClientId; + if ( ! destinationRootClientId && ! clientId && ! isAppender ) { + const end = getBlockSelectionEnd(); + if ( end ) { + destinationRootClientId = getBlockRootClientId( end ) || undefined; + } + } + const destinationRootBlockName = getBlockName( destinationRootClientId ); + + return { + rootChildBlocks: getChildBlockNames( destinationRootBlockName ), + items: getInserterItems( destinationRootClientId ), + destinationRootClientId, + }; + } ), + withDispatch( ( dispatch, ownProps, { select } ) => { + const { + showInsertionPoint, + hideInsertionPoint, + } = dispatch( 'core/block-editor' ); + + // To avoid duplication, getInsertionIndex is extracted and used in two event handlers + // This breaks the withDispatch not containing any logic rule. + // Since it's a function only called when the event handlers are called, + // it's fine to extract it. + // eslint-disable-next-line no-restricted-syntax + function getInsertionIndex() { + const { + getBlock, + getBlockIndex, + getBlockSelectionEnd, + getBlockOrder, + } = select( 'core/block-editor' ); + const { + isPostTitleSelected, + } = select( 'core/editor' ); + const { clientId, destinationRootClientId, isAppender } = ownProps; + + // if post title is selected insert as first block + if ( isPostTitleSelected() ) { + return 0; + } + + // If the clientId is defined, we insert at the position of the block. + if ( clientId ) { + return getBlockIndex( clientId, destinationRootClientId ); + } + + // If there a selected block, + const end = getBlockSelectionEnd(); + if ( ! isAppender && end ) { + // and the last selected block is unmodified (empty), it will be replaced + if ( isUnmodifiedDefaultBlock( getBlock( end ) ) ) { + return getBlockIndex( end, destinationRootClientId ); + } + + // we insert after the selected block. + return getBlockIndex( end, destinationRootClientId ) + 1; + } + + // Otherwise, we insert at the end of the current rootClientId + return getBlockOrder( destinationRootClientId ).length; + } + + return { + showInsertionPoint() { + const index = getInsertionIndex(); + showInsertionPoint( ownProps.destinationRootClientId, index ); + }, + hideInsertionPoint, + onSelect( item ) { + const { + replaceBlocks, + insertBlock, + } = dispatch( 'core/block-editor' ); + const { + getSelectedBlock, + } = select( 'core/block-editor' ); + const { isAppender } = ownProps; + const { name, initialAttributes } = item; + const selectedBlock = getSelectedBlock(); + const insertedBlock = createBlock( name, initialAttributes ); + if ( ! isAppender && selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { + replaceBlocks( selectedBlock.clientId, insertedBlock ); + } else { + insertBlock( + insertedBlock, + getInsertionIndex(), + ownProps.destinationRootClientId + ); + } + + ownProps.onSelect(); + }, + }; + } ), + withInstanceId, +)( InserterMenu ); diff --git a/packages/block-editor/src/components/inserter/style.native.scss b/packages/block-editor/src/components/inserter/style.native.scss index 82d5fa58226504..e10b685dda406c 100644 --- a/packages/block-editor/src/components/inserter/style.native.scss +++ b/packages/block-editor/src/components/inserter/style.native.scss @@ -55,3 +55,10 @@ font-size: 12; color: $gray-dark; } + +.addBlockButton { + color: $blue-wordpress; + border: 2px; + border-radius: 10px; + border-color: $blue-wordpress; +} diff --git a/packages/components/src/dropdown/index.native.js b/packages/components/src/dropdown/index.native.js new file mode 100644 index 00000000000000..de7c7b7839613a --- /dev/null +++ b/packages/components/src/dropdown/index.native.js @@ -0,0 +1,62 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +class Dropdown extends Component { + constructor() { + super( ...arguments ); + + this.toggle = this.toggle.bind( this ); + this.close = this.close.bind( this ); + + this.state = { + isOpen: false, + }; + } + + componentWillUnmount() { + const { isOpen } = this.state; + const { onToggle } = this.props; + if ( isOpen && onToggle ) { + onToggle( false ); + } + } + + componentDidUpdate( prevProps, prevState ) { + const { isOpen } = this.state; + const { onToggle } = this.props; + if ( prevState.isOpen !== isOpen && onToggle ) { + onToggle( isOpen ); + } + } + + toggle() { + this.setState( ( state ) => ( { + isOpen: ! state.isOpen, + } ) ); + } + + close() { + this.setState( { isOpen: false } ); + } + + render() { + const { isOpen } = this.state; + const { + renderContent, + renderToggle, + } = this.props; + + const args = { isOpen, onToggle: this.toggle, onClose: this.close }; + + return ( + <> + { renderToggle( args ) } + { isOpen && renderContent( args ) } + </> + ); + } +} + +export default Dropdown; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 5965c13e26eaeb..4984cdde2fd36d 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -1,6 +1,7 @@ // Components export * from './primitives'; export { default as Dashicon } from './dashicon'; +export { default as Dropdown } from './dropdown'; export { default as Toolbar } from './toolbar'; export { default as ToolbarButton } from './toolbar-button'; export { default as Icon } from './icon'; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index 341737b41bef2b..fb9dab39a03f2b 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -5,8 +5,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view import { FlatList } from 'react-native'; export const KeyboardAwareFlatList = ( { - blockToolbarHeight, - innerToolbarHeight, + extraScrollHeight, shouldPreventAutomaticScroll, innerRef, ...listProps @@ -16,9 +15,7 @@ export const KeyboardAwareFlatList = ( { keyboardDismissMode="none" enableResetScrollToCoords={ false } keyboardShouldPersistTaps="handled" - extraScrollHeight={ innerToolbarHeight } - extraBottomInset={ -listProps.safeAreaBottomInset } - inputAccessoryViewHeight={ blockToolbarHeight } + extraScrollHeight={ extraScrollHeight } extraHeight={ 0 } innerRef={ ( ref ) => { this.scrollViewRef = ref; diff --git a/packages/edit-post/src/components/header/header-toolbar/index.native.js b/packages/edit-post/src/components/header/header-toolbar/index.native.js new file mode 100644 index 00000000000000..9c71845889d371 --- /dev/null +++ b/packages/edit-post/src/components/header/header-toolbar/index.native.js @@ -0,0 +1,102 @@ +/** + * External dependencies + */ +import { ScrollView, Keyboard, Platform, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { withViewportMatch } from '@wordpress/viewport'; +import { __ } from '@wordpress/i18n'; +import { + Inserter, + BlockToolbar, +} from '@wordpress/block-editor'; +import { Toolbar, ToolbarButton } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +function HeaderToolbar( { + hasFixedToolbar, + hasRedo, + hasUndo, + redo, + undo, + showInserter, + showKeyboardHideButton, + clearSelectedBlock, +} ) { + const hideKeyboard = () => { + clearSelectedBlock(); + if ( Platform.OS === 'android' ) { + // Avoiding extra blur calls on iOS but still needed for android. + Keyboard.dismiss(); + } + }; + + return ( + <View style={ styles.container }> + <ScrollView + horizontal={ true } + showsHorizontalScrollIndicator={ false } + keyboardShouldPersistTaps="always" + alwaysBounceHorizontal={ false } + contentContainerStyle={ styles.scrollableContent } + > + <Toolbar accessible={ false }> + <Inserter disabled={ ! showInserter } /> + { /* TODO: replace with EditorHistoryRedo and EditorHistoryUndo */ } + <ToolbarButton + title={ __( 'Undo' ) } + icon="undo" + isDisabled={ ! hasUndo } + onClick={ undo } + extraProps={ { hint: __( 'Double tap to undo last change' ) } } + /> + <ToolbarButton + title={ __( 'Redo' ) } + icon="redo" + isDisabled={ ! hasRedo } + onClick={ redo } + extraProps={ { hint: __( 'Double tap to redo last change' ) } } + /> + </Toolbar> + { hasFixedToolbar && + <BlockToolbar /> + } + </ScrollView> + { showKeyboardHideButton && + <Toolbar passedStyle={ styles.keyboardHideContainer }> + <ToolbarButton + title={ __( 'Hide keyboard' ) } + icon="keyboard-hide" + onClick={ hideKeyboard } + extraProps={ { hint: __( 'Tap to hide the keyboard' ) } } + /> + </Toolbar> + } + </View> + ); +} + +export default compose( [ + withSelect( ( select ) => ( { + hasRedo: select( 'core/editor' ).hasEditorRedo(), + hasUndo: select( 'core/editor' ).hasEditorUndo(), + hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), + // This setting (richEditingEnabled) should not live in the block editor's setting. + showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', + } ) ), + withDispatch( ( dispatch ) => ( { + redo: dispatch( 'core/editor' ).redo, + undo: dispatch( 'core/editor' ).undo, + clearSelectedBlock: dispatch( 'core/editor' ).clearSelectedBlock, + } ) ), + withViewportMatch( { isLargeViewport: 'medium' } ), +] )( HeaderToolbar ); diff --git a/packages/block-editor/src/components/block-toolbar/style.native.scss b/packages/edit-post/src/components/header/header-toolbar/style.native.scss similarity index 75% rename from packages/block-editor/src/components/block-toolbar/style.native.scss rename to packages/edit-post/src/components/header/header-toolbar/style.native.scss index 43e996cd8e0755..0aa03b90ed81cc 100644 --- a/packages/block-editor/src/components/block-toolbar/style.native.scss +++ b/packages/edit-post/src/components/header/header-toolbar/style.native.scss @@ -1,3 +1,4 @@ + .container { height: 44px; flex-direction: row; @@ -18,10 +19,3 @@ justify-content: center; align-items: center; } - -.addBlockButton { - color: $blue-wordpress; - border: 2px; - border-radius: 10px; - border-color: $blue-wordpress; -} diff --git a/packages/edit-post/src/components/header/index.native.js b/packages/edit-post/src/components/header/index.native.js new file mode 100644 index 00000000000000..93fbf88ce79674 --- /dev/null +++ b/packages/edit-post/src/components/header/index.native.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import { Keyboard } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import HeaderToolbar from './header-toolbar'; + +export default class Header extends Component { + constructor() { + super( ...arguments ); + + this.keyboardDidShow = this.keyboardDidShow.bind( this ); + this.keyboardDidHide = this.keyboardDidHide.bind( this ); + + this.state = { + isKeyboardVisible: false, + }; + } + + componentDidMount() { + Keyboard.addListener( 'keyboardDidShow', this.keyboardDidShow ); + Keyboard.addListener( 'keyboardDidHide', this.keyboardDidHide ); + } + + componentWillUnmount() { + Keyboard.removeListener( 'keyboardDidShow', this.keyboardDidShow ); + Keyboard.removeListener( 'keyboardDidHide', this.keyboardDidHide ); + } + + keyboardDidShow() { + this.setState( { isKeyboardVisible: true } ); + } + + keyboardDidHide() { + this.setState( { isKeyboardVisible: false } ); + } + + render() { + return ( + <HeaderToolbar showKeyboardHideButton={ this.state.isKeyboardVisible } /> + ); + } +} diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js index 6081dc73410609..84f7e5bcde21dd 100644 --- a/packages/edit-post/src/components/layout/index.native.js +++ b/packages/edit-post/src/components/layout/index.native.js @@ -1,8 +1,9 @@ /** * External dependencies */ -import { SafeAreaView } from 'react-native'; +import { Platform, SafeAreaView, View } from 'react-native'; import SafeArea from 'react-native-safe-area'; +import { sendNativeEditorDidLayout } from 'react-native-gutenberg-bridge'; /** * WordPress dependencies @@ -10,12 +11,14 @@ import SafeArea from 'react-native-safe-area'; import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { HTMLTextInput, ReadableContentView } from '@wordpress/components'; +import { HTMLTextInput, KeyboardAvoidingView, ReadableContentView } from '@wordpress/components'; /** * Internal dependencies */ import styles from './style.scss'; +import headerToolbarStyles from '../header/header-toolbar/style.scss'; +import Header from '../header'; import VisualEditor from '../visual-editor'; class Layout extends Component { @@ -27,7 +30,7 @@ class Layout extends Component { this.state = { rootViewHeight: 0, - safeAreaBottomInset: 0, + safeAreaInsets: { top: 0, bottom: 0, right: 0, left: 0 }, isFullyBordered: true, }; @@ -46,8 +49,8 @@ class Layout extends Component { onSafeAreaInsetsUpdate( result ) { const { safeAreaInsets } = result; - if ( this._isMounted && this.state.safeAreaBottomInset !== safeAreaInsets.bottom ) { - this.setState( { safeAreaBottomInset: safeAreaInsets.bottom } ); + if ( this._isMounted ) { + this.setState( { safeAreaInsets } ); } } @@ -60,7 +63,7 @@ class Layout extends Component { setHeightState( event ) { const { height } = event.nativeEvent.layout; - this.setState( { rootViewHeight: height }, this.props.onNativeEditorDidLayout ); + this.setState( { rootViewHeight: height }, sendNativeEditorDidLayout ); } setBorderStyleState() { @@ -72,9 +75,7 @@ class Layout extends Component { renderHTML() { return ( - <HTMLTextInput - parentHeight={ this.state.rootViewHeight } - /> + <HTMLTextInput /> ); } @@ -90,8 +91,6 @@ class Layout extends Component { return ( <VisualEditor isFullyBordered={ this.state.isFullyBordered } - rootViewHeight={ this.state.rootViewHeight } - safeAreaBottomInset={ this.state.safeAreaBottomInset } setTitleRef={ this.props.setTitleRef } /> ); @@ -102,9 +101,28 @@ class Layout extends Component { mode, } = this.props; + // add a margin view at the bottom for the header + const marginBottom = Platform.OS === 'android' ? headerToolbarStyles.container.height : 0; + + const toolbarKeyboardAvoidingViewStyle = { + ...styles.toolbarKeyboardAvoidingView, + left: this.state.safeAreaInsets.left, + right: this.state.safeAreaInsets.right, + }; + return ( <SafeAreaView style={ styles.container } onLayout={ this.onRootViewLayout }> - { mode === 'text' ? this.renderHTML() : this.renderVisual() } + <View style={ { flex: 1 } }> + { mode === 'text' ? this.renderHTML() : this.renderVisual() } + </View> + <View style={ { flex: 0, flexBasis: marginBottom, height: marginBottom } }> + </View> + <KeyboardAvoidingView + parentHeight={ this.state.rootViewHeight } + style={ toolbarKeyboardAvoidingViewStyle } + > + <Header /> + </KeyboardAvoidingView> </SafeAreaView> ); } diff --git a/packages/edit-post/src/components/layout/style.native.scss b/packages/edit-post/src/components/layout/style.native.scss index badc66b9b619fb..e6d7e241bcd0df 100644 --- a/packages/edit-post/src/components/layout/style.native.scss +++ b/packages/edit-post/src/components/layout/style.native.scss @@ -4,3 +4,10 @@ justify-content: flex-start; background-color: #fff; } + +.toolbarKeyboardAvoidingView { + position: absolute; + bottom: 0; + right: 0; + left: 0; +} diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js index 092ea638f68026..15ca4ed9d451bc 100644 --- a/packages/edit-post/src/components/visual-editor/index.native.js +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -5,7 +5,7 @@ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { BlockEditorProvider, BlockList } from '@wordpress/block-editor'; +import { BlockList } from '@wordpress/block-editor'; import { PostTitle } from '@wordpress/editor'; import { ReadableContentView } from '@wordpress/components'; @@ -43,30 +43,16 @@ class VisualEditor extends Component { render() { const { - blocks, isFullyBordered, - resetEditorBlocks, - resetEditorBlocksWithoutUndoLevel, - rootViewHeight, safeAreaBottomInset, } = this.props; return ( - <BlockEditorProvider - value={ blocks } - onInput={ resetEditorBlocksWithoutUndoLevel } - onChange={ resetEditorBlocks } - settings={ null } - > - <BlockList - header={ this.renderHeader() } - isFullyBordered={ isFullyBordered } - rootViewHeight={ rootViewHeight } - safeAreaBottomInset={ safeAreaBottomInset } - isPostTitleSelected={ this.props.isPostTitleSelected } - onBlockTypeSelected={ this.onPostTitleUnselect } - /> - </BlockEditorProvider> + <BlockList + header={ this.renderHeader() } + isFullyBordered={ isFullyBordered } + safeAreaBottomInset={ safeAreaBottomInset } + /> ); } } @@ -74,21 +60,16 @@ class VisualEditor extends Component { export default compose( [ withSelect( ( select ) => { const { - getEditorBlocks, getEditedPostAttribute, - isPostTitleSelected, } = select( 'core/editor' ); return { - blocks: getEditorBlocks(), title: getEditedPostAttribute( 'title' ), - isPostTitleSelected: isPostTitleSelected(), }; } ), withDispatch( ( dispatch ) => { const { editPost, - resetEditorBlocks, } = dispatch( 'core/editor' ); const { clearSelectedBlock } = dispatch( 'core/block-editor' ); @@ -98,12 +79,6 @@ export default compose( [ editTitle( title ) { editPost( { title } ); }, - resetEditorBlocks, - resetEditorBlocksWithoutUndoLevel( blocks ) { - resetEditorBlocks( blocks, { - __unstableShouldCreateUndoLevel: false, - } ); - }, }; } ), ] )( VisualEditor ); diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index 71f8bbd9cb7746..23e0bad6a9e60a 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -1,22 +1,18 @@ /** * External dependencies */ -import RNReactNativeGutenbergBridge, { - subscribeParentGetHtml, - subscribeParentToggleHTMLMode, - subscribeUpdateHtml, - subscribeSetFocusOnTitle, - subscribeSetTitle, - sendNativeEditorDidLayout, -} from 'react-native-gutenberg-bridge'; +import memize from 'memize'; +import { size, map, without } from 'lodash'; /** * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { parse, serialize, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; +import { EditorProvider } from '@wordpress/editor'; +import { parse, serialize } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { SlotFillProvider } from '@wordpress/components'; /** * Internal dependencies @@ -27,175 +23,119 @@ class Editor extends Component { constructor( props ) { super( ...arguments ); - this.setTitleRef = this.setTitleRef.bind( this ); - - // TODO: use EditorProvider instead - this.post = props.post || { - id: 1, - title: { - raw: props.initialTitle, - }, - content: { - raw: props.initialHtml || '', - }, - type: 'draft', - }; - - props.setupEditor( this.post ); - - // make sure the post content is in sync with gutenberg store - // to avoid marking the post as modified when simply loaded - // For now, let's assume: serialize( parse( html ) ) !== html - this.post.content.raw = serialize( props.getEditorBlocks() ); - if ( props.initialHtmlModeEnabled && props.mode === 'visual' ) { // enable html mode if the initial mode the parent wants it but we're not already in it - this.toggleMode(); + this.props.switchEditorMode( 'text' ); } - } - - componentDidMount() { - this.subscriptionParentGetHtml = subscribeParentGetHtml( () => { - this.serializeToNativeAction(); - } ); - this.subscriptionParentToggleHTMLMode = subscribeParentToggleHTMLMode( () => { - this.toggleMode(); + this.getEditorSettings = memize( this.getEditorSettings, { + maxSize: 1, } ); - - this.subscriptionParentSetTitle = subscribeSetTitle( ( payload ) => { - this.props.editTitle( payload.title ); - } ); - - this.subscriptionParentUpdateHtml = subscribeUpdateHtml( ( payload ) => { - this.updateHtmlAction( payload.html ); - } ); - - this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( () => { - if ( this.postTitleRef ) { - this.postTitleRef.focus(); - } - } ); - } - - componentWillUnmount() { - if ( this.subscriptionParentGetHtml ) { - this.subscriptionParentGetHtml.remove(); - } - - if ( this.subscriptionParentToggleHTMLMode ) { - this.subscriptionParentToggleHTMLMode.remove(); - } - - if ( this.subscriptionParentSetTitle ) { - this.subscriptionParentSetTitle.remove(); - } - - if ( this.subscriptionParentUpdateHtml ) { - this.subscriptionParentUpdateHtml.remove(); - } - - if ( this.subscriptionParentSetFocusOnTitle ) { - this.subscriptionParentSetFocusOnTitle.remove(); - } } - serializeToNativeAction() { - if ( this.props.mode === 'text' ) { - this.updateHtmlAction( this.props.getEditedPostContent() ); - } - - const html = serialize( this.props.getEditorBlocks() ); - const title = this.props.getEditedPostAttribute( 'title' ); - - const hasChanges = title !== this.post.title.raw || html !== this.post.content.raw; - - RNReactNativeGutenbergBridge.provideToNative_Html( html, title, hasChanges ); + getEditorSettings( + settings, + hasFixedToolbar, + focusMode, + hiddenBlockTypes, + blockTypes, + ) { + settings = { + ...settings, + hasFixedToolbar, + focusMode, + }; - if ( hasChanges ) { - this.post.title.raw = title; - this.post.content.raw = html; + // Omit hidden block types if exists and non-empty. + if ( size( hiddenBlockTypes ) > 0 ) { + // Defer to passed setting for `allowedBlockTypes` if provided as + // anything other than `true` (where `true` is equivalent to allow + // all block types). + const defaultAllowedBlockTypes = ( + true === settings.allowedBlockTypes ? + map( blockTypes, 'name' ) : + ( settings.allowedBlockTypes || [] ) + ); + + settings.allowedBlockTypes = without( + defaultAllowedBlockTypes, + ...hiddenBlockTypes, + ); } - } - - updateHtmlAction( html ) { - const parsed = parse( html ); - this.props.resetEditorBlocksWithoutUndoLevel( parsed ); - } - toggleMode() { - const { mode, switchMode } = this.props; - // refresh html content first - this.serializeToNativeAction(); - switchMode( mode === 'visual' ? 'text' : 'visual' ); + return settings; } - componentDidUpdate( prevProps ) { - if ( ! prevProps.isReady && this.props.isReady ) { - const blocks = this.props.getEditorBlocks(); - const isUnsupportedBlock = ( { name } ) => name === getUnregisteredTypeHandlerName(); - const unsupportedBlockNames = blocks.filter( isUnsupportedBlock ).map( ( block ) => block.attributes.originalName ); - RNReactNativeGutenbergBridge.editorDidMount( unsupportedBlockNames ); - } - } + render() { + const { + settings, + hasFixedToolbar, + focusMode, + initialEdits, + hiddenBlockTypes, + blockTypes, + post, + ...props + } = this.props; + + const editorSettings = this.getEditorSettings( + settings, + hasFixedToolbar, + focusMode, + hiddenBlockTypes, + blockTypes, + ); - setTitleRef( titleRef ) { - this.postTitleRef = titleRef; - } + const normalizedPost = post || { + id: 1, + title: { + raw: props.initialTitle, + }, + content: { + // make sure the post content is in sync with gutenberg store + // to avoid marking the post as modified when simply loaded + // For now, let's assume: serialize( parse( html ) ) !== html + raw: serialize( parse( props.initialHtml || '' ) ), + }, + type: 'draft', + }; - render() { return ( - <Layout - setTitleRef={ this.setTitleRef } - onNativeEditorDidLayout={ sendNativeEditorDidLayout } - /> + <SlotFillProvider> + <EditorProvider + settings={ editorSettings } + post={ normalizedPost } + initialEdits={ initialEdits } + useSubRegistry={ false } + { ...props } + > + <Layout setTitleRef={ this.setTitleRef } /> + </EditorProvider> + </SlotFillProvider> ); } } export default compose( [ withSelect( ( select ) => { - const { - __unstableIsEditorReady: isEditorReady, - getEditorBlocks, - getEditedPostAttribute, - getEditedPostContent, - } = select( 'core/editor' ); - const { - getEditorMode, - } = select( 'core/edit-post' ); + const { isFeatureActive, getEditorMode, getPreference } = select( 'core/edit-post' ); + const { getBlockTypes } = select( 'core/blocks' ); return { + hasFixedToolbar: isFeatureActive( 'fixedToolbar' ), + focusMode: isFeatureActive( 'focusMode' ), mode: getEditorMode(), - isReady: isEditorReady(), - getEditorBlocks, - getEditedPostAttribute, - getEditedPostContent, + hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), + blockTypes: getBlockTypes(), }; } ), withDispatch( ( dispatch ) => { - const { - editPost, - setupEditor, - resetEditorBlocks, - } = dispatch( 'core/editor' ); const { switchEditorMode, } = dispatch( 'core/edit-post' ); return { - editTitle( title ) { - editPost( { title } ); - }, - resetEditorBlocksWithoutUndoLevel( blocks ) { - resetEditorBlocks( blocks, { - __unstableShouldCreateUndoLevel: false, - } ); - }, - setupEditor, - switchMode( mode ) { - switchEditorMode( mode ); - }, + switchEditorMode, }; } ), ] )( Editor ); diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 1ffdcb13477335..296e37117e5a1d 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -4,6 +4,7 @@ import '@wordpress/core-data'; import '@wordpress/block-editor'; import '@wordpress/editor'; +import '@wordpress/viewport'; import '@wordpress/notices'; import { registerCoreBlocks } from '@wordpress/block-library'; import { unregisterBlockType } from '@wordpress/blocks'; diff --git a/packages/edit-post/src/store/defaults.native.js b/packages/edit-post/src/store/defaults.native.js new file mode 100644 index 00000000000000..7ac420b872cadc --- /dev/null +++ b/packages/edit-post/src/store/defaults.native.js @@ -0,0 +1,14 @@ +export const PREFERENCES_DEFAULTS = { + editorMode: 'visual', + isGeneralSidebarDismissed: true, + panels: { + 'post-status': { + opened: true, + }, + }, + features: { + fixedToolbar: true, + }, + pinnedPluginItems: {}, + hiddenBlockTypes: [], +}; diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index aee6c0fe505860..cedd6bf8c94b37 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -15,7 +15,7 @@ jest.mock( '../components/layout', () => () => 'Layout' ); /** * Internal dependencies */ -import '../store'; +import '..'; import Editor from '../editor'; const unsupportedBlock = ` diff --git a/packages/editor/src/components/convert-to-group-buttons/index.native.js b/packages/editor/src/components/convert-to-group-buttons/index.native.js new file mode 100644 index 00000000000000..bd0c2f440d06f2 --- /dev/null +++ b/packages/editor/src/components/convert-to-group-buttons/index.native.js @@ -0,0 +1,2 @@ + +export default () => null; diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 85a33d69bf2515..69035455d49f13 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -4,4 +4,7 @@ export { default as PostTitle } from './post-title'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; +// State Related Components +export { default as EditorProvider } from './provider'; + export * from './deprecated'; diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js new file mode 100644 index 00000000000000..d2b1458d3e2e56 --- /dev/null +++ b/packages/editor/src/components/provider/index.native.js @@ -0,0 +1,182 @@ +/** + * External dependencies + */ +import RNReactNativeGutenbergBridge, { + subscribeParentGetHtml, + subscribeParentToggleHTMLMode, + subscribeUpdateHtml, + subscribeSetFocusOnTitle, + subscribeSetTitle, +} from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { parse, serialize, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import EditorProvider from './index.js'; + +class NativeEditorProvider extends Component { + constructor( props ) { + super( ...arguments ); + + // Keep a local reference to `post` to detect changes + this.post = props.post; + + this.setTitleRef = this.setTitleRef.bind( this ); + } + + componentDidMount() { + this.subscriptionParentGetHtml = subscribeParentGetHtml( () => { + this.serializeToNativeAction(); + } ); + + this.subscriptionParentToggleHTMLMode = subscribeParentToggleHTMLMode( () => { + this.toggleMode(); + } ); + + this.subscriptionParentSetTitle = subscribeSetTitle( ( payload ) => { + this.props.editTitle( payload.title ); + } ); + + this.subscriptionParentUpdateHtml = subscribeUpdateHtml( ( payload ) => { + this.updateHtmlAction( payload.html ); + } ); + + this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( () => { + if ( this.postTitleRef ) { + this.postTitleRef.focus(); + } + } ); + } + + componentWillUnmount() { + if ( this.subscriptionParentGetHtml ) { + this.subscriptionParentGetHtml.remove(); + } + + if ( this.subscriptionParentToggleHTMLMode ) { + this.subscriptionParentToggleHTMLMode.remove(); + } + + if ( this.subscriptionParentSetTitle ) { + this.subscriptionParentSetTitle.remove(); + } + + if ( this.subscriptionParentUpdateHtml ) { + this.subscriptionParentUpdateHtml.remove(); + } + + if ( this.subscriptionParentSetFocusOnTitle ) { + this.subscriptionParentSetFocusOnTitle.remove(); + } + } + + componentDidUpdate( prevProps ) { + if ( ! prevProps.isReady && this.props.isReady ) { + const blocks = this.props.blocks; + const isUnsupportedBlock = ( { name } ) => name === getUnregisteredTypeHandlerName(); + const unsupportedBlockNames = blocks.filter( isUnsupportedBlock ).map( ( block ) => block.attributes.originalName ); + RNReactNativeGutenbergBridge.editorDidMount( unsupportedBlockNames ); + } + } + + setTitleRef( titleRef ) { + this.postTitleRef = titleRef; + } + + serializeToNativeAction() { + if ( this.props.mode === 'text' ) { + this.updateHtmlAction( this.props.getEditedPostContent() ); + } + + const html = serialize( this.props.blocks ); + const title = this.props.title; + + const hasChanges = title !== this.post.title.raw || html !== this.post.content.raw; + + RNReactNativeGutenbergBridge.provideToNative_Html( html, title, hasChanges ); + + if ( hasChanges ) { + this.post.title.raw = title; + this.post.content.raw = html; + } + } + + updateHtmlAction( html ) { + const parsed = parse( html ); + this.props.resetEditorBlocksWithoutUndoLevel( parsed ); + } + + toggleMode() { + const { mode, switchMode } = this.props; + // refresh html content first + this.serializeToNativeAction(); + switchMode( mode === 'visual' ? 'text' : 'visual' ); + } + + render() { + const { + children, + post, // eslint-disable-line no-unused-vars + ...props + } = this.props; + + return ( + <EditorProvider post={ this.post } { ...props }> + { children } + </EditorProvider> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + __unstableIsEditorReady: isEditorReady, + getEditorBlocks, + getEditedPostAttribute, + getEditedPostContent, + } = select( 'core/editor' ); + const { + getEditorMode, + } = select( 'core/edit-post' ); + + return { + mode: getEditorMode(), + isReady: isEditorReady(), + blocks: getEditorBlocks(), + title: getEditedPostAttribute( 'title' ), + getEditedPostContent, + }; + } ), + withDispatch( ( dispatch ) => { + const { + editPost, + resetEditorBlocks, + } = dispatch( 'core/editor' ); + const { + switchEditorMode, + } = dispatch( 'core/edit-post' ); + + return { + editTitle( title ) { + editPost( { title } ); + }, + resetEditorBlocksWithoutUndoLevel( blocks ) { + resetEditorBlocks( blocks, { + __unstableShouldCreateUndoLevel: false, + } ); + }, + switchMode( mode ) { + switchEditorMode( mode ); + }, + }; + } ), +] )( NativeEditorProvider ); diff --git a/packages/editor/src/components/reusable-blocks-buttons/index.native.js b/packages/editor/src/components/reusable-blocks-buttons/index.native.js new file mode 100644 index 00000000000000..bd0c2f440d06f2 --- /dev/null +++ b/packages/editor/src/components/reusable-blocks-buttons/index.native.js @@ -0,0 +1,2 @@ + +export default () => null; diff --git a/packages/viewport/src/index.native.js b/packages/viewport/src/index.native.js index 8b137891791fe9..605b511ac9d686 100644 --- a/packages/viewport/src/index.native.js +++ b/packages/viewport/src/index.native.js @@ -1 +1,8 @@ +/** + * Internal dependencies + */ +import './store'; + +export { default as ifViewportMatches } from './if-viewport-matches'; +export { default as withViewportMatch } from './with-viewport-match'; From b32345c13551963db2d1157ee8e9f0a93022c1ff Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Mon, 5 Aug 2019 13:28:58 +0200 Subject: [PATCH 606/664] chore(release): publish - @wordpress/a11y@2.5.0 - @wordpress/annotations@1.5.0 - @wordpress/api-fetch@3.4.0 - @wordpress/autop@2.4.0 - @wordpress/babel-plugin-import-jsx-pragma@2.3.0 - @wordpress/babel-plugin-makepot@3.2.0 - @wordpress/babel-preset-default@4.4.0 - @wordpress/blob@2.5.0 - @wordpress/block-editor@3.0.0 - @wordpress/block-library@2.7.0 - @wordpress/block-serialization-default-parser@3.3.0 - @wordpress/block-serialization-spec-parser@3.2.0 - @wordpress/blocks@6.5.0 - @wordpress/browserslist-config@2.6.0 - @wordpress/components@8.1.0 - @wordpress/compose@3.5.0 - @wordpress/core-data@2.5.0 - @wordpress/custom-templated-path-webpack-plugin@1.5.0 - @wordpress/data-controls@1.1.0 - @wordpress/data@4.7.0 - @wordpress/date@3.4.0 - @wordpress/dependency-extraction-webpack-plugin@1.1.0 - @wordpress/deprecated@2.5.0 - @wordpress/docgen@1.3.0 - @wordpress/dom-ready@2.5.0 - @wordpress/dom@2.4.0 - @wordpress/e2e-test-utils@2.2.0 - @wordpress/e2e-tests@1.4.0 - @wordpress/edit-post@3.6.0 - @wordpress/edit-widgets@0.5.0 - @wordpress/editor@9.5.0 - @wordpress/element@2.6.0 - @wordpress/escape-html@1.5.0 - @wordpress/eslint-plugin@2.4.0 - @wordpress/format-library@1.7.0 - @wordpress/hooks@2.5.0 - @wordpress/html-entities@2.5.0 - @wordpress/i18n@3.6.0 - @wordpress/is-shallow-equal@1.5.0 - @wordpress/jest-console@3.2.0 - @wordpress/jest-preset-default@4.3.0 - @wordpress/jest-puppeteer-axe@1.2.0 - @wordpress/keycodes@2.5.0 - @wordpress/library-export-default-webpack-plugin@1.4.0 - @wordpress/list-reusable-blocks@1.6.0 - @wordpress/media-utils@1.0.0 - @wordpress/notices@1.6.0 - @wordpress/npm-package-json-lint-config@2.1.0 - @wordpress/nux@3.5.0 - @wordpress/plugins@2.5.0 - @wordpress/postcss-themes@2.2.0 - @wordpress/priority-queue@1.3.0 - @wordpress/redux-routine@3.5.0 - @wordpress/rich-text@3.5.0 - @wordpress/scripts@3.4.0 - @wordpress/server-side-render@1.1.0 - @wordpress/shortcode@2.4.0 - @wordpress/token-list@1.5.0 - @wordpress/url@2.7.0 - @wordpress/viewport@2.6.0 - @wordpress/wordcount@2.5.0 --- packages/a11y/package.json | 2 +- packages/annotations/package.json | 2 +- packages/api-fetch/package.json | 2 +- packages/autop/package.json | 2 +- packages/babel-plugin-import-jsx-pragma/package.json | 2 +- packages/babel-plugin-makepot/package.json | 2 +- packages/babel-preset-default/package.json | 2 +- packages/blob/package.json | 2 +- packages/block-editor/package.json | 2 +- packages/block-library/package.json | 2 +- packages/block-serialization-default-parser/package.json | 2 +- packages/block-serialization-spec-parser/package.json | 2 +- packages/blocks/package.json | 2 +- packages/browserslist-config/package.json | 2 +- packages/components/package.json | 2 +- packages/compose/package.json | 2 +- packages/core-data/package.json | 2 +- packages/custom-templated-path-webpack-plugin/package.json | 2 +- packages/data-controls/package.json | 2 +- packages/data/package.json | 2 +- packages/date/package.json | 2 +- packages/dependency-extraction-webpack-plugin/package.json | 2 +- packages/deprecated/package.json | 2 +- packages/docgen/package.json | 2 +- packages/dom-ready/package.json | 2 +- packages/dom/package.json | 2 +- packages/e2e-test-utils/package.json | 2 +- packages/e2e-tests/package.json | 2 +- packages/edit-post/package.json | 2 +- packages/edit-widgets/package.json | 2 +- packages/editor/package.json | 2 +- packages/element/package.json | 2 +- packages/escape-html/package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/format-library/package.json | 2 +- packages/hooks/package.json | 2 +- packages/html-entities/package.json | 2 +- packages/i18n/package.json | 2 +- packages/is-shallow-equal/package.json | 2 +- packages/jest-console/package.json | 2 +- packages/jest-preset-default/package.json | 2 +- packages/jest-puppeteer-axe/package.json | 2 +- packages/keycodes/package.json | 2 +- packages/library-export-default-webpack-plugin/package.json | 2 +- packages/list-reusable-blocks/package.json | 2 +- packages/media-utils/package.json | 2 +- packages/notices/package.json | 2 +- packages/npm-package-json-lint-config/package.json | 2 +- packages/nux/package.json | 2 +- packages/plugins/package.json | 2 +- packages/postcss-themes/package.json | 2 +- packages/priority-queue/package.json | 2 +- packages/redux-routine/package.json | 2 +- packages/rich-text/package.json | 2 +- packages/scripts/package.json | 2 +- packages/server-side-render/package.json | 2 +- packages/shortcode/package.json | 2 +- packages/token-list/package.json | 2 +- packages/url/package.json | 2 +- packages/viewport/package.json | 2 +- packages/wordcount/package.json | 2 +- 61 files changed, 61 insertions(+), 61 deletions(-) diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 1621eb97643289..546abcd609a520 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/a11y", - "version": "2.4.0", + "version": "2.5.0", "description": "Accessibility (a11y) utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/annotations/package.json b/packages/annotations/package.json index 72d181b0fa90b0..ba26b349603ace 100644 --- a/packages/annotations/package.json +++ b/packages/annotations/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/annotations", - "version": "1.4.0", + "version": "1.5.0", "description": "Annotate content in the Gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/api-fetch/package.json b/packages/api-fetch/package.json index 0221ac0a1e8a37..7f944ce3cbcf21 100644 --- a/packages/api-fetch/package.json +++ b/packages/api-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/api-fetch", - "version": "3.3.0", + "version": "3.4.0", "description": "Utility to make WordPress REST API requests.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/autop/package.json b/packages/autop/package.json index 796a500413da8e..41d14a0b0a534a 100644 --- a/packages/autop/package.json +++ b/packages/autop/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/autop", - "version": "2.3.0", + "version": "2.4.0", "description": "WordPress's automatic paragraph functions `autop` and `removep`.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-import-jsx-pragma/package.json b/packages/babel-plugin-import-jsx-pragma/package.json index 6f4b029ebb5e64..b744b8a3f0dd51 100644 --- a/packages/babel-plugin-import-jsx-pragma/package.json +++ b/packages/babel-plugin-import-jsx-pragma/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-import-jsx-pragma", - "version": "2.2.0", + "version": "2.3.0", "description": "Babel transform plugin for automatically injecting an import to be used as the pragma for the React JSX Transform plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-plugin-makepot/package.json b/packages/babel-plugin-makepot/package.json index dd32f8e4ae78f5..29747041e3c5fc 100644 --- a/packages/babel-plugin-makepot/package.json +++ b/packages/babel-plugin-makepot/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-plugin-makepot", - "version": "3.1.0", + "version": "3.2.0", "description": "WordPress Babel internationalization (i18n) plugin.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/babel-preset-default/package.json b/packages/babel-preset-default/package.json index 3bf0df978d6ebb..a6e298d055ac07 100644 --- a/packages/babel-preset-default/package.json +++ b/packages/babel-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/babel-preset-default", - "version": "4.3.0", + "version": "4.4.0", "description": "Default Babel preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blob/package.json b/packages/blob/package.json index 187ad40385d1a5..ea86e634737810 100644 --- a/packages/blob/package.json +++ b/packages/blob/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blob", - "version": "2.4.0", + "version": "2.5.0", "description": "Blob utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 61c49f9d9a088e..beab36f210740c 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-editor", - "version": "2.2.0", + "version": "3.0.0", "description": "Generic block editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-library/package.json b/packages/block-library/package.json index e3be5d583fdc90..3e535e3e8bf580 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.6.0", + "version": "2.7.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index f4509975a0831b..9f414ff00ecdb3 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "3.2.0", + "version": "3.3.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index a4faf314c37169..20102b0c217844 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "3.1.0", + "version": "3.2.0", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/package.json b/packages/blocks/package.json index a296a3ca6975b7..95fa54024c9b22 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.4.0", + "version": "6.5.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/browserslist-config/package.json b/packages/browserslist-config/package.json index 9e70302edeb817..318a8f299899e2 100644 --- a/packages/browserslist-config/package.json +++ b/packages/browserslist-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/browserslist-config", - "version": "2.5.0", + "version": "2.6.0", "description": "WordPress Browserslist shared configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/components/package.json b/packages/components/package.json index 3a1381367b463a..7421ef260b5439 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "8.0.0", + "version": "8.1.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/compose/package.json b/packages/compose/package.json index 6ca617b8d43c40..d219a5a6aebb4a 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "3.4.0", + "version": "3.5.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index ccf20e4efee3e6..6f55b40cd39f69 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.4.0", + "version": "2.5.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/custom-templated-path-webpack-plugin/package.json b/packages/custom-templated-path-webpack-plugin/package.json index 463e59991aa46b..e00a9b36cfbefe 100644 --- a/packages/custom-templated-path-webpack-plugin/package.json +++ b/packages/custom-templated-path-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/custom-templated-path-webpack-plugin", - "version": "1.4.0", + "version": "1.5.0", "description": "Webpack plugin for creating custom path template tags.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index e54574c9eec93b..8f3715c1cc1450 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "1.0.0", + "version": "1.1.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data/package.json b/packages/data/package.json index 6c256fedcc64b7..72794a6b4209b0 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.6.0", + "version": "4.7.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/package.json b/packages/date/package.json index aecd12e085e93a..3ba157c866f805 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "3.3.0", + "version": "3.4.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 89f7693337577b..062e3433e1ab17 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "1.0.1", + "version": "1.1.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index 4315c6d81039aa..6b32349e442fa3 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "2.4.0", + "version": "2.5.0", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 645008645fa8e0..288783fad638d5 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.2.0", + "version": "1.3.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 827accd9b47dd7..19efb56d67eedd 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "2.4.0", + "version": "2.5.0", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/dom/package.json b/packages/dom/package.json index 1ee73ddb2dd0d6..e2543dc12090a3 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "2.3.0", + "version": "2.4.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 25b6889ec1530a..f773eb99a82750 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "2.1.0", + "version": "2.2.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index c615a0e7541dfd..17ec9ba8188c88 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.3.0", + "version": "1.4.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 09cebd91d061b5..92cba80719a80a 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.5.0", + "version": "3.6.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index bf71d3bdd4e344..56f8c4e9bd57a8 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "0.4.0", + "version": "0.5.0", "private": true, "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", diff --git a/packages/editor/package.json b/packages/editor/package.json index 88ddf540a926aa..bed0ff5b61c23a 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.4.0", + "version": "9.5.0", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/element/package.json b/packages/element/package.json index f793bd4414bc6d..342d508da098fa 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "2.5.0", + "version": "2.6.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 4d2b81bd73259b..2b3c55a0f80e93 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "1.4.0", + "version": "1.5.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 9bcd60b15de867..b8c4bc4b485129 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "2.3.0", + "version": "2.4.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index e84f04d29956e6..84f47c5fc58828 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.6.0", + "version": "1.7.0", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index bac2718aceb28a..68d702018a3f25 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/hooks", - "version": "2.4.0", + "version": "2.5.0", "description": "WordPress hooks library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json index a6f449b3e2aaf0..b0ba97b8cdec82 100644 --- a/packages/html-entities/package.json +++ b/packages/html-entities/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/html-entities", - "version": "2.4.0", + "version": "2.5.0", "description": "HTML entity utilities for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 23307b7815eef9..e2a538a3130b42 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "3.5.0", + "version": "3.6.0", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index 3850ff5e8f7d4e..618f6487d4a66f 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "1.4.0", + "version": "1.5.0", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 2548fb7d34ad24..6b14900e0e3b27 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-console", - "version": "3.1.0", + "version": "3.2.0", "description": "Custom Jest matchers for the Console object.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index cb5de531080c01..f4dc3aef47882f 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "4.2.0", + "version": "4.3.0", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index 3370fa4cb9b3a6..eaf4025995ad47 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-puppeteer-axe", - "version": "1.1.0", + "version": "1.2.0", "description": "Axe API integration with Jest and Puppeteer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index 945e40dffb0a27..04eaf845439cea 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.4.0", + "version": "2.5.0", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index e14e8abdaa0c0e..31da9e75b077f9 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "1.3.0", + "version": "1.4.0", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 5fee209e114f97..8e8fe5e480265a 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.5.0", + "version": "1.6.0", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 6b4d553ad48318..5ea55ec7a436d0 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/media-utils", - "version": "0.2.0", + "version": "1.0.0", "description": "WordPress Media Upload Utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/package.json b/packages/notices/package.json index 2afe1ce72c4355..e056cf43bf6c78 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.5.0", + "version": "1.6.0", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/npm-package-json-lint-config/package.json b/packages/npm-package-json-lint-config/package.json index c05441fbf88adc..058dadc1d4eff4 100644 --- a/packages/npm-package-json-lint-config/package.json +++ b/packages/npm-package-json-lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/npm-package-json-lint-config", - "version": "2.0.0", + "version": "2.1.0", "description": "WordPress npm-package-json-lint shareable configuration.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/package.json b/packages/nux/package.json index 5391fd8bdd7ea0..88e07bbc0b4c25 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.4.0", + "version": "3.5.0", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index a7c65a4dea5f75..b1ea4eb8da18c0 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "2.4.0", + "version": "2.5.0", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/postcss-themes/package.json b/packages/postcss-themes/package.json index 81dece9744c76e..b19f52e5ef976b 100644 --- a/packages/postcss-themes/package.json +++ b/packages/postcss-themes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/postcss-themes", - "version": "2.1.0", + "version": "2.2.0", "description": "PostCSS plugin to generate theme colors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index b3d6eefbb973af..14026df5257147 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/priority-queue", - "version": "1.2.0", + "version": "1.3.0", "description": "Generic browser priority queue.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 3f6fea2c863b62..d8e0a7a99dbb8c 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "3.4.0", + "version": "3.5.0", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 02aac8be88dd14..1719ada3c18941 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.4.0", + "version": "3.5.0", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 4001466804b733..b1ab422f0af068 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "3.3.0", + "version": "3.4.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 3d6af1e7f21073..e43b579d0e4df9 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "1.0.0", + "version": "1.1.0", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index 5718879964be8d..cda2d2b6916865 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/shortcode", - "version": "2.3.0", + "version": "2.4.0", "description": "Shortcode module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 28aa9e50364123..93a6b0dae53894 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "1.4.0", + "version": "1.5.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/url/package.json b/packages/url/package.json index de69e9363e7a3b..4273f0a8f07bf8 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "2.6.0", + "version": "2.7.0", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 29e49449ac541c..f0e9a81a1005b8 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.5.0", + "version": "2.6.0", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index bb6db801438446..e929df4e989a7a 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "2.4.0", + "version": "2.5.0", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 237128744dbd43ae6ab95573a7ab4d667f403346 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Mon, 5 Aug 2019 13:42:15 +0200 Subject: [PATCH 607/664] Updating changelogs for the Gutenberg 6.2 packages release --- packages/babel-plugin-import-jsx-pragma/CHANGELOG.md | 2 +- packages/babel-preset-default/CHANGELOG.md | 2 +- packages/block-editor/CHANGELOG.md | 2 +- packages/block-library/CHANGELOG.md | 2 +- packages/blocks/CHANGELOG.md | 2 +- packages/components/CHANGELOG.md | 2 +- packages/data-controls/CHANGELOG.md | 2 +- packages/docgen/CHANGELOG.md | 2 +- packages/edit-post/CHANGELOG.md | 2 +- packages/eslint-plugin/CHANGELOG.md | 7 ++++++- packages/is-shallow-equal/CHANGELOG.md | 2 +- packages/scripts/CHANGELOG.md | 2 +- packages/server-side-render/CHANGELOG.md | 2 +- 13 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 27860040e61090..8be173174858b5 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.1.0 (unreleased) +## 2.4.0 (2019-08-05) ### New Feature diff --git a/packages/babel-preset-default/CHANGELOG.md b/packages/babel-preset-default/CHANGELOG.md index ad77db1ec4a04b..e84e32066de1ea 100644 --- a/packages/babel-preset-default/CHANGELOG.md +++ b/packages/babel-preset-default/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 4.4.0 (2019-08-05) ### Bug Fixes diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 67c7e2a9725f74..07abd2b81221bb 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 3.0.0 (2019-08-05) ### New Features diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 78e90241d1869a..c76eacd5a4bee5 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master (unreleased) +## 2.7.0 (2019-08-05) ### Enhancements diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index fdbc957dd802e7..82d571202f45fa 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 6.4.0 (2019-08-05) ### Improvements diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 1ddc0573e2be23..de312a2b2e7615 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 8.1.0 (2019-08-05) ### New Features diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index dff204f06c92d6..f07bc9249d3344 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -1,3 +1,3 @@ -# Master +## 1.0.0 (2019-06-12) Initial release of the @wordpress/data-controls package. diff --git a/packages/docgen/CHANGELOG.md b/packages/docgen/CHANGELOG.md index f81c562f3011c5..e00d1c2064ae83 100644 --- a/packages/docgen/CHANGELOG.md +++ b/packages/docgen/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.2.1 (Unreleased) +## 1.3.0 (2019-08-05) ### Bug Fixes diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 52bbfd658b1b2c..2a17afc9ad02b8 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 3.6.0 (2019-08-05) ### Refactor diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index b83a615c712818..b8693b5eef709f 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -6,9 +6,14 @@ ### New Features -- [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) now supports an `excludePattern` option to exempt function calls by name. - New Rule: [`@wordpress/no-unguarded-get-range-at`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) +## 2.4.0 (2019-08-05) + +### New Features + +- [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) now supports an `excludePattern` option to exempt function calls by name. + ### Improvements - The recommended `react` configuration specifies an option to [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) to exempt React hooks usage, by convention of hooks beginning with "use" prefix. diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md index df25b00178ac17..17019daee1ea6b 100644 --- a/packages/is-shallow-equal/CHANGELOG.md +++ b/packages/is-shallow-equal/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 1.5.0 (2019-08-05) ### Bug Fixes diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 322369b244bdf0..31bb82b82be37d 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 3.4.0 (2019-08-05) ### New Features diff --git a/packages/server-side-render/CHANGELOG.md b/packages/server-side-render/CHANGELOG.md index 04ea9fe434a6fb..7781506cc47626 100644 --- a/packages/server-side-render/CHANGELOG.md +++ b/packages/server-side-render/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 1.1.0 (2019-08-05) ### Enhancements From c5a5c94317dafcd36d85e0992e711d5b6bf58c63 Mon Sep 17 00:00:00 2001 From: Miina <miina.sikk@gmail.com> Date: Mon, 5 Aug 2019 16:23:03 +0300 Subject: [PATCH 608/664] Fully disable animation style if enableAnimations is false. (#16893) --- .../components/block-list/moving-animation.js | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js index 4cb9b0ea9fa296..1d4afe2d6c5d68 100644 --- a/packages/block-editor/src/components/block-list/moving-animation.js +++ b/packages/block-editor/src/components/block-list/moving-animation.js @@ -75,23 +75,26 @@ function useMovingAnimation( ref, isSelected, enableAnimation, triggerAnimationO immediate: prefersReducedMotion, } ); - return { - transformOrigin: 'center', - transform: interpolate( - [ - animationProps.x, - animationProps.y, - ], - ( x, y ) => x === 0 && y === 0 ? undefined : `translate3d(${ x }px,${ y }px,0)` - ), - zIndex: interpolate( - [ - animationProps.x, - animationProps.y, - ], - ( x, y ) => ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1` - ), - }; + // Dismiss animations if disabled. + return prefersReducedMotion ? + {} : + { + transformOrigin: 'center', + transform: interpolate( + [ + animationProps.x, + animationProps.y, + ], + ( x, y ) => x === 0 && y === 0 ? undefined : `translate3d(${ x }px,${ y }px,0)` + ), + zIndex: interpolate( + [ + animationProps.x, + animationProps.y, + ], + ( x, y ) => ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1` + ), + }; } export default useMovingAnimation; From 1df83a3998d1acbf224f96bbe4320b607747cb45 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Mon, 5 Aug 2019 09:30:11 -0400 Subject: [PATCH 609/664] Core Data: Add support for entity edits and undo history. (#16867) * Core Data: Add support for entity edits and undo history. * Core Data: Update docs. --- .../developers/data/data-core.md | 199 +++++++++++++++++- packages/core-data/README.md | 199 +++++++++++++++++- packages/core-data/src/actions.js | 137 ++++++++++-- packages/core-data/src/entities.js | 2 + .../core-data/src/queried-data/reducer.js | 10 +- packages/core-data/src/reducer.js | 142 ++++++++++++- packages/core-data/src/selectors.js | 181 +++++++++++++++- packages/core-data/src/test/actions.js | 18 +- packages/core-data/src/test/reducer.js | 14 +- packages/core-data/src/test/selectors.js | 34 +-- 10 files changed, 891 insertions(+), 45 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index 3f823475fcca1c..ceb9daef907720 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -71,6 +71,22 @@ _Returns_ - `?Array`: An array of autosaves for the post, or undefined if there is none. +<a name="getCurrentUndoOffset" href="#getCurrentUndoOffset">#</a> **getCurrentUndoOffset** + +Returns the current undo offset for the +entity records edits history. The offset +represents how many items from the end +of the history stack we are at. 0 is the +last edit, -1 is the second last, and so on. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `number`: The current undo offset. + <a name="getCurrentUser" href="#getCurrentUser">#</a> **getCurrentUser** Returns the current user. @@ -83,6 +99,21 @@ _Returns_ - `Object`: Current user object. +<a name="getEditedEntityRecord" href="#getEditedEntityRecord">#</a> **getEditedEntityRecord** + +Returns the specified entity record, merged with its edits. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record, merged with its edits. + <a name="getEmbedPreview" href="#getEmbedPreview">#</a> **getEmbedPreview** Returns the embed preview for the given URL. @@ -138,6 +169,40 @@ _Returns_ - `?Object`: Record. +<a name="getEntityRecordEdits" href="#getEntityRecordEdits">#</a> **getEntityRecordEdits** + +Returns the specified entity record's edits. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record's edits. + +<a name="getEntityRecordNonTransientEdits" href="#getEntityRecordNonTransientEdits">#</a> **getEntityRecordNonTransientEdits** + +Returns the specified entity record's non transient edits. + +Transient edits don't create an undo level, and +are not considered for change detection. +They are defined in the entity's config. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record's non transient edits. + <a name="getEntityRecords" href="#getEntityRecords">#</a> **getEntityRecords** Returns the Entity's records. @@ -153,6 +218,34 @@ _Returns_ - `Array`: Records. +<a name="getLastEntitySaveError" href="#getLastEntitySaveError">#</a> **getLastEntitySaveError** + +Returns the specified entity record's last save error. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record's save error. + +<a name="getRedoEdit" href="#getRedoEdit">#</a> **getRedoEdit** + +Returns the next edit from the current undo offset +for the entity records edits history, if any. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `?Object`: The edit. + <a name="getThemeSupports" href="#getThemeSupports">#</a> **getThemeSupports** Return theme supports data in the index. @@ -165,6 +258,19 @@ _Returns_ - `*`: Index data. +<a name="getUndoEdit" href="#getUndoEdit">#</a> **getUndoEdit** + +Returns the previous edit from the current undo offset +for the entity records edits history, if any. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `?Object`: The edit. + <a name="getUserQueryResults" href="#getUserQueryResults">#</a> **getUserQueryResults** Returns all the users returned by a query ID. @@ -178,6 +284,22 @@ _Returns_ - `Array`: Users list. +<a name="hasEditsForEntityRecord" href="#hasEditsForEntityRecord">#</a> **hasEditsForEntityRecord** + +Returns true if the specified entity record has edits, +and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `boolean`: Whether the entity record has edits or not. + <a name="hasFetchedAutosaves" href="#hasFetchedAutosaves">#</a> **hasFetchedAutosaves** Returns true if the REST request for autosaves has completed. @@ -192,6 +314,32 @@ _Returns_ - `boolean`: True if the REST request was completed. False otherwise. +<a name="hasRedo" href="#hasRedo">#</a> **hasRedo** + +Returns true if there is a next edit from the current undo offset +for the entity records edits history, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `boolean`: Whether there is a next edit or not. + +<a name="hasUndo" href="#hasUndo">#</a> **hasUndo** + +Returns true if there is a previous edit from the current undo offset +for the entity records edits history, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `boolean`: Whether there is a previous edit or not. + <a name="hasUploadPermissions" href="#hasUploadPermissions">#</a> **hasUploadPermissions** > **Deprecated** since 5.0. Callers should use the more generic `canUser()` selector instead of `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. @@ -242,6 +390,21 @@ _Returns_ - `boolean`: Whether a request is in progress for an embed preview. +<a name="isSavingEntityRecord" href="#isSavingEntityRecord">#</a> **isSavingEntityRecord** + +Returns true if the specified entity record is saving, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: Whether the entity record is saving or not. + <!-- END TOKEN(Autogenerated selectors) --> @@ -261,6 +424,22 @@ _Returns_ - `Object`: Action object. +<a name="editEntityRecord" href="#editEntityRecord">#</a> **editEntityRecord** + +Returns an action object that triggers an +edit to an entity record. + +_Parameters_ + +- _kind_ `string`: Kind of the edited entity record. +- _name_ `string`: Name of the edited entity record. +- _recordId_ `number`: Record ID of the edited entity record. +- _edits_ `Object`: The edits. + +_Returns_ + +- `Object`: Action object. + <a name="receiveAutosaves" href="#receiveAutosaves">#</a> **receiveAutosaves** Returns an action object used in signalling that the autosaves for a @@ -368,6 +547,21 @@ _Returns_ - `Object`: Action object. +<a name="redo" href="#redo">#</a> **redo** + +Action triggered to redo the last undoed +edit to an entity record, if any. + +<a name="saveEditedEntityRecord" href="#saveEditedEntityRecord">#</a> **saveEditedEntityRecord** + +Action triggered to save an entity record's edits. + +_Parameters_ + +- _kind_ `string`: Kind of the entity. +- _name_ `string`: Name of the entity. +- _recordId_ `Object`: ID of the record. + <a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord** Action triggered to save an entity record. @@ -378,8 +572,9 @@ _Parameters_ - _name_ `string`: Name of the received entity. - _record_ `Object`: Record to be saved. -_Returns_ +<a name="undo" href="#undo">#</a> **undo** -- `Object`: Updated record. +Action triggered to undo the last edit to +an entity record, if any. <!-- END TOKEN(Autogenerated actions) --> diff --git a/packages/core-data/README.md b/packages/core-data/README.md index a2fb271017fcd7..cf9f0a9989b28f 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -54,6 +54,22 @@ _Returns_ - `Object`: Action object. +<a name="editEntityRecord" href="#editEntityRecord">#</a> **editEntityRecord** + +Returns an action object that triggers an +edit to an entity record. + +_Parameters_ + +- _kind_ `string`: Kind of the edited entity record. +- _name_ `string`: Name of the edited entity record. +- _recordId_ `number`: Record ID of the edited entity record. +- _edits_ `Object`: The edits. + +_Returns_ + +- `Object`: Action object. + <a name="receiveAutosaves" href="#receiveAutosaves">#</a> **receiveAutosaves** Returns an action object used in signalling that the autosaves for a @@ -161,6 +177,21 @@ _Returns_ - `Object`: Action object. +<a name="redo" href="#redo">#</a> **redo** + +Action triggered to redo the last undoed +edit to an entity record, if any. + +<a name="saveEditedEntityRecord" href="#saveEditedEntityRecord">#</a> **saveEditedEntityRecord** + +Action triggered to save an entity record's edits. + +_Parameters_ + +- _kind_ `string`: Kind of the entity. +- _name_ `string`: Name of the entity. +- _recordId_ `Object`: ID of the record. + <a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord** Action triggered to save an entity record. @@ -171,9 +202,10 @@ _Parameters_ - _name_ `string`: Name of the received entity. - _record_ `Object`: Record to be saved. -_Returns_ +<a name="undo" href="#undo">#</a> **undo** -- `Object`: Updated record. +Action triggered to undo the last edit to +an entity record, if any. <!-- END TOKEN(Autogenerated actions) --> @@ -248,6 +280,22 @@ _Returns_ - `?Array`: An array of autosaves for the post, or undefined if there is none. +<a name="getCurrentUndoOffset" href="#getCurrentUndoOffset">#</a> **getCurrentUndoOffset** + +Returns the current undo offset for the +entity records edits history. The offset +represents how many items from the end +of the history stack we are at. 0 is the +last edit, -1 is the second last, and so on. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `number`: The current undo offset. + <a name="getCurrentUser" href="#getCurrentUser">#</a> **getCurrentUser** Returns the current user. @@ -260,6 +308,21 @@ _Returns_ - `Object`: Current user object. +<a name="getEditedEntityRecord" href="#getEditedEntityRecord">#</a> **getEditedEntityRecord** + +Returns the specified entity record, merged with its edits. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record, merged with its edits. + <a name="getEmbedPreview" href="#getEmbedPreview">#</a> **getEmbedPreview** Returns the embed preview for the given URL. @@ -315,6 +378,40 @@ _Returns_ - `?Object`: Record. +<a name="getEntityRecordEdits" href="#getEntityRecordEdits">#</a> **getEntityRecordEdits** + +Returns the specified entity record's edits. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record's edits. + +<a name="getEntityRecordNonTransientEdits" href="#getEntityRecordNonTransientEdits">#</a> **getEntityRecordNonTransientEdits** + +Returns the specified entity record's non transient edits. + +Transient edits don't create an undo level, and +are not considered for change detection. +They are defined in the entity's config. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record's non transient edits. + <a name="getEntityRecords" href="#getEntityRecords">#</a> **getEntityRecords** Returns the Entity's records. @@ -330,6 +427,34 @@ _Returns_ - `Array`: Records. +<a name="getLastEntitySaveError" href="#getLastEntitySaveError">#</a> **getLastEntitySaveError** + +Returns the specified entity record's last save error. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: The entity record's save error. + +<a name="getRedoEdit" href="#getRedoEdit">#</a> **getRedoEdit** + +Returns the next edit from the current undo offset +for the entity records edits history, if any. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `?Object`: The edit. + <a name="getThemeSupports" href="#getThemeSupports">#</a> **getThemeSupports** Return theme supports data in the index. @@ -342,6 +467,19 @@ _Returns_ - `*`: Index data. +<a name="getUndoEdit" href="#getUndoEdit">#</a> **getUndoEdit** + +Returns the previous edit from the current undo offset +for the entity records edits history, if any. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `?Object`: The edit. + <a name="getUserQueryResults" href="#getUserQueryResults">#</a> **getUserQueryResults** Returns all the users returned by a query ID. @@ -355,6 +493,22 @@ _Returns_ - `Array`: Users list. +<a name="hasEditsForEntityRecord" href="#hasEditsForEntityRecord">#</a> **hasEditsForEntityRecord** + +Returns true if the specified entity record has edits, +and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `boolean`: Whether the entity record has edits or not. + <a name="hasFetchedAutosaves" href="#hasFetchedAutosaves">#</a> **hasFetchedAutosaves** Returns true if the REST request for autosaves has completed. @@ -369,6 +523,32 @@ _Returns_ - `boolean`: True if the REST request was completed. False otherwise. +<a name="hasRedo" href="#hasRedo">#</a> **hasRedo** + +Returns true if there is a next edit from the current undo offset +for the entity records edits history, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `boolean`: Whether there is a next edit or not. + +<a name="hasUndo" href="#hasUndo">#</a> **hasUndo** + +Returns true if there is a previous edit from the current undo offset +for the entity records edits history, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. + +_Returns_ + +- `boolean`: Whether there is a previous edit or not. + <a name="hasUploadPermissions" href="#hasUploadPermissions">#</a> **hasUploadPermissions** > **Deprecated** since 5.0. Callers should use the more generic `canUser()` selector instead of `hasUploadPermissions()`, e.g. `canUser( 'create', 'media' )`. @@ -419,6 +599,21 @@ _Returns_ - `boolean`: Whether a request is in progress for an embed preview. +<a name="isSavingEntityRecord" href="#isSavingEntityRecord">#</a> **isSavingEntityRecord** + +Returns true if the specified entity record is saving, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `?Object`: Whether the entity record is saving or not. + <!-- END TOKEN(Autogenerated selectors) --> diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index f9c7940d09db53..8e78957bbb6756 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, find } from 'lodash'; +import { castArray, merge, isEqual, find } from 'lodash'; /** * Internal dependencies @@ -11,7 +11,7 @@ import { receiveQueriedItems, } from './queried-data'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; -import { apiFetch } from './controls'; +import { select, apiFetch } from './controls'; /** * Returns an action object used in signalling that authors have been received. @@ -115,14 +115,100 @@ export function receiveEmbedPreview( url, preview ) { }; } +/** + * Returns an action object that triggers an + * edit to an entity record. + * + * @param {string} kind Kind of the edited entity record. + * @param {string} name Name of the edited entity record. + * @param {number} recordId Record ID of the edited entity record. + * @param {Object} edits The edits. + * + * @return {Object} Action object. + */ +export function* editEntityRecord( kind, name, recordId, edits ) { + const { transientEdits = {}, mergedEdits = {} } = yield select( 'getEntity', kind, name ); + const record = yield select( 'getEntityRecord', kind, name, recordId ); + const editedRecord = yield select( + 'getEditedEntityRecord', + kind, + name, + recordId + ); + + const edit = { + kind, + name, + recordId, + // Clear edits when they are equal to their persisted counterparts + // so that the property is not considered dirty. + edits: Object.keys( edits ).reduce( ( acc, key ) => { + const value = mergedEdits[ key ] ? + merge( record[ key ], edits[ key ] ) : + edits[ key ]; + acc[ key ] = isEqual( record[ key ], value ) ? undefined : value; + return acc; + }, {} ), + transientEdits, + }; + return { + type: 'EDIT_ENTITY_RECORD', + ...edit, + meta: { + undo: { + ...edit, + // Send the current values for things like the first undo stack entry. + edits: Object.keys( edits ).reduce( ( acc, key ) => { + acc[ key ] = editedRecord[ key ]; + return acc; + }, {} ), + }, + }, + }; +} + +/** + * Action triggered to undo the last edit to + * an entity record, if any. + */ +export function* undo() { + const undoEdit = yield select( 'getUndoEdit' ); + if ( ! undoEdit ) { + return; + } + yield { + type: 'EDIT_ENTITY_RECORD', + ...undoEdit, + meta: { + isUndo: true, + }, + }; +} + +/** + * Action triggered to redo the last undoed + * edit to an entity record, if any. + */ +export function* redo() { + const redoEdit = yield select( 'getRedoEdit' ); + if ( ! redoEdit ) { + return; + } + yield { + type: 'EDIT_ENTITY_RECORD', + ...redoEdit, + meta: { + isRedo: true, + }, + }; +} + /** * Action triggered to save an entity record. * * @param {string} kind Kind of the received entity. * @param {string} name Name of the received entity. * @param {Object} record Record to be saved. - * - * @return {Object} Updated record. */ export function* saveEntityRecord( kind, name, record ) { const entities = yield getKindEntities( kind ); @@ -132,14 +218,41 @@ export function* saveEntityRecord( kind, name, record ) { } const key = entity.key || DEFAULT_ENTITY_KEY; const recordId = record[ key ]; - const updatedRecord = yield apiFetch( { - path: `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`, - method: recordId ? 'PUT' : 'POST', - data: record, - } ); - yield receiveEntityRecords( kind, name, updatedRecord, undefined, true ); - - return updatedRecord; + + yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId }; + let error; + try { + const updatedRecord = yield apiFetch( { + path: `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`, + method: recordId ? 'PUT' : 'POST', + data: record, + } ); + yield receiveEntityRecords( kind, name, updatedRecord, undefined, true ); + } catch ( _error ) { + error = _error; + } + yield { type: 'SAVE_ENTITY_RECORD_FINISH', kind, name, recordId, error }; +} + +/** + * Action triggered to save an entity record's edits. + * + * @param {string} kind Kind of the entity. + * @param {string} name Name of the entity. + * @param {Object} recordId ID of the record. + */ +export function* saveEditedEntityRecord( kind, name, recordId ) { + if ( ! ( yield select( 'hasEditsForEntityRecord', kind, name, recordId ) ) ) { + return; + } + const edits = yield select( + 'getEntityRecordNonTransientEdits', + kind, + name, + recordId + ); + const record = { id: recordId, ...edits }; + yield* saveEntityRecord( kind, name, record ); } /** diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 280e54a5297f3b..a78142b3360f9a 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -35,6 +35,8 @@ function* loadPostTypeEntities() { kind: 'postType', baseURL: '/wp/v2/' + postType.rest_base, name, + transientEdits: { blocks: true }, + mergedEdits: { meta: true }, }; } ); } diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 8e78544dcdbe2f..0c3256e9a8f987 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { keyBy, map, flowRight } from 'lodash'; +import { map, flowRight } from 'lodash'; /** * WordPress dependencies @@ -12,6 +12,7 @@ import { combineReducers } from '@wordpress/data'; * Internal dependencies */ import { + conservativeMapItem, ifMatchingAction, replaceAction, onSubKey, @@ -70,9 +71,14 @@ export function getMergedItemIds( itemIds, nextItemIds, page, perPage ) { function items( state = {}, action ) { switch ( action.type ) { case 'RECEIVE_ITEMS': + const key = action.key || DEFAULT_ENTITY_KEY; return { ...state, - ...keyBy( action.items, action.key || DEFAULT_ENTITY_KEY ), + ...action.items.reduce( ( acc, value ) => { + const itemId = value[ key ]; + acc[ itemId ] = conservativeMapItem( state[ itemId ], value ); + return acc; + }, {} ), }; } diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 272ab20e2693ec..96ed12e5ef78c7 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { keyBy, map, groupBy, flowRight } from 'lodash'; +import { keyBy, map, groupBy, flowRight, isEqual, get } from 'lodash'; /** * WordPress dependencies @@ -121,7 +121,9 @@ export function themeSupports( state = {}, action ) { /** * Higher Order Reducer for a given entity config. It supports: * - * - Fetching a record by primary key + * - Fetching + * - Editing + * - Saving * * @param {Object} entityConfig Entity config. * @@ -145,7 +147,80 @@ function entity( entityConfig ) { key: entityConfig.key || DEFAULT_ENTITY_KEY, }; } ), - ] )( queriedDataReducer ); + ] )( + combineReducers( { + queriedData: queriedDataReducer, + + edits: ( state = {}, action ) => { + switch ( action.type ) { + case 'RECEIVE_ITEMS': + const nextState = { ...state }; + + for ( const record of action.items ) { + const recordId = record[ action.key ]; + const edits = nextState[ recordId ]; + if ( ! edits ) { + continue; + } + + const nextEdits = Object.keys( edits ).reduce( ( acc, key ) => { + // If the edited value is still different to the persisted value, + // keep the edited value in edits. + if ( + ! isEqual( edits[ key ], get( record[ key ], 'raw', record[ key ] ) ) + ) { + acc[ key ] = edits[ key ]; + } + return acc; + }, {} ); + + if ( Object.keys( nextEdits ).length ) { + nextState[ recordId ] = nextEdits; + } else { + delete nextState[ recordId ]; + } + } + + return nextState; + + case 'EDIT_ENTITY_RECORD': + const nextEdits = { + ...state[ action.recordId ], + ...action.edits, + }; + Object.keys( nextEdits ).forEach( ( key ) => { + // Delete cleared edits so that the properties + // are not considered dirty. + if ( nextEdits[ key ] === undefined ) { + delete nextEdits[ key ]; + } + } ); + return { + ...state, + [ action.recordId ]: nextEdits, + }; + } + + return state; + }, + + saving: ( state = {}, action ) => { + switch ( action.type ) { + case 'SAVE_ENTITY_RECORD_START': + case 'SAVE_ENTITY_RECORD_FINISH': + return { + ...state, + [ action.recordId ]: { + pending: action.type === 'SAVE_ENTITY_RECORD_START', + error: action.error, + }, + }; + } + + return state; + }, + } ) + ); } /** @@ -214,6 +289,66 @@ export const entities = ( state = {}, action ) => { }; }; +/** + * Reducer keeping track of entity edit undo history. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +const UNDO_INITIAL_STATE = []; +UNDO_INITIAL_STATE.offset = 0; +export function undo( state = UNDO_INITIAL_STATE, action ) { + switch ( action.type ) { + case 'EDIT_ENTITY_RECORD': + if ( action.meta.isUndo || action.meta.isRedo ) { + const nextState = [ ...state ]; + nextState.offset = state.offset + ( action.meta.isUndo ? -1 : 1 ); + return nextState; + } + + // Transient edits don't create an undo level, but are + // reachable in the next meaningful edit to which they + // are merged. They are defined in the entity's config. + if ( ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) { + const nextState = [ ...state ]; + nextState.flattenedUndo = { ...state.flattenedUndo, ...action.edits }; + nextState.offset = state.offset; + return nextState; + } + + let nextState; + if ( state.length === 0 ) { + // Create an initial entry so that we can undo to it. + nextState = [ + { + kind: action.meta.undo.kind, + name: action.meta.undo.name, + recordId: action.meta.undo.recordId, + edits: { ...state.flattenedUndo, ...action.meta.undo.edits }, + }, + ]; + } else { + // Clear potential redos, because this only supports linear history. + nextState = state.slice( 0, state.offset || undefined ); + nextState.flattenedUndo = state.flattenedUndo; + } + nextState.offset = 0; + + nextState.push( { + kind: action.kind, + name: action.name, + recordId: action.recordId, + edits: { ...nextState.flattenedUndo, ...action.edits }, + } ); + + return nextState; + } + + return state; +} + /** * Reducer managing embed preview data. * @@ -284,6 +419,7 @@ export default combineReducers( { taxonomies, themeSupports, entities, + undo, embedPreviews, userPermissions, autosaves, diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 42e4a1f23d982d..e0ac2aa856ae1c 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -104,7 +104,7 @@ export function getEntity( state, kind, name ) { * @return {Object?} Record. */ export function getEntityRecord( state, kind, name, key ) { - return get( state.entities.data, [ kind, name, 'items', key ] ); + return get( state.entities.data, [ kind, name, 'queriedData', 'items', key ] ); } /** @@ -118,13 +118,190 @@ export function getEntityRecord( state, kind, name, key ) { * @return {Array} Records. */ export function getEntityRecords( state, kind, name, query ) { - const queriedState = get( state.entities.data, [ kind, name ] ); + const queriedState = get( state.entities.data, [ kind, name, 'queriedData' ] ); if ( ! queriedState ) { return []; } return getQueriedItems( queriedState, query ); } +/** + * Returns the specified entity record's edits. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {Object?} The entity record's edits. + */ +export function getEntityRecordEdits( state, kind, name, recordId ) { + return get( state.entities.data, [ kind, name, 'edits', recordId ] ); +} + +/** + * Returns the specified entity record's non transient edits. + * + * Transient edits don't create an undo level, and + * are not considered for change detection. + * They are defined in the entity's config. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {Object?} The entity record's non transient edits. + */ +export const getEntityRecordNonTransientEdits = createSelector( + ( state, kind, name, recordId ) => { + const { transientEdits = {} } = getEntity( state, kind, name ); + const edits = + getEntityRecordEdits( state, kind, name, recordId ) || []; + return Object.keys( edits ).reduce( ( acc, key ) => { + if ( ! transientEdits[ key ] ) { + acc[ key ] = edits[ key ]; + } + return acc; + }, {} ); + }, + ( state ) => [ state.entities.config, state.entities.data ] +); + +/** + * Returns true if the specified entity record has edits, + * and false otherwise. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {boolean} Whether the entity record has edits or not. + */ +export function hasEditsForEntityRecord( state, kind, name, recordId ) { + return Object.keys( getEntityRecordNonTransientEdits( state, kind, name, recordId ) ).length > 0; +} + +/** + * Returns the specified entity record, merged with its edits. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {Object?} The entity record, merged with its edits. + */ +export const getEditedEntityRecord = createSelector( + ( state, kind, name, recordId ) => { + const record = getEntityRecord( state, kind, name, recordId ); + return { + ...Object.keys( record ).reduce( ( acc, key ) => { + acc[ key ] = get( record[ key ], 'raw', record[ key ] ); + return acc; + }, {} ), + ...getEntityRecordEdits( state, kind, name, recordId ), + }; + }, + ( state ) => [ state.entities.data ] +); + +/** + * Returns true if the specified entity record is saving, and false otherwise. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {Object?} Whether the entity record is saving or not. + */ +export function isSavingEntityRecord( state, kind, name, recordId ) { + return get( + state.entities.data, + [ kind, name, 'saving', recordId, 'pending' ], + false + ); +} + +/** + * Returns the specified entity record's last save error. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {Object?} The entity record's save error. + */ +export function getLastEntitySaveError( state, kind, name, recordId ) { + return get( state.entities.data, [ kind, name, 'saving', recordId, 'error' ] ); +} + +/** + * Returns the current undo offset for the + * entity records edits history. The offset + * represents how many items from the end + * of the history stack we are at. 0 is the + * last edit, -1 is the second last, and so on. + * + * @param {Object} state State tree. + * + * @return {number} The current undo offset. + */ +export function getCurrentUndoOffset( state ) { + return state.undo.offset; +} + +/** + * Returns the previous edit from the current undo offset + * for the entity records edits history, if any. + * + * @param {Object} state State tree. + * + * @return {Object?} The edit. + */ +export function getUndoEdit( state ) { + return state.undo[ state.undo.length - 2 + getCurrentUndoOffset( state ) ]; +} + +/** + * Returns the next edit from the current undo offset + * for the entity records edits history, if any. + * + * @param {Object} state State tree. + * + * @return {Object?} The edit. + */ +export function getRedoEdit( state ) { + return state.undo[ state.undo.length + getCurrentUndoOffset( state ) ]; +} + +/** + * Returns true if there is a previous edit from the current undo offset + * for the entity records edits history, and false otherwise. + * + * @param {Object} state State tree. + * + * @return {boolean} Whether there is a previous edit or not. + */ +export function hasUndo( state ) { + return Boolean( getUndoEdit( state ) ); +} + +/** + * Returns true if there is a next edit from the current undo offset + * for the entity records edits history, and false otherwise. + * + * @param {Object} state State tree. + * + * @return {boolean} Whether there is a next edit or not. + */ +export function hasRedo( state ) { + return Boolean( getRedoEdit( state ) ); +} + /** * Return theme supports data in the index. * diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 7ef1abd0dc43c7..16ac5776be1976 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -11,7 +11,10 @@ describe( 'saveEntityRecord', () => { // Trigger generator fulfillment.next(); // Provide entities and trigger apiFetch - const { value: apiFetchAction } = fulfillment.next( entities ); + expect( fulfillment.next( entities ).value.type ).toBe( + 'SAVE_ENTITY_RECORD_START' + ); + const { value: apiFetchAction } = fulfillment.next(); expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/posts', method: 'POST', @@ -20,6 +23,7 @@ describe( 'saveEntityRecord', () => { // Provide response and trigger action const { value: received } = fulfillment.next( { ...post, id: 10 } ); expect( received ).toEqual( receiveEntityRecords( 'postType', 'post', { ...post, id: 10 }, undefined, true ) ); + expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); } ); it( 'triggers a PUT request for an existing record', async () => { @@ -29,7 +33,10 @@ describe( 'saveEntityRecord', () => { // Trigger generator fulfillment.next(); // Provide entities and trigger apiFetch - const { value: apiFetchAction } = fulfillment.next( entities ); + expect( fulfillment.next( entities ).value.type ).toBe( + 'SAVE_ENTITY_RECORD_START' + ); + const { value: apiFetchAction } = fulfillment.next(); expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/posts/10', method: 'PUT', @@ -38,6 +45,7 @@ describe( 'saveEntityRecord', () => { // Provide response and trigger action const { value: received } = fulfillment.next( post ); expect( received ).toEqual( receiveEntityRecords( 'postType', 'post', post, undefined, true ) ); + expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); } ); it( 'triggers a PUT request for an existing record with a custom key', async () => { @@ -47,7 +55,10 @@ describe( 'saveEntityRecord', () => { // Trigger generator fulfillment.next(); // Provide entities and trigger apiFetch - const { value: apiFetchAction } = fulfillment.next( entities ); + expect( fulfillment.next( entities ).value.type ).toBe( + 'SAVE_ENTITY_RECORD_START' + ); + const { value: apiFetchAction } = fulfillment.next(); expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/types/page', method: 'PUT', @@ -56,6 +67,7 @@ describe( 'saveEntityRecord', () => { // Provide response and trigger action const { value: received } = fulfillment.next( postType ); expect( received ).toEqual( receiveEntityRecords( 'root', 'postType', postType, undefined, true ) ); + expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); } ); } ); diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index ec8349d0a65d36..eea3054bc64684 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -34,7 +34,7 @@ describe( 'entities', () => { it( 'returns the default state for all defined entities', () => { const state = entities( undefined, {} ); - expect( state.data.root.postType ).toEqual( { items: {}, queries: {} } ); + expect( state.data.root.postType.queriedData ).toEqual( { items: {}, queries: {} } ); } ); it( 'returns with received post types by slug', () => { @@ -46,7 +46,7 @@ describe( 'entities', () => { name: 'postType', } ); - expect( state.data.root.postType ).toEqual( { + expect( state.data.root.postType.queriedData ).toEqual( { items: { b: { slug: 'b', title: 'beach' }, s: { slug: 's', title: 'sun' }, @@ -60,10 +60,12 @@ describe( 'entities', () => { data: { root: { postType: { - items: { - w: { slug: 'w', title: 'water' }, + queriedData: { + items: { + w: { slug: 'w', title: 'water' }, + }, + queries: {}, }, - queries: {}, }, }, }, @@ -75,7 +77,7 @@ describe( 'entities', () => { name: 'postType', } ); - expect( state.data.root.postType ).toEqual( { + expect( state.data.root.postType.queriedData ).toEqual( { items: { w: { slug: 'w', title: 'water' }, b: { slug: 'b', title: 'beach' }, diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index 1335b744e90528..eb488c040e8fb9 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -24,8 +24,10 @@ describe( 'getEntityRecord', () => { data: { root: { postType: { - items: {}, - queries: {}, + queriedData: { + items: {}, + queries: {}, + }, }, }, }, @@ -40,10 +42,12 @@ describe( 'getEntityRecord', () => { data: { root: { postType: { - items: { - post: { slug: 'post' }, + queriedData: { + items: { + post: { slug: 'post' }, + }, + queries: {}, }, - queries: {}, }, }, }, @@ -60,8 +64,10 @@ describe( 'getEntityRecords', () => { data: { root: { postType: { - items: {}, - queries: {}, + queriedData: { + items: {}, + queries: {}, + }, }, }, }, @@ -76,12 +82,14 @@ describe( 'getEntityRecords', () => { data: { root: { postType: { - items: { - post: { slug: 'post' }, - page: { slug: 'page' }, - }, - queries: { - '': [ 'post', 'page' ], + queriedData: { + items: { + post: { slug: 'post' }, + page: { slug: 'page' }, + }, + queries: { + '': [ 'post', 'page' ], + }, }, }, }, From 9cebb98ca930e2ffc47f43df23d28e0c74893731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 5 Aug 2019 15:52:00 +0200 Subject: [PATCH 610/664] Accessibility: Fix the issue when focus is moved to the post title from More Menu (#16874) --- packages/editor/src/components/post-title/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index e992e25bae11a9..d5f81b5d143c8f 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -136,7 +136,7 @@ class PostTitle extends Component { right away, without needing to click anything. */ /* eslint-disable jsx-a11y/no-autofocus */ - autoFocus={ isCleanNewPost } + autoFocus={ document.body === document.activeElement && isCleanNewPost } /* eslint-enable jsx-a11y/no-autofocus */ /> </KeyboardShortcuts> From e0d2f4e4ebf52af425dd4147f6fa66865b86c858 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 5 Aug 2019 14:57:17 +0100 Subject: [PATCH 611/664] Fix: Insert Before/After appear even when default block is disabled (#15024) --- .../src/components/block-actions/index.js | 35 ++++++++++++------- .../components/block-settings-menu/index.js | 12 +++++-- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index ffb14ffca8c5eb..3e12269b074e33 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -11,48 +11,57 @@ import { withSelect, withDispatch } from '@wordpress/data'; import { cloneBlock, hasBlockSupport, switchToBlockType } from '@wordpress/blocks'; function BlockActions( { + canDuplicate, + canInsertDefaultBlock, + children, + isLocked, onDuplicate, - onRemove, - onInsertBefore, - onInsertAfter, onGroup, + onInsertAfter, + onInsertBefore, + onRemove, onUngroup, - isLocked, - canDuplicate, - children, } ) { return children( { + canDuplicate, + canInsertDefaultBlock, + isLocked, onDuplicate, - onRemove, + onGroup, onInsertAfter, onInsertBefore, - onGroup, + onRemove, onUngroup, - isLocked, - canDuplicate, } ); } export default compose( [ withSelect( ( select, props ) => { const { + canInsertBlockType, + getBlockRootClientId, getBlocksByClientId, getTemplateLock, - getBlockRootClientId, } = select( 'core/block-editor' ); + const { getDefaultBlockName } = select( 'core/blocks' ); const blocks = getBlocksByClientId( props.clientIds ); const canDuplicate = every( blocks, ( block ) => { return !! block && hasBlockSupport( block.name, 'multiple', true ); } ); const rootClientId = getBlockRootClientId( props.clientIds[ 0 ] ); + const canInsertDefaultBlock = canInsertBlockType( + getDefaultBlockName(), + rootClientId + ); return { - isLocked: !! getTemplateLock( rootClientId ), blocks, canDuplicate, - rootClientId, + canInsertDefaultBlock, extraProps: props, + isLocked: !! getTemplateLock( rootClientId ), + rootClientId, }; } ), withDispatch( ( dispatch, props, { select } ) => { diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 005be38e380b43..527f84ab0ca984 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -32,7 +32,15 @@ export function BlockSettingsMenu( { clientIds } ) { return ( <BlockActions clientIds={ clientIds }> - { ( { onDuplicate, onRemove, onInsertAfter, onInsertBefore, canDuplicate, isLocked } ) => ( + { ( { + canDuplicate, + canInsertDefaultBlock, + isLocked, + onDuplicate, + onInsertAfter, + onInsertBefore, + onRemove, + } ) => ( <Toolbar> <DropdownMenu icon="ellipsis" @@ -69,7 +77,7 @@ export function BlockSettingsMenu( { clientIds } ) { { __( 'Duplicate' ) } </MenuItem> ) } - { ! isLocked && ( + { canInsertDefaultBlock && ( <> <MenuItem className="editor-block-settings-menu__control block-editor-block-settings-menu__control" From 54323e94b843ad28e720ac1a095c8a981d287908 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Mon, 5 Aug 2019 20:23:12 +0200 Subject: [PATCH 612/664] [RNMobile] Refactor BlockToolbar follow-up PR (#16906) * Keyboard is already hidden with clearSelectedBlock * Hide Keyboard when switching mode * require clearSelectedBlock from core/block-editor instead of core/editor * require clearSelectedBlock from core/block-editor instead of core/editor --- .../header/header-toolbar/index.native.js | 14 +++----------- .../editor/src/components/provider/index.native.js | 6 ++++++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/edit-post/src/components/header/header-toolbar/index.native.js b/packages/edit-post/src/components/header/header-toolbar/index.native.js index 9c71845889d371..487dcea882dab1 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.native.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { ScrollView, Keyboard, Platform, View } from 'react-native'; +import { ScrollView, View } from 'react-native'; /** * WordPress dependencies @@ -31,14 +31,6 @@ function HeaderToolbar( { showKeyboardHideButton, clearSelectedBlock, } ) { - const hideKeyboard = () => { - clearSelectedBlock(); - if ( Platform.OS === 'android' ) { - // Avoiding extra blur calls on iOS but still needed for android. - Keyboard.dismiss(); - } - }; - return ( <View style={ styles.container }> <ScrollView @@ -75,7 +67,7 @@ function HeaderToolbar( { <ToolbarButton title={ __( 'Hide keyboard' ) } icon="keyboard-hide" - onClick={ hideKeyboard } + onClick={ clearSelectedBlock } extraProps={ { hint: __( 'Tap to hide the keyboard' ) } } /> </Toolbar> @@ -96,7 +88,7 @@ export default compose( [ withDispatch( ( dispatch ) => ( { redo: dispatch( 'core/editor' ).redo, undo: dispatch( 'core/editor' ).undo, - clearSelectedBlock: dispatch( 'core/editor' ).clearSelectedBlock, + clearSelectedBlock: dispatch( 'core/block-editor' ).clearSelectedBlock, } ) ), withViewportMatch( { isLargeViewport: 'medium' } ), ] )( HeaderToolbar ); diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index d2b1458d3e2e56..d19da119ef0ba5 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -118,6 +118,8 @@ class NativeEditorProvider extends Component { const { mode, switchMode } = this.props; // refresh html content first this.serializeToNativeAction(); + // make sure to blur the selected block and dismiss the keyboard + this.props.clearSelectedBlock(); switchMode( mode === 'visual' ? 'text' : 'visual' ); } @@ -161,11 +163,15 @@ export default compose( [ editPost, resetEditorBlocks, } = dispatch( 'core/editor' ); + const { + clearSelectedBlock, + } = dispatch( 'core/block-editor' ); const { switchEditorMode, } = dispatch( 'core/edit-post' ); return { + clearSelectedBlock, editTitle( title ) { editPost( { title } ); }, From e83007111f49fb9d1c8e3c373085aea870794984 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 5 Aug 2019 19:38:52 +0100 Subject: [PATCH 613/664] Fix: Block manager doesn't respect allowed_block_types hook (#16586) --- .../specs/plugins/allowed-blocks.test.js | 25 ++++++++++++ .../components/edit-post-settings/index.js | 7 ++++ .../manage-blocks-modal/category.js | 26 ++++++++++--- packages/edit-post/src/editor.js | 39 ++++++++++--------- 4 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 packages/edit-post/src/components/edit-post-settings/index.js diff --git a/packages/e2e-tests/specs/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/plugins/allowed-blocks.test.js index f7bdffcf3a703a..11d5bfc8303fce 100644 --- a/packages/e2e-tests/specs/plugins/allowed-blocks.test.js +++ b/packages/e2e-tests/specs/plugins/allowed-blocks.test.js @@ -38,4 +38,29 @@ describe( 'Allowed Blocks Filter', () => { ) )[ 0 ]; expect( galleryBlockButton ).toBeUndefined(); } ); + + it( 'should remove not allowed blocks from the block manager', async () => { + const BLOCK_LABEL_SELECTOR = '.edit-post-manage-blocks-modal__checklist-item .components-checkbox-control__label'; + await page.click( + '.edit-post-more-menu [aria-label="More tools & options"]' + ); + const [ button ] = await page.$x( + `//button[contains(text(), 'Block Manager')]` + ); + await button.click( 'button' ); + + await page.waitForSelector( BLOCK_LABEL_SELECTOR ); + const blocks = await page.evaluate( + ( selector ) => { + return Array.from( document.querySelectorAll( selector ) ).map( + ( element ) => ( ( element.innerText || '' ).trim() ) + ).sort(); + }, + BLOCK_LABEL_SELECTOR + ); + expect( blocks ).toEqual( [ + 'Image', + 'Paragraph', + ] ); + } ); } ); diff --git a/packages/edit-post/src/components/edit-post-settings/index.js b/packages/edit-post/src/components/edit-post-settings/index.js new file mode 100644 index 00000000000000..db6c9f0454edfe --- /dev/null +++ b/packages/edit-post/src/components/edit-post-settings/index.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +const EditPostSettings = createContext( {} ); +export default EditPostSettings; diff --git a/packages/edit-post/src/components/manage-blocks-modal/category.js b/packages/edit-post/src/components/manage-blocks-modal/category.js index 00a2ff15ab7937..c4beeebb642ea8 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/category.js +++ b/packages/edit-post/src/components/manage-blocks-modal/category.js @@ -1,11 +1,12 @@ /** * External dependencies */ -import { without, map } from 'lodash'; +import { includes, map, without } from 'lodash'; /** * WordPress dependencies */ +import { useContext, useMemo } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose, withInstanceId } from '@wordpress/compose'; import { CheckboxControl } from '@wordpress/components'; @@ -14,6 +15,7 @@ import { CheckboxControl } from '@wordpress/components'; * Internal dependencies */ import BlockTypesChecklist from './checklist'; +import EditPostSettings from '../edit-post-settings'; function BlockManagerCategory( { instanceId, @@ -23,18 +25,32 @@ function BlockManagerCategory( { toggleVisible, toggleAllVisible, } ) { - if ( ! blockTypes.length ) { + const settings = useContext( EditPostSettings ); + const { allowedBlockTypes } = settings; + const filteredBlockTypes = useMemo( + () => { + if ( allowedBlockTypes === true ) { + return blockTypes; + } + return blockTypes.filter( ( { name } ) => { + return includes( allowedBlockTypes || [], name ); + } ); + }, + [ allowedBlockTypes, blockTypes ] + ); + + if ( ! filteredBlockTypes.length ) { return null; } const checkedBlockNames = without( - map( blockTypes, 'name' ), + map( filteredBlockTypes, 'name' ), ...hiddenBlockTypes ); const titleId = 'edit-post-manage-blocks-modal__category-title-' + instanceId; - const isAllChecked = checkedBlockNames.length === blockTypes.length; + const isAllChecked = checkedBlockNames.length === filteredBlockTypes.length; let ariaChecked; if ( isAllChecked ) { @@ -59,7 +75,7 @@ function BlockManagerCategory( { label={ <span id={ titleId }>{ category.title }</span> } /> <BlockTypesChecklist - blockTypes={ blockTypes } + blockTypes={ filteredBlockTypes } value={ checkedBlockNames } onItemChange={ toggleVisible } /> diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 92fad75ebef7e8..70a29b54466a1d 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -22,6 +22,7 @@ import { import preventEventDiscovery from './prevent-event-discovery'; import Layout from './components/layout'; import EditorInitialization from './components/editor-initialization'; +import EditPostSettings from './components/edit-post-settings'; class Editor extends Component { constructor() { @@ -93,24 +94,26 @@ class Editor extends Component { return ( <StrictMode> - <SlotFillProvider> - <DropZoneProvider> - <EditorProvider - settings={ editorSettings } - post={ post } - initialEdits={ initialEdits } - useSubRegistry={ false } - { ...props } - > - <ErrorBoundary onError={ onError }> - <EditorInitialization postId={ postId } /> - <Layout /> - <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> - </ErrorBoundary> - <PostLockedModal /> - </EditorProvider> - </DropZoneProvider> - </SlotFillProvider> + <EditPostSettings.Provider value={ settings }> + <SlotFillProvider> + <DropZoneProvider> + <EditorProvider + settings={ editorSettings } + post={ post } + initialEdits={ initialEdits } + useSubRegistry={ false } + { ...props } + > + <ErrorBoundary onError={ onError }> + <EditorInitialization postId={ postId } /> + <Layout /> + <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> + </ErrorBoundary> + <PostLockedModal /> + </EditorProvider> + </DropZoneProvider> + </SlotFillProvider> + </EditPostSettings.Provider> </StrictMode> ); } From 1fcc64653dd282eff561e8dd58ff0b892a04ca18 Mon Sep 17 00:00:00 2001 From: Jon Surrell <jon.surrell@automattic.com> Date: Mon, 5 Aug 2019 21:46:10 +0200 Subject: [PATCH 614/664] Fix changelog date (#16908) --- packages/babel-plugin-import-jsx-pragma/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md index 8be173174858b5..3bb5792f55c780 100644 --- a/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md +++ b/packages/babel-plugin-import-jsx-pragma/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.4.0 (2019-08-05) +## 2.2.0 (2019-05-21) ### New Feature From 71db90897754cabde568edd025d8b9a090822d00 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 6 Aug 2019 00:04:15 +0100 Subject: [PATCH 615/664] Fix: Widgets screen block toolbar overlaps the Block accordion (#16765) --- packages/block-editor/src/components/block-list/index.js | 9 ++++++++- .../edit-widgets/src/components/widget-area/index.js | 4 +++- .../edit-widgets/src/components/widget-area/style.scss | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index e3f68637dbb91d..623840c3492ee3 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -8,6 +8,7 @@ import { sortBy, throttle, } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies @@ -191,6 +192,7 @@ class BlockList extends Component { render() { const { + className, blockClientIds, rootClientId, isDraggable, @@ -202,7 +204,12 @@ class BlockList extends Component { } = this.props; return ( - <div className="editor-block-list__layout block-editor-block-list__layout"> + <div className={ + classnames( + 'editor-block-list__layout block-editor-block-list__layout', + className + ) + }> { blockClientIds.map( ( clientId ) => { const isBlockInSelection = hasMultiSelection ? multiSelectedBlockClientIds.includes( clientId ) : diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 3369f3ba1525b5..9ed9055f196e6b 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -84,7 +84,9 @@ function WidgetArea( { </Sidebar.Inspector> <WritingFlow> <ObserveTyping> - <BlockList /> + <BlockList + className="edit-widgets-main-block-list" + /> </ObserveTyping> </WritingFlow> </BlockEditorProvider> diff --git a/packages/edit-widgets/src/components/widget-area/style.scss b/packages/edit-widgets/src/components/widget-area/style.scss index d9307abab0a798..cdf6480887c632 100644 --- a/packages/edit-widgets/src/components/widget-area/style.scss +++ b/packages/edit-widgets/src/components/widget-area/style.scss @@ -2,3 +2,6 @@ max-width: $content-width; margin: 0 auto 30px; } +.edit-widgets-main-block-list { + padding-top: $block-toolbar-height + 2 * $grid-size; +} From c5d573a4e4b19ea8e8eebb2c48aa28e0537d55aa Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Mon, 5 Aug 2019 21:29:18 -0700 Subject: [PATCH 616/664] Custom Fields option: Add confirmation step (#15688) * Add confirmation step to Custom Fields option Add a confirmation step to the Custom Fields option so that the page doesn't reload without warning. Co-authored-by: Marek Hrabe <marekhrabe@me.com> * Update E2E tests for new Custom Fields setting flow * Remove notice in favor of plain text. * Custom Fields option: Adjust margin to align text * update description Co-Authored-By: Daniel Richards <daniel.richards@automattic.com> * simplify change handler * add dynamic button based on the next state to be saved * add notice about saved content and make sure it won't expand modal * update snapshot with new button label and confirm msg * make button label in test dynamic * simplify condition for determining label Co-Authored-By: Robert Anderson <robert@noisysocks.com> * use willEnable as the prop name --- packages/e2e-tests/specs/preview.test.js | 4 +- .../components/options-modal/options/base.js | 16 +- .../options/enable-custom-fields.js | 71 +++++---- .../enable-custom-fields.js.snap | 142 ++++++++++++++++-- .../options/test/enable-custom-fields.js | 73 ++++----- .../src/components/options-modal/style.scss | 16 +- 6 files changed, 230 insertions(+), 92 deletions(-) diff --git a/packages/e2e-tests/specs/preview.test.js b/packages/e2e-tests/specs/preview.test.js index 255f6200d116ad..f7ad7d830bf452 100644 --- a/packages/e2e-tests/specs/preview.test.js +++ b/packages/e2e-tests/specs/preview.test.js @@ -71,8 +71,10 @@ async function toggleCustomFieldsOption( shouldBeChecked ) { ); if ( isChecked !== shouldBeChecked ) { - const navigationCompleted = page.waitForNavigation(); await checkboxHandle.click(); + const [ saveButton ] = await page.$x( shouldBeChecked ? '//button[text()="Enable & Reload"]' : '//button[text()="Disable & Reload"]' ); + const navigationCompleted = page.waitForNavigation(); + saveButton.click(); await navigationCompleted; return; } diff --git a/packages/edit-post/src/components/options-modal/options/base.js b/packages/edit-post/src/components/options-modal/options/base.js index 1dd5b9d013af0f..cef8fe130b268e 100644 --- a/packages/edit-post/src/components/options-modal/options/base.js +++ b/packages/edit-post/src/components/options-modal/options/base.js @@ -3,14 +3,16 @@ */ import { CheckboxControl } from '@wordpress/components'; -function BaseOption( { label, isChecked, onChange } ) { +function BaseOption( { label, isChecked, onChange, children } ) { return ( - <CheckboxControl - className="edit-post-options-modal__option" - label={ label } - checked={ isChecked } - onChange={ onChange } - /> + <div className="edit-post-options-modal__option"> + <CheckboxControl + label={ label } + checked={ isChecked } + onChange={ onChange } + /> + { children } + </div> ); } diff --git a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js index 140c6f1c46d2d8..82b8002dca1484 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js +++ b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js @@ -1,7 +1,9 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { Button } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; /** @@ -9,39 +11,44 @@ import { withSelect } from '@wordpress/data'; */ import BaseOption from './base'; -export class EnableCustomFieldsOption extends Component { - constructor( { isChecked } ) { - super( ...arguments ); - - this.toggleCustomFields = this.toggleCustomFields.bind( this ); - - this.state = { isChecked }; - } - - toggleCustomFields() { - // Submit a hidden form which triggers the toggle_custom_fields admin action. - // This action will toggle the setting and reload the editor with the meta box - // assets included on the page. - document.getElementById( 'toggle-custom-fields-form' ).submit(); - - // Make it look like something happened while the page reloads. - this.setState( { isChecked: ! this.props.isChecked } ); - } - - render() { - const { label } = this.props; - const { isChecked } = this.state; +export function CustomFieldsConfirmation( { willEnable } ) { + const [ isReloading, setIsReloading ] = useState( false ); + + return ( + <> + <p className="edit-post-options-modal__custom-fields-confirmation-message"> + { __( 'A page reload is required for this change. Make sure your content is saved before reloading.' ) } + </p> + <Button + className="edit-post-options-modal__custom-fields-confirmation-button" + isDefault + isBusy={ isReloading } + disabled={ isReloading } + onClick={ () => { + setIsReloading( true ); + document.getElementById( 'toggle-custom-fields-form' ).submit(); + } } + > + { willEnable ? __( 'Enable & Reload' ) : __( 'Disable & Reload' ) } + </Button> + </> + ); +} - return ( - <BaseOption - label={ label } - isChecked={ isChecked } - onChange={ this.toggleCustomFields } - /> - ); - } +export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) { + const [ isChecked, setIsChecked ] = useState( areCustomFieldsEnabled ); + + return ( + <BaseOption + label={ label } + isChecked={ isChecked } + onChange={ setIsChecked } + > + { isChecked !== areCustomFieldsEnabled && <CustomFieldsConfirmation willEnable={ isChecked } /> } + </BaseOption> + ); } export default withSelect( ( select ) => ( { - isChecked: !! select( 'core/editor' ).getEditorSettings().enableCustomFields, + areCustomFieldsEnabled: !! select( 'core/editor' ).getEditorSettings().enableCustomFields, } ) )( EnableCustomFieldsOption ); diff --git a/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap b/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap index 14bed9e08237e7..d4fa948a778449 100644 --- a/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap +++ b/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap @@ -1,17 +1,135 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EnableCustomFieldsOption renders properly when checked 1`] = ` -<BaseOption - isChecked={true} - label="Custom Fields" - onChange={[Function]} -/> +exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation message when toggled on 1`] = ` +<div + className="edit-post-options-modal__option" +> + <div + className="components-base-control" + > + <div + className="components-base-control__field" + > + <input + checked={true} + className="components-checkbox-control__input" + id="inspector-checkbox-control-3" + onChange={[Function]} + type="checkbox" + value="1" + /> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-3" + /> + </div> + </div> + <p + className="edit-post-options-modal__custom-fields-confirmation-message" + > + A page reload is required for this change. Make sure your content is saved before reloading. + </p> + <button + className="components-button edit-post-options-modal__custom-fields-confirmation-button is-button is-default" + disabled={false} + onClick={[Function]} + type="button" + > + Enable & Reload + </button> +</div> `; -exports[`EnableCustomFieldsOption renders properly when unchecked 1`] = ` -<BaseOption - isChecked={false} - label="Custom Fields" - onChange={[Function]} -/> +exports[`EnableCustomFieldsOption renders a checked checkbox when custom fields are enabled 1`] = ` +<div + className="edit-post-options-modal__option" +> + <div + className="components-base-control" + > + <div + className="components-base-control__field" + > + <input + checked={true} + className="components-checkbox-control__input" + id="inspector-checkbox-control-0" + onChange={[Function]} + type="checkbox" + value="1" + /> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-0" + /> + </div> + </div> +</div> +`; + +exports[`EnableCustomFieldsOption renders an unchecked checkbox and a confirmation message when toggled off 1`] = ` +<div + className="edit-post-options-modal__option" +> + <div + className="components-base-control" + > + <div + className="components-base-control__field" + > + <input + checked={false} + className="components-checkbox-control__input" + id="inspector-checkbox-control-2" + onChange={[Function]} + type="checkbox" + value="1" + /> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-2" + /> + </div> + </div> + <p + className="edit-post-options-modal__custom-fields-confirmation-message" + > + A page reload is required for this change. Make sure your content is saved before reloading. + </p> + <button + className="components-button edit-post-options-modal__custom-fields-confirmation-button is-button is-default" + disabled={false} + onClick={[Function]} + type="button" + > + Disable & Reload + </button> +</div> +`; + +exports[`EnableCustomFieldsOption renders an unchecked checkbox when custom fields are disabled 1`] = ` +<div + className="edit-post-options-modal__option" +> + <div + className="components-base-control" + > + <div + className="components-base-control__field" + > + <input + checked={false} + className="components-checkbox-control__input" + id="inspector-checkbox-control-1" + onChange={[Function]} + type="checkbox" + value="1" + /> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-1" + /> + </div> + </div> +</div> `; diff --git a/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js b/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js index 8ce8f78eb4be63..5ac60c92753af3 100644 --- a/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js +++ b/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js @@ -1,62 +1,63 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { default as TestRenderer, act } from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; /** * Internal dependencies */ -import { EnableCustomFieldsOption } from '../enable-custom-fields'; +import { EnableCustomFieldsOption, CustomFieldsConfirmation } from '../enable-custom-fields'; +import BaseOption from '../base'; describe( 'EnableCustomFieldsOption', () => { - it( 'renders properly when checked', () => { - const wrapper = shallow( <EnableCustomFieldsOption label="Custom Fields" isChecked /> ); - expect( wrapper ).toMatchSnapshot(); + it( 'renders a checked checkbox when custom fields are enabled', () => { + const renderer = TestRenderer.create( <EnableCustomFieldsOption areCustomFieldsEnabled /> ); + expect( renderer ).toMatchSnapshot(); } ); - it( 'can be unchecked', () => { - const submit = jest.fn(); - const getElementById = jest.spyOn( document, 'getElementById' ).mockImplementation( () => ( { - submit, - } ) ); - - const wrapper = shallow( <EnableCustomFieldsOption label="Custom Fields" isChecked /> ); - - expect( wrapper.prop( 'isChecked' ) ).toBe( true ); - - wrapper.prop( 'onChange' )(); - wrapper.update(); - - expect( wrapper.prop( 'isChecked' ) ).toBe( false ); - expect( getElementById ).toHaveBeenCalledWith( 'toggle-custom-fields-form' ); - expect( submit ).toHaveBeenCalled(); + it( 'renders an unchecked checkbox when custom fields are disabled', () => { + const renderer = TestRenderer.create( + <EnableCustomFieldsOption areCustomFieldsEnabled={ false } /> + ); + expect( renderer ).toMatchSnapshot(); + } ); - getElementById.mockRestore(); + it( 'renders an unchecked checkbox and a confirmation message when toggled off', () => { + const renderer = new TestRenderer.create( <EnableCustomFieldsOption areCustomFieldsEnabled /> ); + act( () => { + renderer.root.findByType( BaseOption ).props.onChange( false ); + } ); + expect( renderer ).toMatchSnapshot(); } ); - it( 'renders properly when unchecked', () => { - const wrapper = shallow( - <EnableCustomFieldsOption label="Custom Fields" isChecked={ false } /> + it( 'renders a checked checkbox and a confirmation message when toggled on', () => { + const renderer = new TestRenderer.create( + <EnableCustomFieldsOption areCustomFieldsEnabled={ false } /> ); - expect( wrapper ).toMatchSnapshot(); + act( () => { + renderer.root.findByType( BaseOption ).props.onChange( true ); + } ); + expect( renderer ).toMatchSnapshot(); } ); +} ); - it( 'can be checked', () => { +describe( 'CustomFieldsConfirmation', () => { + it( 'submits the toggle-custom-fields-form', () => { const submit = jest.fn(); const getElementById = jest.spyOn( document, 'getElementById' ).mockImplementation( () => ( { submit, } ) ); - const wrapper = shallow( - <EnableCustomFieldsOption label="Custom Fields" isChecked={ false } /> - ); - - expect( wrapper.prop( 'isChecked' ) ).toBe( false ); - - wrapper.prop( 'onChange' )(); - wrapper.update(); + const renderer = new TestRenderer.create( <CustomFieldsConfirmation /> ); + act( () => { + renderer.root.findByType( Button ).props.onClick(); + } ); - expect( wrapper.prop( 'isChecked' ) ).toBe( true ); expect( getElementById ).toHaveBeenCalledWith( 'toggle-custom-fields-form' ); expect( submit ).toHaveBeenCalled(); diff --git a/packages/edit-post/src/components/options-modal/style.scss b/packages/edit-post/src/components/options-modal/style.scss index 01b5f816c0ca0e..8592234beb2446 100644 --- a/packages/edit-post/src/components/options-modal/style.scss +++ b/packages/edit-post/src/components/options-modal/style.scss @@ -21,13 +21,21 @@ margin: 0; } - &.components-base-control + &.components-base-control { - margin-bottom: 0; - } - .components-checkbox-control__label { flex-grow: 1; padding: 0.6rem 0 0.6rem 10px; } } + + &__custom-fields-confirmation-message, + &__custom-fields-confirmation-button { + margin: 0 0 0.6rem 48px; + + @include break-medium() { + margin-left: 38px; + } + @include break-small() { + max-width: 300px; + } + } } From eba92cb7fe8cadba03e6e32cf440d85675e5c0fc Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Mon, 5 Aug 2019 21:56:44 -0700 Subject: [PATCH 617/664] Table block: Removes the `word-break:break-all;` from the table cells (#16741) * Fixes #16740. Removes the word-break:break-all; from the CSS for the Table cells. * Added the overflow-wrap:break-word attribute which appears to be widely supported and works better than word-wrap:break-all. * Changed to word-break:break-word as per @brentswisher's wonderful catch. * Missed an overflow-wrap. Got it now. --- packages/block-library/src/table/editor.scss | 3 ++- packages/block-library/src/table/style.scss | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index 73df244f815ff8..f5dbb5f2643c04 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -9,9 +9,10 @@ // Ensure the table element is not full-width when aligned. width: auto; } + td, th { - word-break: break-all; + word-break: break-word; } } diff --git a/packages/block-library/src/table/style.scss b/packages/block-library/src/table/style.scss index 13e9f048059890..ba3c949bb7cb3d 100644 --- a/packages/block-library/src/table/style.scss +++ b/packages/block-library/src/table/style.scss @@ -17,7 +17,7 @@ td, th { - word-break: break-all; + word-break: break-word; } } @@ -34,8 +34,7 @@ td, th { - // Aligned tables shouldn't scroll horizontally so we need their contents to wrap. - word-break: break-all; + word-break: break-word; } } From d7e51c56888b3880a0c27d7129f4285ec9200df4 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski <grzegorz@gziolo.pl> Date: Tue, 6 Aug 2019 08:07:10 +0200 Subject: [PATCH 618/664] Server side render: Fix CHANGELOG entry --- packages/server-side-render/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server-side-render/CHANGELOG.md b/packages/server-side-render/CHANGELOG.md index 7781506cc47626..c83787d1c7b171 100644 --- a/packages/server-side-render/CHANGELOG.md +++ b/packages/server-side-render/CHANGELOG.md @@ -1,5 +1,5 @@ -## 1.1.0 (2019-08-05) +## 1.0.0 (2019-06-12) -### Enhancements +### Initial Release - Extracted the package from `@wordpress/components` and `@wordpress/editor`; From f52b01dd3fb2d2b08bc3a97d2cc9eac6748a1261 Mon Sep 17 00:00:00 2001 From: Marek Hrabe <marekhrabe@me.com> Date: Tue, 6 Aug 2019 00:56:14 -0700 Subject: [PATCH 619/664] update test with new checkbox implementation (#16918) --- .../enable-custom-fields.js.snap | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap b/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap index d4fa948a778449..71c2036684552d 100644 --- a/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap +++ b/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap @@ -21,7 +21,22 @@ exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation <label className="components-checkbox-control__label" htmlFor="inspector-checkbox-control-3" - /> + > + <svg + aria-hidden="true" + className="dashicon dashicons-yes components-checkbox-control__checked" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z" + /> + </svg> + </label> </div> </div> <p @@ -61,7 +76,22 @@ exports[`EnableCustomFieldsOption renders a checked checkbox when custom fields <label className="components-checkbox-control__label" htmlFor="inspector-checkbox-control-0" - /> + > + <svg + aria-hidden="true" + className="dashicon dashicons-yes components-checkbox-control__checked" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z" + /> + </svg> + </label> </div> </div> </div> From b4c7054da14d2b4660a96d95998702fe599f316b Mon Sep 17 00:00:00 2001 From: tellthemachines <tellthemachines@users.noreply.github.com> Date: Tue, 6 Aug 2019 18:42:02 +1000 Subject: [PATCH 620/664] Gutenberg experiments settings page. (#16626) * Gutenberg experiments settings page. * Address PR feedback. * Fix labels and heading semantics * UI improvements and disable by default. * Updated docs. * Address review feedback. * Changed feature flag logic * Remove check for test env * Merge experiments settings with existing settings. * Enable experimental blocks in test. * Prettify. * Docs updates * Remove extra whitespace. * Change function name. --- gutenberg.php | 27 ++-- lib/experiments-page.php | 120 ++++++++++++++++++ lib/load.php | 1 + packages/block-editor/README.md | 4 +- packages/block-editor/src/store/defaults.js | 4 + packages/block-library/src/index.js | 53 ++++++-- .../src/navigation-menu/index.js | 2 +- .../src/enable-experimental-features.js | 27 ++++ packages/e2e-test-utils/src/index.js | 1 + .../e2e-tests/specs/block-transforms.test.js | 2 + packages/edit-post/src/index.js | 6 +- packages/edit-widgets/src/index.js | 5 +- .../editor/src/components/provider/index.js | 2 + .../full-content/full-content.spec.js | 7 +- 14 files changed, 233 insertions(+), 28 deletions(-) create mode 100644 lib/experiments-page.php create mode 100644 packages/e2e-test-utils/src/enable-experimental-features.js diff --git a/gutenberg.php b/gutenberg.php index a8e822b1ea4eee..7ab481538e32cc 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -43,14 +43,16 @@ function gutenberg_menu() { 'gutenberg' ); - add_submenu_page( - 'gutenberg', - __( 'Widgets (beta)', 'gutenberg' ), - __( 'Widgets (beta)', 'gutenberg' ), - 'edit_theme_options', - 'gutenberg-widgets', - 'the_gutenberg_widgets' - ); + if ( get_option( 'gutenberg-experiments' ) && array_key_exists( 'gutenberg-widget-experiments', get_option( 'gutenberg-experiments' ) ) ) { + add_submenu_page( + 'gutenberg', + __( 'Widgets (beta)', 'gutenberg' ), + __( 'Widgets (beta)', 'gutenberg' ), + 'edit_theme_options', + 'gutenberg-widgets', + 'the_gutenberg_widgets' + ); + } if ( current_user_can( 'edit_posts' ) ) { $submenu['gutenberg'][] = array( @@ -65,6 +67,15 @@ function gutenberg_menu() { 'https://developer.wordpress.org/block-editor/', ); } + + add_submenu_page( + 'gutenberg', + __( 'Experiments Settings', 'gutenberg' ), + __( 'Experiments', 'gutenberg' ), + 'edit_posts', + 'gutenberg-experiments', + 'the_gutenberg_experiments' + ); } add_action( 'admin_menu', 'gutenberg_menu' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php new file mode 100644 index 00000000000000..0ca203444dc81f --- /dev/null +++ b/lib/experiments-page.php @@ -0,0 +1,120 @@ +<?php +/** + * Bootstraping the Gutenberg experiments page. + * + * @package gutenberg + */ + +/** + * The main entry point for the Gutenberg experiments page. + * + * @since 6.3.0 + * + * @param string $page The page name the function is being called for, `'gutenberg_customizer'` for the Customizer. + */ +function the_gutenberg_experiments( $page = 'gutenberg_page_gutenberg-experiments' ) { + ?> + <div + id="experiments-editor" + class="wrap" + > + <h1><?php echo __( 'Experiment settings', 'gutenberg' ); ?></h1> + <?php settings_errors(); ?> + <form method="post" action="options.php"> + <?php settings_fields( 'gutenberg-experiments' ); ?> + <?php do_settings_sections( 'gutenberg-experiments' ); ?> + <?php submit_button(); ?> + </form> + </div> + <?php +} + +/** + * Set up the experiments settings. + * + * @since 6.3.0 + */ +function gutenberg_initialize_experiments_settings() { + add_settings_section( + 'gutenberg_experiments_section', + // The empty string ensures the render function won't output a h2. + '', + 'gutenberg_display_experiment_section', + 'gutenberg-experiments' + ); + add_settings_field( + 'gutenberg-widget-experiments', + __( 'Widgets', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable Widgets Screen and Legacy Widget Block', 'gutenberg' ), + 'id' => 'gutenberg-widget-experiments', + ) + ); + add_settings_field( + 'gutenberg-menu-block', + __( 'Menu Block', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Enable Navigation Menu Block', 'gutenberg' ), + 'id' => 'gutenberg-menu-block', + ) + ); + register_setting( + 'gutenberg-experiments', + 'gutenberg-experiments' + ); +} + +add_action( 'admin_init', 'gutenberg_initialize_experiments_settings' ); + +/** + * Display a checkbox field for a Gutenberg experiment. + * + * @since 6.3.0 + * + * @param array $args ( $label, $id ). + */ +function gutenberg_display_experiment_field( $args ) { + $options = get_option( 'gutenberg-experiments' ); + $value = isset( $options[ $args['id'] ] ) ? 1 : 0; + ?> + <label for="<?php echo $args['id']; ?>"> + <input type="checkbox" name="<?php echo 'gutenberg-experiments[' . $args['id'] . ']'; ?>" id="<?php echo $args['id']; ?>" value="1" <?php checked( 1, $value ); ?> /> + <?php echo $args['label']; ?> + </label> + <?php +} + +/** + * Display the experiments section. + * + * @since 6.3.0 + */ +function gutenberg_display_experiment_section() { + ?> + <p><?php echo __( 'Gutenberg has some experimental features you can turn on. Simply select each you would like to use. These features are likely to change so it is inadvisable to use them in production.', 'gutenberg' ); ?></p> + + <?php +} + +/** + * Extends default editor settings with experiments settings. + * + * @param array $settings Default editor settings. + * + * @return array Filtered editor settings. + */ +function gutenberg_experiments_editor_settings( $settings ) { + $experiments_exist = get_option( 'gutenberg-experiments' ); + $experiments_settings = array( + '__experimentalEnableLegacyWidgetBlock' => $experiments_exist ? array_key_exists( 'gutenberg-widget-experiments', get_option( 'gutenberg-experiments' ) ) : false, + '__experimentalEnableMenuBlock' => $experiments_exist ? array_key_exists( 'gutenberg-menu-block', get_option( 'gutenberg-experiments' ) ) : false, + ); + return array_merge( $settings, $experiments_settings ); +} +add_filter( 'block_editor_settings', 'gutenberg_experiments_editor_settings' ); diff --git a/lib/load.php b/lib/load.php index c34a8d4fc4d408..f97db4e38eb897 100644 --- a/lib/load.php +++ b/lib/load.php @@ -35,4 +35,5 @@ require dirname( __FILE__ ) . '/demo.php'; require dirname( __FILE__ ) . '/widgets.php'; require dirname( __FILE__ ) . '/widgets-page.php'; +require dirname( __FILE__ ) . '/experiments-page.php'; require dirname( __FILE__ ) . '/customizer.php'; diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 36a256f93cb685..477c8657e40db3 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -382,7 +382,9 @@ The default editor settings bodyPlaceholder string Empty post placeholder titlePlaceholder string Empty title placeholder codeEditingEnabled string Whether or not the user can switch to the code editor - \_\_experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + **experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + **experimentalEnableLegacyWidgetBlock boolean Whether the user has enabled the Legacy Widget Block + \_\_experimentalEnableMenuBlock boolean Whether the user has enabled the Menu Block <a name="SkipToSelectedBlock" href="#SkipToSelectedBlock">#</a> **SkipToSelectedBlock** diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 1f91e29c7e2f50..885a8b79f7fb06 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -28,6 +28,8 @@ export const PREFERENCES_DEFAULTS = { * titlePlaceholder string Empty title placeholder * codeEditingEnabled string Whether or not the user can switch to the code editor * __experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + * __experimentalEnableLegacyWidgetBlock boolean Whether the user has enabled the Legacy Widget Block + * __experimentalEnableMenuBlock boolean Whether the user has enabled the Menu Block */ export const SETTINGS_DEFAULTS = { alignWide: false, @@ -144,5 +146,7 @@ export const SETTINGS_DEFAULTS = { availableLegacyWidgets: {}, hasPermissionsToManageWidgets: false, __experimentalCanUserUseUnfilteredHTML: false, + __experimentalEnableLegacyWidgetBlock: false, + __experimentalEnableMenuBlock: false, }; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index a9a9b78f34ca92..0d313bc171b20e 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -62,6 +62,23 @@ import * as tagCloud from './tag-cloud'; import * as classic from './classic'; +/** + * Function to register an individual block. + * + * @param {Object} block The block to be registered. + * + */ +const registerBlock = ( block ) => { + if ( ! block ) { + return; + } + const { metadata, settings, name } = block; + if ( metadata ) { + unstable__bootstrapServerSideBlockDefinitions( { [ name ]: metadata } ); // eslint-disable-line camelcase + } + registerBlockType( name, settings ); +}; + /** * Function to register core blocks provided by the block editor. * @@ -104,11 +121,8 @@ export const registerCoreBlocks = () => { mediaText, latestComments, latestPosts, - process.env.GUTENBERG_PHASE === 2 ? legacyWidget : null, missing, more, - process.env.GUTENBERG_PHASE === 2 ? navigationMenu : null, - process.env.GUTENBERG_PHASE === 2 ? navigationMenuItem : null, nextpage, preformatted, pullquote, @@ -124,16 +138,7 @@ export const registerCoreBlocks = () => { textColumns, verse, video, - ].forEach( ( block ) => { - if ( ! block ) { - return; - } - const { metadata, settings, name } = block; - if ( metadata ) { - unstable__bootstrapServerSideBlockDefinitions( { [ name ]: metadata } ); // eslint-disable-line camelcase - } - registerBlockType( name, settings ); - } ); + ].forEach( registerBlock ); setDefaultBlockName( paragraph.name ); if ( window.wp && window.wp.oldEditor ) { @@ -145,3 +150,25 @@ export const registerCoreBlocks = () => { setGroupingBlockName( group.name ); } }; + +/** + * Function to register experimental core blocks depending on editor settings. + * + * @param {Object} settings Editor settings. + * + * @example + * ```js + * import { __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; + * + * __experimentalRegisterExperimentalCoreBlocks( settings ); + * ``` + */ +export const __experimentalRegisterExperimentalCoreBlocks = process.env.GUTENBERG_PHASE === 2 ? ( settings ) => { + const { __experimentalEnableLegacyWidgetBlock, __experimentalEnableMenuBlock } = settings; + + [ + __experimentalEnableLegacyWidgetBlock ? legacyWidget : null, + __experimentalEnableMenuBlock ? navigationMenu : null, + __experimentalEnableMenuBlock ? navigationMenuItem : null, + ].forEach( registerBlock ); +} : undefined; diff --git a/packages/block-library/src/navigation-menu/index.js b/packages/block-library/src/navigation-menu/index.js index 8ca8e0c3fe560d..a7343c61100b49 100644 --- a/packages/block-library/src/navigation-menu/index.js +++ b/packages/block-library/src/navigation-menu/index.js @@ -26,7 +26,7 @@ export const settings = { align: [ 'wide', 'full' ], anchor: true, html: false, - inserter: false, + inserter: true, }, edit, diff --git a/packages/e2e-test-utils/src/enable-experimental-features.js b/packages/e2e-test-utils/src/enable-experimental-features.js new file mode 100644 index 00000000000000..fcb47843b54b9f --- /dev/null +++ b/packages/e2e-test-utils/src/enable-experimental-features.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { visitAdminPage } from './visit-admin-page'; + +/** + * Enables experimental features from the plugin settings section. + * + * @param {Array} features Array of {string} selectors of settings to enable. Assumes they can be enabled with one click. + */ +export async function enableExperimentalFeatures( features ) { + const query = addQueryArgs( '', { + page: 'gutenberg-experiments', + } ); + await visitAdminPage( '/admin.php', query ); + + await Promise.all( features.map( async ( feature ) => { + await page.waitForSelector( feature ); + await page.click( feature ); + await page.click( '#submit' ); + } ) ); +} diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index c6839ac84f1d55..06d898b31fc377 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -11,6 +11,7 @@ export { createURL } from './create-url'; export { deactivatePlugin } from './deactivate-plugin'; export { disablePrePublishChecks } from './disable-pre-publish-checks'; export { dragAndResize } from './drag-and-resize'; +export { enableExperimentalFeatures } from './enable-experimental-features'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; export { enablePrePublishChecks } from './enable-pre-publish-checks'; export { ensureSidebarOpened } from './ensure-sidebar-opened'; diff --git a/packages/e2e-tests/specs/block-transforms.test.js b/packages/e2e-tests/specs/block-transforms.test.js index 89d14bf524491f..381facd4c00673 100644 --- a/packages/e2e-tests/specs/block-transforms.test.js +++ b/packages/e2e-tests/specs/block-transforms.test.js @@ -19,6 +19,7 @@ import { getEditedPostContent, hasBlockSwitcher, createNewPost, + enableExperimentalFeatures, setPostContent, selectBlockByClientId, transformBlockTo, @@ -99,6 +100,7 @@ describe( 'Block transforms', () => { const transformStructure = {}; beforeAll( async () => { + await enableExperimentalFeatures( [ '#gutenberg-widget-experiments', '#gutenberg-menu-block' ] ); await createNewPost(); for ( const fileBase of fileBasenames ) { diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 8027695313a6f0..531f8e1c0267ee 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -7,7 +7,7 @@ import '@wordpress/editor'; import '@wordpress/nux'; import '@wordpress/viewport'; import '@wordpress/notices'; -import { registerCoreBlocks } from '@wordpress/block-library'; +import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; import { render, unmountComponentAtNode } from '@wordpress/element'; /** @@ -65,8 +65,10 @@ export function reinitializeEditor( postType, postId, target, settings, initialE export function initializeEditor( id, postType, postId, settings, initialEdits ) { const target = document.getElementById( id ); const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, initialEdits ); - registerCoreBlocks(); + if ( process.env.GUTENBERG_PHASE === 2 ) { + __experimentalRegisterExperimentalCoreBlocks( settings ); + } // Show a console log warning if the browser is not in Standards rendering mode. const documentMode = document.compatMode === 'CSS1Compat' ? 'Standards' : 'Quirks'; diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 16035513da954d..7efa0d6c5a4dd2 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -3,7 +3,7 @@ */ import '@wordpress/notices'; import { render } from '@wordpress/element'; -import { registerCoreBlocks } from '@wordpress/block-library'; +import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; /** * Internal dependencies @@ -21,6 +21,9 @@ import CustomizerEditWidgetsInitializer from './components/customizer-edit-widge */ export function initialize( id, settings ) { registerCoreBlocks(); + if ( process.env.GUTENBERG_PHASE === 2 ) { + __experimentalRegisterExperimentalCoreBlocks( settings ); + } render( <EditWidgetsInitializer settings={ settings } diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index cfa67ddbb615d7..b11341e1a33f0f 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -99,6 +99,8 @@ class EditorProvider extends Component { 'template', 'templateLock', 'titlePlaceholder', + '__experimentalEnableLegacyWidgetBlock', + '__experimentalEnableMenuBlock', ] ), __experimentalReusableBlocks: reusableBlocks, __experimentalMediaUpload: hasUploadPermissions ? mediaUpload : undefined, diff --git a/test/integration/full-content/full-content.spec.js b/test/integration/full-content/full-content.spec.js index cd5308e92b081e..c4c7b9c6b0dc76 100644 --- a/test/integration/full-content/full-content.spec.js +++ b/test/integration/full-content/full-content.spec.js @@ -14,7 +14,7 @@ import { unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from '@wordpress/blocks'; import { parse as grammarParse } from '@wordpress/block-serialization-default-parser'; -import { registerCoreBlocks } from '@wordpress/block-library'; +import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; import { //eslint-disable-line no-restricted-syntax blockNameToFixtureBasename, getAvailableBlockFixturesBasenames, @@ -48,10 +48,13 @@ function normalizeParsedBlocks( blocks ) { describe( 'full post content fixture', () => { beforeAll( () => { unstable__bootstrapServerSideBlockDefinitions( require( './server-registered.json' ) ); - + const settings = { __experimentalEnableLegacyWidgetBlock: true, __experimentalEnableMenuBlock: true }; // Load all hooks that modify blocks require( '../../../packages/editor/src/hooks' ); registerCoreBlocks(); + if ( process.env.GUTENBERG_PHASE === 2 ) { + __experimentalRegisterExperimentalCoreBlocks( settings ); + } } ); blockBasenames.forEach( ( basename ) => { From 4ef60f35a79bf36e6b756722ee2287cf4419f8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Tue, 6 Aug 2019 10:52:18 +0200 Subject: [PATCH 621/664] Chore: Update Lerna to the latest version (3.16.4) (#16919) --- package-lock.json | 2482 ++++++++++++++++++++++++++++----------------- package.json | 2 +- 2 files changed, 1557 insertions(+), 927 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04093ff7f7b4fa..83fca7b32a5d4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1190,6 +1190,368 @@ "minimist": "^1.2.0" } }, + "@evocateur/libnpmaccess": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz", + "integrity": "sha512-KSCAHwNWro0CF2ukxufCitT9K5LjL/KuMmNzSu8wuwN2rjyKHD8+cmOsiybK+W5hdnwc5M1SmRlVCaMHQo+3rg==", + "dev": true, + "requires": { + "@evocateur/npm-registry-fetch": "^4.0.0", + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "@evocateur/libnpmpublish": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@evocateur/libnpmpublish/-/libnpmpublish-1.2.2.tgz", + "integrity": "sha512-MJrrk9ct1FeY9zRlyeoyMieBjGDG9ihyyD9/Ft6MMrTxql9NyoEx2hw9casTIP4CdqEVu+3nQ2nXxoJ8RCXyFg==", + "dev": true, + "requires": { + "@evocateur/npm-registry-fetch": "^4.0.0", + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + }, + "dependencies": { + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + } + } + }, + "@evocateur/npm-registry-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@evocateur/npm-registry-fetch/-/npm-registry-fetch-4.0.0.tgz", + "integrity": "sha512-k1WGfKRQyhJpIr+P17O5vLIo2ko1PFLKwoetatdduUSt/aQ4J2sJrJwwatdI5Z3SiYk/mRH9S3JpdmMFd/IK4g==", + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, + "@evocateur/pacote": { + "version": "9.6.3", + "resolved": "https://registry.npmjs.org/@evocateur/pacote/-/pacote-9.6.3.tgz", + "integrity": "sha512-ExqNqcbdHQprEgKnY/uQz7WRtyHRbQxRl4JnVkSkmtF8qffRrF9K+piZKNLNSkRMOT/3H0e3IP44QVCHaXMWOQ==", + "dev": true, + "requires": { + "@evocateur/npm-registry-fetch": "^4.0.0", + "bluebird": "^3.5.3", + "cacache": "^12.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.5.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.4.4", + "npm-pick-manifest": "^2.2.3", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.3", + "safe-buffer": "^5.2.0", + "semver": "^5.7.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, + "cacache": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, "@hapi/address": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.0.0.tgz", @@ -1498,109 +1860,127 @@ } }, "@lerna/add": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.14.0.tgz", - "integrity": "sha512-Sa79Ju6HqF3heSVpBiYPNrGtuS56U/jMzVq4CcVvhNwB34USLrzJJncGFVcfnuUvsjKeFJv+jHxUycHeRE8XYw==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.16.2.tgz", + "integrity": "sha512-RAAaF8aODPogj2Ge9Wj3uxPFIBGpog9M+HwSuq03ZnkkO831AmasCTJDqV+GEpl1U2DvnhZQEwHpWmTT0uUeEw==", "dev": true, "requires": { - "@lerna/bootstrap": "3.14.0", - "@lerna/command": "3.14.0", - "@lerna/filter-options": "3.14.0", - "@lerna/npm-conf": "3.13.0", + "@evocateur/pacote": "^9.6.3", + "@lerna/bootstrap": "3.16.2", + "@lerna/command": "3.16.0", + "@lerna/filter-options": "3.16.0", + "@lerna/npm-conf": "3.16.0", "@lerna/validation-error": "3.13.0", "dedent": "^0.7.0", "npm-package-arg": "^6.1.0", - "p-map": "^1.2.0", - "pacote": "^9.5.0", - "semver": "^5.5.0" + "p-map": "^2.1.0", + "semver": "^6.2.0" }, "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@lerna/batch-packages": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.14.0.tgz", - "integrity": "sha512-RlBkQVNTqk1qvn6PFWiWNiskllUHh6tXbTVm43mZRNd+vhAyvrQC8RWJxH0ECVvnFAt9rSNGRIVbEJ31WnNQLg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.16.0.tgz", + "integrity": "sha512-7AdMkANpubY/FKFI01im01tlx6ygOBJ/0JcixMUWoWP/7Ds3SWQF22ID6fbBr38jUWptYLDs2fagtTDL7YUPuA==", "dev": true, "requires": { - "@lerna/package-graph": "3.14.0", + "@lerna/package-graph": "3.16.0", "npmlog": "^4.1.2" } }, "@lerna/bootstrap": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.14.0.tgz", - "integrity": "sha512-AvnuDp8b0kX4zZgqD3v7ItPABhUsN5CmTEvZBD2JqM+xkQKhzCfz5ABcHEwDwOPWnNQmtH+/2iQdwaD7xBcAXw==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.16.2.tgz", + "integrity": "sha512-I+gs7eh6rv9Vyd+CwqL7sftRfOOsSzCle8cv/CGlMN7/p7EAVhxEdAw8SYoHIKHzipXszuqqy1Y3opyleD0qdA==", "dev": true, "requires": { - "@lerna/batch-packages": "3.14.0", - "@lerna/command": "3.14.0", - "@lerna/filter-options": "3.14.0", - "@lerna/has-npm-version": "3.13.3", - "@lerna/npm-install": "3.13.3", - "@lerna/package-graph": "3.14.0", + "@lerna/batch-packages": "3.16.0", + "@lerna/command": "3.16.0", + "@lerna/filter-options": "3.16.0", + "@lerna/has-npm-version": "3.16.0", + "@lerna/npm-install": "3.16.0", + "@lerna/package-graph": "3.16.0", "@lerna/pulse-till-done": "3.13.0", - "@lerna/rimraf-dir": "3.13.3", - "@lerna/run-lifecycle": "3.14.0", - "@lerna/run-parallel-batches": "3.13.0", - "@lerna/symlink-binary": "3.14.0", - "@lerna/symlink-dependencies": "3.14.0", + "@lerna/rimraf-dir": "3.14.2", + "@lerna/run-lifecycle": "3.16.2", + "@lerna/run-parallel-batches": "3.16.0", + "@lerna/symlink-binary": "3.16.2", + "@lerna/symlink-dependencies": "3.16.2", "@lerna/validation-error": "3.13.0", "dedent": "^0.7.0", - "get-port": "^3.2.0", - "multimatch": "^2.1.0", + "get-port": "^4.2.0", + "multimatch": "^3.0.0", "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", "p-finally": "^1.0.0", - "p-map": "^1.2.0", + "p-map": "^2.1.0", "p-map-series": "^1.0.0", "p-waterfall": "^1.0.0", "read-package-tree": "^5.1.6", - "semver": "^5.5.0" + "semver": "^6.2.0" }, "dependencies": { + "get-port": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz", + "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==", + "dev": true + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@lerna/changed": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.14.1.tgz", - "integrity": "sha512-G0RgYL/WLTFzbezRBLUO2J0v39EvgZIO5bHHUtYt7zUFSfzapkPfvpdpBj+5JlMtf0B2xfxwTk+lSA4LVnbfmA==", + "version": "3.16.4", + "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.16.4.tgz", + "integrity": "sha512-NCD7XkK744T23iW0wqKEgF4R9MYmReUbyHCZKopFnsNpQdqumc3SOIvQUAkKCP6hQJmYvxvOieoVgy/CVDpZ5g==", "dev": true, "requires": { - "@lerna/collect-updates": "3.14.0", - "@lerna/command": "3.14.0", - "@lerna/listable": "3.14.0", + "@lerna/collect-updates": "3.16.0", + "@lerna/command": "3.16.0", + "@lerna/listable": "3.16.0", "@lerna/output": "3.13.0", - "@lerna/version": "3.14.1" + "@lerna/version": "3.16.4" } }, "@lerna/check-working-tree": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.14.1.tgz", - "integrity": "sha512-ae/sdZPNh4SS+6c4UDuWP/QKbtIFAn/TvKsPncA1Jdo0PqXLBlug4DzkHTGkvZ5F0nj+0JrSxYteInakJV99vg==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.14.2.tgz", + "integrity": "sha512-7safqxM/MYoAoxZxulUDtIJIbnBIgo0PB/FHytueG+9VaX7GMnDte2Bt1EKa0dz2sAyQdmQ3Q8ZXpf/6JDjaeg==", "dev": true, "requires": { - "@lerna/collect-uncommitted": "3.14.1", - "@lerna/describe-ref": "3.13.3", + "@lerna/collect-uncommitted": "3.14.2", + "@lerna/describe-ref": "3.14.2", "@lerna/validation-error": "3.13.0" } }, "@lerna/child-process": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.13.3.tgz", - "integrity": "sha512-3/e2uCLnbU+bydDnDwyadpOmuzazS01EcnOleAnuj9235CU2U97DH6OyoG1EW/fU59x11J+HjIqovh5vBaMQjQ==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.14.2.tgz", + "integrity": "sha512-xnq+W5yQb6RkwI0p16ZQnrn6HkloH/MWTw4lGE1nKsBLAUbmSU5oTE93W1nrG0X3IMF/xWc9UYvNdUGMWvZZ4w==", "dev": true, "requires": { "chalk": "^2.3.1", @@ -1664,19 +2044,27 @@ } }, "@lerna/clean": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.14.0.tgz", - "integrity": "sha512-wEuAqOS9VMqh2C20KD63IySzyEnyVDqDI3LUsXw+ByUf9AJDgEHv0TCOxbDjDYaaQw1tjSBNZMyYInNeoASwhA==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.16.0.tgz", + "integrity": "sha512-5P9U5Y19WmYZr7UAMGXBpY7xCRdlR7zhHy8MAPDKVx70rFIBS6nWXn5n7Kntv74g7Lm1gJ2rsiH5tj1OPcRJgg==", "dev": true, "requires": { - "@lerna/command": "3.14.0", - "@lerna/filter-options": "3.14.0", + "@lerna/command": "3.16.0", + "@lerna/filter-options": "3.16.0", "@lerna/prompt": "3.13.0", "@lerna/pulse-till-done": "3.13.0", - "@lerna/rimraf-dir": "3.13.3", - "p-map": "^1.2.0", + "@lerna/rimraf-dir": "3.14.2", + "p-map": "^2.1.0", "p-map-series": "^1.0.0", "p-waterfall": "^1.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, "@lerna/cli": { @@ -1875,48 +2263,62 @@ } }, "@lerna/collect-uncommitted": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-3.14.1.tgz", - "integrity": "sha512-hQ67S+nlSJwsPylXbWlrQSZUcWa8tTNIdcMd9OY4+QxdJlZUG7CLbWSyaxi0g11WdoRJHT163mr9xQyAvIVT1A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-3.14.2.tgz", + "integrity": "sha512-4EkQu4jIOdNL2BMzy/N0ydHB8+Z6syu6xiiKXOoFl0WoWU9H1jEJCX4TH7CmVxXL1+jcs8FIS2pfQz4oew99Eg==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", + "@lerna/child-process": "3.14.2", "chalk": "^2.3.1", "figgy-pudding": "^3.5.1", "npmlog": "^4.1.2" } }, "@lerna/collect-updates": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.14.0.tgz", - "integrity": "sha512-siRHo2atAwj5KpKVOo6QTVIYDYbNs7dzTG6ow9VcFMLKX5shuaEyFA22Z3LmnxQ3sakVFdgvvVeediEz6cM3VA==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.16.0.tgz", + "integrity": "sha512-HwAIl815X2TNlmcp28zCrSdXfoZWNP7GJPEqNWYk7xDJTYLqQ+SrmKUePjb3AMGBwYAraZSEJLbHdBpJ5+cHmQ==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/describe-ref": "3.13.3", + "@lerna/child-process": "3.14.2", + "@lerna/describe-ref": "3.14.2", "minimatch": "^3.0.4", "npmlog": "^4.1.2", - "slash": "^1.0.0" + "slash": "^2.0.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } } }, "@lerna/command": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.14.0.tgz", - "integrity": "sha512-PtFi5EtXB2VuSruoLsjfZdus56d7oKlZAI4iSRoaS/BBxE2Wyfn7//vW7Ow4hZCzuqb9tBcpDq+4u2pdXN1d2Q==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.16.0.tgz", + "integrity": "sha512-u7tE4GC4/gfbPA9eQg+0ulnoJ+PMoMqomx033r/IxqZrHtmJR9+pF/37S0fsxJ2hX/RMFPC7c9Q/i8NEufSpdQ==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/package-graph": "3.14.0", - "@lerna/project": "3.13.1", + "@lerna/child-process": "3.14.2", + "@lerna/package-graph": "3.16.0", + "@lerna/project": "3.16.0", "@lerna/validation-error": "3.13.0", "@lerna/write-log-file": "3.13.0", "dedent": "^0.7.0", "execa": "^1.0.0", - "is-ci": "^1.0.10", - "lodash": "^4.17.5", + "is-ci": "^2.0.0", + "lodash": "^4.17.14", "npmlog": "^4.1.2" }, "dependencies": { + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -1954,6 +2356,15 @@ "pump": "^3.0.0" } }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1973,30 +2384,31 @@ } }, "@lerna/conventional-commits": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.14.0.tgz", - "integrity": "sha512-hGZ2qQZ9uEGf2eeIiIpEodSs9Qkkf/2uYEtNT7QN1RYISPUh6/lKGBssc5dpbCF64aEuxmemWLdlDf1ogG6++w==", + "version": "3.16.4", + "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.16.4.tgz", + "integrity": "sha512-QSZJ0bC9n6FVaf+7KDIq5zMv8WnHXnwhyL5jG1Nyh3SgOg9q2uflqh7YsYB+G6FwaRfnPaKosh6obijpYg0llA==", "dev": true, "requires": { "@lerna/validation-error": "3.13.0", "conventional-changelog-angular": "^5.0.3", "conventional-changelog-core": "^3.1.6", - "conventional-recommended-bump": "^4.0.4", - "fs-extra": "^7.0.0", + "conventional-recommended-bump": "^5.0.0", + "fs-extra": "^8.1.0", "get-stream": "^4.0.0", + "lodash.template": "^4.5.0", "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", - "pify": "^3.0.0", - "semver": "^5.5.0" + "pify": "^4.0.1", + "semver": "^6.2.0" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } @@ -2010,10 +2422,16 @@ "pump": "^3.0.0" } }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "pump": { @@ -2027,34 +2445,34 @@ } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@lerna/create": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.14.0.tgz", - "integrity": "sha512-J4PeGnzVBOSV7Cih8Uhv9xIauljR9bGcfSDN9aMzFtJhSX0xFXNvmnpXRORp7xNHV2lbxk7mNxRQxzR9CQRMuw==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.16.0.tgz", + "integrity": "sha512-OZApR1Iz7awutbmj4sAArwhqCyKgcrnw9rH0aWAUrkYWrD1w4TwkvAcYAsfx5GpQGbLQwoXhoyyPwPfZRRWz3Q==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/command": "3.14.0", - "@lerna/npm-conf": "3.13.0", + "@evocateur/pacote": "^9.6.3", + "@lerna/child-process": "3.14.2", + "@lerna/command": "3.16.0", + "@lerna/npm-conf": "3.16.0", "@lerna/validation-error": "3.13.0", "camelcase": "^5.0.0", "dedent": "^0.7.0", - "fs-extra": "^7.0.0", - "globby": "^8.0.1", + "fs-extra": "^8.1.0", + "globby": "^9.2.0", "init-package-json": "^1.10.3", "npm-package-arg": "^6.1.0", "p-reduce": "^1.0.0", - "pacote": "^9.5.0", - "pify": "^3.0.0", - "semver": "^5.5.0", - "slash": "^1.0.0", + "pify": "^4.0.1", + "semver": "^6.2.0", + "slash": "^2.0.0", "validate-npm-package-license": "^3.0.3", "validate-npm-package-name": "^3.0.0", "whatwg-url": "^7.0.0" @@ -2067,26 +2485,38 @@ "dev": true }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, "whatwg-url": { @@ -2103,84 +2533,98 @@ } }, "@lerna/create-symlink": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.14.0.tgz", - "integrity": "sha512-Kw51HYOOi6UfCKncqkgEU1k/SYueSBXgkNL91FR8HAZH7EPSRTEtp9mnJo568g0+Hog5C+3cOaWySwhHpRG29A==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.16.2.tgz", + "integrity": "sha512-pzXIJp6av15P325sgiIRpsPXLFmkisLhMBCy4764d+7yjf2bzrJ4gkWVMhsv4AdF0NN3OyZ5jjzzTtLNqfR+Jw==", "dev": true, "requires": { - "cmd-shim": "^2.0.2", - "fs-extra": "^7.0.0", + "@zkochan/cmd-shim": "^3.1.0", + "fs-extra": "^8.1.0", "npmlog": "^4.1.2" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true } } }, "@lerna/describe-ref": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.13.3.tgz", - "integrity": "sha512-5KcLTvjdS4gU5evW8ESbZ0BF44NM5HrP3dQNtWnOUSKJRgsES8Gj0lq9AlB2+YglZfjEftFT03uOYOxnKto4Uw==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.14.2.tgz", + "integrity": "sha512-qa5pzDRK2oBQXNjyRmRnN7E8a78NMYfQjjlRFB0KNHMsT6mCiL9+8kIS39sSE2NqT8p7xVNo2r2KAS8R/m3CoQ==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", + "@lerna/child-process": "3.14.2", "npmlog": "^4.1.2" } }, "@lerna/diff": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.14.0.tgz", - "integrity": "sha512-H6FSj0jOiQ6unVCwOK6ReT5uZN6ZIn/j/cx4YwuOtU3SMcs3UfuQRIFNeKg/tKmOcQGd39Mn9zDhmt3TAYGROA==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.16.0.tgz", + "integrity": "sha512-QUpVs5TPl8vBIne10/vyjUxanQBQQp7Lk3iaB8MnCysKr0O+oy7trWeFVDPEkBTCD177By7yPGyW5Yey1nCBbA==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/command": "3.14.0", + "@lerna/child-process": "3.14.2", + "@lerna/command": "3.16.0", "@lerna/validation-error": "3.13.0", "npmlog": "^4.1.2" } }, "@lerna/exec": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.14.0.tgz", - "integrity": "sha512-cNFO8hWsBVLeqVQ7LsQ4rYKbbQ2eN+Ne+hWKTlUQoyRbYzgJ22TXhjKR6IMr68q0xtclcDlasfcNO+XEWESh0g==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.16.0.tgz", + "integrity": "sha512-mH3O5NXf/O88jBaBBTUf+d56CUkxpg782s3Jxy7HWbVuSUULt3iMRPTh+zEXO5/555etsIVVDDyUR76meklrJA==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/command": "3.14.0", - "@lerna/filter-options": "3.14.0", - "@lerna/run-topologically": "3.14.0", + "@lerna/child-process": "3.14.2", + "@lerna/command": "3.16.0", + "@lerna/filter-options": "3.16.0", + "@lerna/run-topologically": "3.16.0", "@lerna/validation-error": "3.13.0", - "p-map": "^1.2.0" + "p-map": "^2.1.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, "@lerna/filter-options": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.14.0.tgz", - "integrity": "sha512-ZmNZK9m8evxHc+2ZnDyCm8XFIKVDKpIASG1wtizr3R14t49fuYE7nR+rm4t82u9oSSmER8gb8bGzh0SKZme/jg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.16.0.tgz", + "integrity": "sha512-InIi1fF8+PxpCwir9bIy+pGxrdE6hvN0enIs1eNGCVS1TTE8osNgiZXa838bMQ1yaEccdcnVX6Z03BNKd56kNg==", "dev": true, "requires": { - "@lerna/collect-updates": "3.14.0", - "@lerna/filter-packages": "3.13.0", + "@lerna/collect-updates": "3.16.0", + "@lerna/filter-packages": "3.16.0", "dedent": "^0.7.0" } }, "@lerna/filter-packages": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.13.0.tgz", - "integrity": "sha512-RWiZWyGy3Mp7GRVBn//CacSnE3Kw82PxE4+H6bQ3pDUw/9atXn7NRX+gkBVQIYeKamh7HyumJtyOKq3Pp9BADQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.16.0.tgz", + "integrity": "sha512-eGFzQTx0ogkGDCnbTuXqssryR6ilp8+dcXt6B+aq1MaqL/vOJRZyqMm4TY3CUOUnzZCi9S2WWyMw3PnAJOF+kg==", "dev": true, "requires": { "@lerna/validation-error": "3.13.0", - "multimatch": "^2.1.0", + "multimatch": "^3.0.0", "npmlog": "^4.1.2" } }, @@ -2194,33 +2638,39 @@ } }, "@lerna/get-packed": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.13.0.tgz", - "integrity": "sha512-EgSim24sjIjqQDC57bgXD9l22/HCS93uQBbGpkzEOzxAVzEgpZVm7Fm1t8BVlRcT2P2zwGnRadIvxTbpQuDPTg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.16.0.tgz", + "integrity": "sha512-AjsFiaJzo1GCPnJUJZiTW6J1EihrPkc2y3nMu6m3uWFxoleklsSCyImumzVZJssxMi3CPpztj8LmADLedl9kXw==", "dev": true, "requires": { - "fs-extra": "^7.0.0", + "fs-extra": "^8.1.0", "ssri": "^6.0.1", "tar": "^4.4.8" }, "dependencies": { "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -2231,18 +2681,18 @@ } }, "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "yallist": { @@ -2254,16 +2704,120 @@ } }, "@lerna/github-client": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.13.3.tgz", - "integrity": "sha512-fcJkjab4kX0zcLLSa/DCUNvU3v8wmy2c1lhdIbL7s7gABmDcV0QZq93LhnEee3VkC9UpnJ6GKG4EkD7eIifBnA==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.16.0.tgz", + "integrity": "sha512-IVJjcKjkYaUEPJsDyAblHGEFFNKCRyMagbIDm14L7Ab94ccN6i4TKOqAFEJn2SJHYvKKBdp3Zj2zNlASOMe3DA==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@octokit/plugin-enterprise-rest": "^2.1.1", - "@octokit/rest": "^16.16.0", + "@lerna/child-process": "3.14.2", + "@octokit/plugin-enterprise-rest": "^3.6.1", + "@octokit/rest": "^16.28.4", "git-url-parse": "^11.1.2", "npmlog": "^4.1.2" + }, + "dependencies": { + "@octokit/request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.0.2.tgz", + "integrity": "sha512-z1BQr43g4kOL4ZrIVBMHwi68Yg9VbkRUyuAgqCp1rU3vbYa69+2gIld/+gHclw15bJWQnhqqyEb7h5a5EqgZ0A==", + "dev": true, + "requires": { + "@octokit/endpoint": "^5.1.0", + "@octokit/request-error": "^1.0.1", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^3.0.0" + } + }, + "@octokit/rest": { + "version": "16.28.7", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.28.7.tgz", + "integrity": "sha512-cznFSLEhh22XD3XeqJw51OLSfyL2fcFKUO+v2Ep9MTAFfFLS1cK1Zwd1yEgQJmJoDnj4/vv3+fGGZweG+xsbIA==", + "dev": true, + "requires": { + "@octokit/request": "^5.0.0", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^2.0.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^3.0.0", + "url-template": "^2.0.8" + } + }, + "before-after-hook": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", + "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", + "dev": true + }, + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, + "universal-user-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-3.0.0.tgz", + "integrity": "sha512-T3siHThqoj5X0benA5H0qcDnrKGXzU8TKoX15x/tQHw1hQBvIEBHjxQ2klizYsqBOO/Q+WuxoQUihadeeqDnoA==", + "dev": true, + "requires": { + "os-name": "^3.0.0" + } + } + } + }, + "@lerna/gitlab-client": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-3.15.0.tgz", + "integrity": "sha512-OsBvRSejHXUBMgwWQqNoioB8sgzL/Pf1pOUhHKtkiMl6aAWjklaaq5HPMvTIsZPfS6DJ9L5OK2GGZuooP/5c8Q==", + "dev": true, + "requires": { + "node-fetch": "^2.5.0", + "npmlog": "^4.1.2", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } } }, "@lerna/global-options": { @@ -2273,166 +2827,198 @@ "dev": true }, "@lerna/has-npm-version": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.13.3.tgz", - "integrity": "sha512-mQzoghRw4dBg0R9FFfHrj0TH0glvXyzdEZmYZ8Isvx5BSuEEwpsryoywuZSdppcvLu8o7NAdU5Tac8cJ/mT52w==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.16.0.tgz", + "integrity": "sha512-TIY036dA9J8OyTrZq9J+it2DVKifL65k7hK8HhkUPpitJkw6jwbMObA/8D40LOGgWNPweJWqmlrTbRSwsR7DrQ==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "semver": "^5.5.0" + "@lerna/child-process": "3.14.2", + "semver": "^6.2.0" }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@lerna/import": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.14.0.tgz", - "integrity": "sha512-j8z/m85FX1QYPgl5TzMNupdxsQF/NFZSmdCR19HQzqiVKC8ULGzF30WJEk66+KeZ94wYMSakINtYD+41s34pNQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.16.0.tgz", + "integrity": "sha512-trsOmGHzw0rL/f8BLNvd+9PjoTkXq2Dt4/V2UCha254hMQaYutbxcYu8iKPxz9x86jSPlH7FpbTkkHXDsoY7Yg==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/command": "3.14.0", + "@lerna/child-process": "3.14.2", + "@lerna/command": "3.16.0", "@lerna/prompt": "3.13.0", "@lerna/pulse-till-done": "3.13.0", "@lerna/validation-error": "3.13.0", "dedent": "^0.7.0", - "fs-extra": "^7.0.0", + "fs-extra": "^8.1.0", "p-map-series": "^1.0.0" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true } } }, "@lerna/init": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.14.0.tgz", - "integrity": "sha512-X3PQkQZds5ozA1xiarmVzAK6LPLNK3bBu24Api0w2KJXO7Ccs9ob/VcGdevZuzqdJo1Xg2H6oBhEqIClU9Uqqw==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.16.0.tgz", + "integrity": "sha512-Ybol/x5xMtBgokx4j7/Y3u0ZmNh0NiSWzBFVaOs2NOJKvuqrWimF67DKVz7yYtTYEjtaMdug64ohFF4jcT/iag==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", - "@lerna/command": "3.14.0", - "fs-extra": "^7.0.0", - "p-map": "^1.2.0", - "write-json-file": "^2.3.0" + "@lerna/child-process": "3.14.2", + "@lerna/command": "3.16.0", + "fs-extra": "^8.1.0", + "p-map": "^2.1.0", + "write-json-file": "^3.2.0" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } + } + }, + "@lerna/link": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.16.2.tgz", + "integrity": "sha512-eCPg5Lo8HT525fIivNoYF3vWghO3UgEVFdbsiPmhzwI7IQyZro5HWYzLtywSAdEog5XZpd2Bbn0CsoHWBB3gww==", + "dev": true, + "requires": { + "@lerna/command": "3.16.0", + "@lerna/package-graph": "3.16.0", + "@lerna/symlink-dependencies": "3.16.2", + "p-map": "^2.1.0", + "slash": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true } } }, - "@lerna/link": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.14.0.tgz", - "integrity": "sha512-xlwQhWTVOZrgAuoONY3/OIBWehDfZXmf5qFhnOy7lIxByRhEX5Vwx0ApaGxHTv3Flv7T+oI4s8UZVq5F6dT8Aw==", - "dev": true, - "requires": { - "@lerna/command": "3.14.0", - "@lerna/package-graph": "3.14.0", - "@lerna/symlink-dependencies": "3.14.0", - "p-map": "^1.2.0", - "slash": "^1.0.0" - } - }, "@lerna/list": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.14.0.tgz", - "integrity": "sha512-Gp+9gaIkBfXBwc9Ng0Y74IEfAqpQpLiXwOP4IOpdINxOeDpllhMaYP6SzLaMvrfSyHRayM7Cq5/PRnHkXQ5uuQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.16.0.tgz", + "integrity": "sha512-TkvstoPsgKqqQ0KfRumpsdMXfRSEhdXqOLq519XyI5IRWYxhoqXqfi8gG37UoBPhBNoe64japn5OjphF3rOmQA==", "dev": true, "requires": { - "@lerna/command": "3.14.0", - "@lerna/filter-options": "3.14.0", - "@lerna/listable": "3.14.0", + "@lerna/command": "3.16.0", + "@lerna/filter-options": "3.16.0", + "@lerna/listable": "3.16.0", "@lerna/output": "3.13.0" } }, "@lerna/listable": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.14.0.tgz", - "integrity": "sha512-ZK44Mo8xf/N97eQZ236SPSq0ek6+gk4HqHIx05foEMZVV1iIDH4a/nblLsJNjGQVsIdMYFPaqNJ0z+ZQfiJazQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.16.0.tgz", + "integrity": "sha512-mtdAT2EEECqrJSDm/aXlOUFr1MRE4p6hppzY//Klp05CogQy6uGaKk+iKG5yyCLaOXFFZvG4HfO11CmoGSDWzw==", "dev": true, "requires": { - "@lerna/query-graph": "3.14.0", + "@lerna/query-graph": "3.16.0", "chalk": "^2.3.1", "columnify": "^1.5.4" } }, "@lerna/log-packed": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.13.0.tgz", - "integrity": "sha512-Rmjrcz+6aM6AEcEVWmurbo8+AnHOvYtDpoeMMJh9IZ9SmZr2ClXzmD7wSvjTQc8BwOaiWjjC/ukcT0UYA2m7wg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.16.0.tgz", + "integrity": "sha512-Fp+McSNBV/P2mnLUYTaSlG8GSmpXM7krKWcllqElGxvAqv6chk2K3c2k80MeVB4WvJ9tRjUUf+i7HUTiQ9/ckQ==", "dev": true, "requires": { - "byte-size": "^4.0.3", + "byte-size": "^5.0.1", "columnify": "^1.5.4", "has-unicode": "^2.0.1", "npmlog": "^4.1.2" } }, "@lerna/npm-conf": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.13.0.tgz", - "integrity": "sha512-Jg2kANsGnhg+fbPEzE0X9nX5oviEAvWj0nYyOkcE+cgWuT7W0zpnPXC4hA4C5IPQGhwhhh0IxhWNNHtjTuw53g==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.16.0.tgz", + "integrity": "sha512-HbO3DUrTkCAn2iQ9+FF/eisDpWY5POQAOF1m7q//CZjdC2HSW3UYbKEGsSisFxSfaF9Z4jtrV+F/wX6qWs3CuA==", "dev": true, "requires": { "config-chain": "^1.1.11", - "pify": "^3.0.0" + "pify": "^4.0.1" }, "dependencies": { "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true } } }, "@lerna/npm-dist-tag": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.14.0.tgz", - "integrity": "sha512-DEyYEdufTGIC6E4RTJUsYPgqlz1Bs/XPeEQ5fd+ojWnICevj7dRrr2DfHucPiUCADlm2jbAraAQc3QPU0dXRhw==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.16.0.tgz", + "integrity": "sha512-MQrBkqJJB9+eNphuj9w90QPMOs4NQXMuSRk9NqzeFunOmdDopPCV0Q7IThSxEuWnhJ2n3B7G0vWUP7tNMPdqIQ==", "dev": true, "requires": { - "@lerna/otplease": "3.14.0", + "@evocateur/npm-registry-fetch": "^4.0.0", + "@lerna/otplease": "3.16.0", "figgy-pudding": "^3.5.1", "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.9.0", "npmlog": "^4.1.2" } }, "@lerna/npm-install": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.13.3.tgz", - "integrity": "sha512-7Jig9MLpwAfcsdQ5UeanAjndChUjiTjTp50zJ+UZz4CbIBIDhoBehvNMTCL2G6pOEC7sGEg6sAqJINAqred6Tg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.16.0.tgz", + "integrity": "sha512-APUOIilZCzDzce92uLEwzt1r7AEMKT/hWA1ThGJL+PO9Rn8A95Km3o2XZAYG4W0hR+P4O2nSVuKbsjQtz8CjFQ==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", + "@lerna/child-process": "3.14.2", "@lerna/get-npm-exec-opts": "3.13.0", - "fs-extra": "^7.0.0", + "fs-extra": "^8.1.0", "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", "signal-exit": "^3.0.2", @@ -2440,69 +3026,81 @@ }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true } } }, "@lerna/npm-publish": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.14.0.tgz", - "integrity": "sha512-ShG0qEnGkWxtjQvIRATgm/CzeoVaSyyoNRag5t8gDSR/r2u9ux72oROKQUEaE8OwcTS4rL2cyBECts8XMNmyYw==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.16.2.tgz", + "integrity": "sha512-tGMb9vfTxP57vUV5svkBQxd5Tzc+imZbu9ZYf8Mtwe0+HYfDjNiiHLIQw7G95w4YRdc5KsCE8sQ0uSj+f2soIg==", "dev": true, "requires": { - "@lerna/otplease": "3.14.0", - "@lerna/run-lifecycle": "3.14.0", + "@evocateur/libnpmpublish": "^1.2.2", + "@lerna/otplease": "3.16.0", + "@lerna/run-lifecycle": "3.16.2", "figgy-pudding": "^3.5.1", - "fs-extra": "^7.0.0", - "libnpmpublish": "^1.1.1", + "fs-extra": "^8.1.0", "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", - "pify": "^3.0.0", + "pify": "^4.0.1", "read-package-json": "^2.0.13" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true } } }, "@lerna/npm-run-script": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.13.3.tgz", - "integrity": "sha512-qR4o9BFt5hI8Od5/DqLalOJydnKpiQFEeN0h9xZi7MwzuX1Ukwh3X22vqsX4YRbipIelSFtrDzleNVUm5jj0ow==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.14.2.tgz", + "integrity": "sha512-LbVFv+nvAoRTYLMrJlJ8RiakHXrLslL7Jp/m1R18vYrB8LYWA3ey+nz5Tel2OELzmjUiemAKZsD9h6i+Re5egg==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", + "@lerna/child-process": "3.14.2", "@lerna/get-npm-exec-opts": "3.13.0", "npmlog": "^4.1.2" } }, "@lerna/otplease": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/otplease/-/otplease-3.14.0.tgz", - "integrity": "sha512-rYAWzaYZ81bwnrmTkYWGgcc13bl/6DlG7pjWQWNGAJNLzO5zzj0xmXN5sMFJnNvDpSiS/ZS1sIuPvb4xnwLUkg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/otplease/-/otplease-3.16.0.tgz", + "integrity": "sha512-uqZ15wYOHC+/V0WnD2iTLXARjvx3vNrpiIeyIvVlDB7rWse9mL4egex/QSgZ+lDx1OID7l2kgvcUD9cFpbqB7Q==", "dev": true, "requires": { "@lerna/prompt": "3.13.0", @@ -2519,40 +3117,40 @@ } }, "@lerna/pack-directory": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.14.0.tgz", - "integrity": "sha512-E9PmC1oWYjYN8Z0Oeoj7X98NruMg/pcdDiRxnwJ5awnB0d/kyfoquHXCYwCQQFCnWUfto7m5lM4CSostcolEVQ==", + "version": "3.16.4", + "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.16.4.tgz", + "integrity": "sha512-uxSF0HZeGyKaaVHz5FroDY9A5NDDiCibrbYR6+khmrhZtY0Bgn6hWq8Gswl9iIlymA+VzCbshWIMX4o2O8C8ng==", "dev": true, "requires": { - "@lerna/get-packed": "3.13.0", - "@lerna/package": "3.13.0", - "@lerna/run-lifecycle": "3.14.0", + "@lerna/get-packed": "3.16.0", + "@lerna/package": "3.16.0", + "@lerna/run-lifecycle": "3.16.2", "figgy-pudding": "^3.5.1", - "npm-packlist": "^1.4.1", + "npm-packlist": "^1.4.4", "npmlog": "^4.1.2", - "tar": "^4.4.8", + "tar": "^4.4.10", "temp-write": "^3.4.0" }, "dependencies": { "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "yallist": { @@ -2564,26 +3162,33 @@ } }, "@lerna/package": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.13.0.tgz", - "integrity": "sha512-kSKO0RJQy093BufCQnkhf1jB4kZnBvL7kK5Ewolhk5gwejN+Jofjd8DGRVUDUJfQ0CkW1o6GbUeZvs8w8VIZDg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.16.0.tgz", + "integrity": "sha512-2lHBWpaxcBoiNVbtyLtPUuTYEaB/Z+eEqRS9duxpZs6D+mTTZMNy6/5vpEVSCBmzvdYpyqhqaYjjSLvjjr5Riw==", "dev": true, "requires": { - "load-json-file": "^4.0.0", + "load-json-file": "^5.3.0", "npm-package-arg": "^6.1.0", "write-pkg": "^3.1.0" }, "dependencies": { + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.1.15", "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" } }, "parse-json": { @@ -2597,9 +3202,9 @@ } }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "strip-bom": { @@ -2611,61 +3216,61 @@ } }, "@lerna/package-graph": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.14.0.tgz", - "integrity": "sha512-dNpA/64STD5YXhaSlg4gT6Z474WPJVCHoX1ibsVIFu0fVgH609Y69bsdmbvTRdI7r6Dcu4ZfGxdR636RTrH+Eg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.16.0.tgz", + "integrity": "sha512-A2mum/gNbv7zCtAwJqoxzqv89As73OQNK2MgSX1SHWya46qoxO9a9Z2c5lOFQ8UFN5ZxqWMfFYXRCz7qzwmFXw==", "dev": true, "requires": { - "@lerna/prerelease-id-from-version": "3.14.0", + "@lerna/prerelease-id-from-version": "3.16.0", "@lerna/validation-error": "3.13.0", "npm-package-arg": "^6.1.0", "npmlog": "^4.1.2", - "semver": "^5.5.0" + "semver": "^6.2.0" }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@lerna/prerelease-id-from-version": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.14.0.tgz", - "integrity": "sha512-Ap3Z/dNhqQuSrKmK+JmzYvQYI2vowxHvUVxZJiDVilW8dyNnxkCsYFmkuZytk5sxVz4VeGLNPS2RSsU5eeSS+Q==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.16.0.tgz", + "integrity": "sha512-qZyeUyrE59uOK8rKdGn7jQz+9uOpAaF/3hbslJVFL1NqF9ELDTqjCPXivuejMX/lN4OgD6BugTO4cR7UTq/sZA==", "dev": true, "requires": { - "semver": "^5.5.0" + "semver": "^6.2.0" }, "dependencies": { "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "@lerna/project": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.13.1.tgz", - "integrity": "sha512-/GoCrpsCCTyb9sizk1+pMBrIYchtb+F1uCOn3cjn9yenyG/MfYEnlfrbV5k/UDud0Ei75YBLbmwCbigHkAKazQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.16.0.tgz", + "integrity": "sha512-NrKcKK1EqXqhrGvslz6Q36+ZHuK3zlDhGdghRqnxDcHxMPT01NgLcmsnymmQ+gjMljuLRmvKYYCuHrknzX8VrA==", "dev": true, "requires": { - "@lerna/package": "3.13.0", + "@lerna/package": "3.16.0", "@lerna/validation-error": "3.13.0", "cosmiconfig": "^5.1.0", "dedent": "^0.7.0", "dot-prop": "^4.2.0", - "glob-parent": "^3.1.0", - "globby": "^8.0.1", - "load-json-file": "^4.0.0", + "glob-parent": "^5.0.0", + "globby": "^9.2.0", + "load-json-file": "^5.3.0", "npmlog": "^4.1.2", - "p-map": "^1.2.0", + "p-map": "^2.1.0", "resolve-from": "^4.0.0", - "write-json-file": "^2.3.0" + "write-json-file": "^3.2.0" }, "dependencies": { "cosmiconfig": { @@ -2680,6 +3285,21 @@ "parse-json": "^4.0.0" } }, + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -2698,18 +3318,34 @@ } } }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.1.15", "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" } }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -2721,9 +3357,9 @@ } }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, "resolve-from": { @@ -2751,57 +3387,70 @@ } }, "@lerna/publish": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.14.1.tgz", - "integrity": "sha512-p+By/P84XJkndBzrmcnVLMcFpGAE+sQZCQK4e3aKQrEMLDrEwXkWt/XJxzeQskPxInFA/7Icj686LOADO7p0qg==", - "dev": true, - "requires": { - "@lerna/check-working-tree": "3.14.1", - "@lerna/child-process": "3.13.3", - "@lerna/collect-updates": "3.14.0", - "@lerna/command": "3.14.0", - "@lerna/describe-ref": "3.13.3", - "@lerna/log-packed": "3.13.0", - "@lerna/npm-conf": "3.13.0", - "@lerna/npm-dist-tag": "3.14.0", - "@lerna/npm-publish": "3.14.0", + "version": "3.16.4", + "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.16.4.tgz", + "integrity": "sha512-XZY+gRuF7/v6PDQwl7lvZaGWs8CnX6WIPIu+OCcyFPSL/rdWegdN7HieKBHskgX798qRQc2GrveaY7bNoTKXAw==", + "dev": true, + "requires": { + "@evocateur/libnpmaccess": "^3.1.2", + "@evocateur/npm-registry-fetch": "^4.0.0", + "@evocateur/pacote": "^9.6.3", + "@lerna/check-working-tree": "3.14.2", + "@lerna/child-process": "3.14.2", + "@lerna/collect-updates": "3.16.0", + "@lerna/command": "3.16.0", + "@lerna/describe-ref": "3.14.2", + "@lerna/log-packed": "3.16.0", + "@lerna/npm-conf": "3.16.0", + "@lerna/npm-dist-tag": "3.16.0", + "@lerna/npm-publish": "3.16.2", + "@lerna/otplease": "3.16.0", "@lerna/output": "3.13.0", - "@lerna/pack-directory": "3.14.0", - "@lerna/prerelease-id-from-version": "3.14.0", + "@lerna/pack-directory": "3.16.4", + "@lerna/prerelease-id-from-version": "3.16.0", "@lerna/prompt": "3.13.0", "@lerna/pulse-till-done": "3.13.0", - "@lerna/run-lifecycle": "3.14.0", - "@lerna/run-topologically": "3.14.0", + "@lerna/run-lifecycle": "3.16.2", + "@lerna/run-topologically": "3.16.0", "@lerna/validation-error": "3.13.0", - "@lerna/version": "3.14.1", + "@lerna/version": "3.16.4", "figgy-pudding": "^3.5.1", - "fs-extra": "^7.0.0", - "libnpmaccess": "^3.0.1", + "fs-extra": "^8.1.0", "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.9.0", "npmlog": "^4.1.2", "p-finally": "^1.0.0", - "p-map": "^1.2.0", + "p-map": "^2.1.0", "p-pipe": "^1.2.0", - "pacote": "^9.5.0", - "semver": "^5.5.0" + "semver": "^6.2.0" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -2816,150 +3465,196 @@ } }, "@lerna/query-graph": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-3.14.0.tgz", - "integrity": "sha512-6YTh3vDMW2hUxHdKeRvx4bosc9lZClKaN+DzC1XKTkwDbWrsjmEzLcemKL6QnyyeuryN2f/eto7P9iSe3z3pQQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-3.16.0.tgz", + "integrity": "sha512-p0RO+xmHDO95ChJdWkcy9TNLysLkoDARXeRHzY5U54VCwl3Ot/2q8fMCVlA5UeGXDutEyyByl3URqEpcQCWI7Q==", "dev": true, "requires": { - "@lerna/package-graph": "3.14.0", + "@lerna/package-graph": "3.16.0", "figgy-pudding": "^3.5.1" } }, "@lerna/resolve-symlink": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.13.0.tgz", - "integrity": "sha512-Lc0USSFxwDxUs5JvIisS8JegjA6SHSAWJCMvi2osZx6wVRkEDlWG2B1JAfXUzCMNfHoZX0/XX9iYZ+4JIpjAtg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.16.0.tgz", + "integrity": "sha512-Ibj5e7njVHNJ/NOqT4HlEgPFPtPLWsO7iu59AM5bJDcAJcR96mLZ7KGVIsS2tvaO7akMEJvt2P+ErwCdloG3jQ==", "dev": true, "requires": { - "fs-extra": "^7.0.0", + "fs-extra": "^8.1.0", "npmlog": "^4.1.2", "read-cmd-shim": "^1.0.1" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true } } }, "@lerna/rimraf-dir": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.13.3.tgz", - "integrity": "sha512-d0T1Hxwu3gpYVv73ytSL+/Oy8JitsmvOYUR5ouRSABsmqS7ZZCh5t6FgVDDGVXeuhbw82+vuny1Og6Q0k4ilqw==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.14.2.tgz", + "integrity": "sha512-eFNkZsy44Bu9v1Hrj5Zk6omzg8O9h/7W6QYK1TTUHeyrjTEwytaNQlqF0lrTLmEvq55sviV42NC/8P3M2cvq8Q==", "dev": true, "requires": { - "@lerna/child-process": "3.13.3", + "@lerna/child-process": "3.14.2", "npmlog": "^4.1.2", "path-exists": "^3.0.0", "rimraf": "^2.6.2" } }, "@lerna/run": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.14.0.tgz", - "integrity": "sha512-kGGFGLYPKozAN07CSJ7kOyLY6W3oLCQcxCathg1isSkBqQH29tWUg8qNduOlhIFLmnq/nf1JEJxxoXnF6IRLjQ==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.16.0.tgz", + "integrity": "sha512-woTeLlB1OAAz4zzjdI6RyIxSGuxiUPHJZm89E1pDEPoWwtQV6HMdMgrsQd9ATsJ5Ez280HH4bF/LStAlqW8Ufg==", "dev": true, "requires": { - "@lerna/command": "3.14.0", - "@lerna/filter-options": "3.14.0", - "@lerna/npm-run-script": "3.13.3", + "@lerna/command": "3.16.0", + "@lerna/filter-options": "3.16.0", + "@lerna/npm-run-script": "3.14.2", "@lerna/output": "3.13.0", - "@lerna/run-topologically": "3.14.0", + "@lerna/run-topologically": "3.16.0", "@lerna/timer": "3.13.0", "@lerna/validation-error": "3.13.0", - "p-map": "^1.2.0" + "p-map": "^2.1.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, "@lerna/run-lifecycle": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.14.0.tgz", - "integrity": "sha512-GUM3L9MzGRSW0WQ8wbLW1+SYStU1OFjW0GBzShhBnFrO4nGRrU7VchsLpcLu0hk2uCzyhsrDKzifEdOdUyMoEQ==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.16.2.tgz", + "integrity": "sha512-RqFoznE8rDpyyF0rOJy3+KjZCeTkO8y/OB9orPauR7G2xQ7PTdCpgo7EO6ZNdz3Al+k1BydClZz/j78gNCmL2A==", "dev": true, "requires": { - "@lerna/npm-conf": "3.13.0", + "@lerna/npm-conf": "3.16.0", "figgy-pudding": "^3.5.1", - "npm-lifecycle": "^2.1.1", + "npm-lifecycle": "^3.1.2", "npmlog": "^4.1.2" } }, "@lerna/run-parallel-batches": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.13.0.tgz", - "integrity": "sha512-bICFBR+cYVF1FFW+Tlm0EhWDioTUTM6dOiVziDEGE1UZha1dFkMYqzqdSf4bQzfLS31UW/KBd/2z8jy2OIjEjg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.16.0.tgz", + "integrity": "sha512-2J/Nyv+MvogmQEfC7VcS21ifk7w0HVvzo2yOZRPvkCzGRu/rducxtB4RTcr58XCZ8h/Bt1aqQYKExu3c/3GXwg==", "dev": true, "requires": { - "p-map": "^1.2.0", + "p-map": "^2.1.0", "p-map-series": "^1.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, "@lerna/run-topologically": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-3.14.0.tgz", - "integrity": "sha512-y+KBpC1YExFzGynovt9MY4O/bc3RrJaKeuXieiPfKGKxrdtmZe/r33oj/xePTXZq65jnw3SaU3H8S5CrrdkwDg==", + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-3.16.0.tgz", + "integrity": "sha512-4Hlpv4zDtKWa5Z0tPkeu0sK+bxZEKgkNESMGmWrUCNfj7xwvAJurcraK8+a2Y0TFYwf0qjSLY/MzX+ZbJA3Cgw==", "dev": true, "requires": { - "@lerna/query-graph": "3.14.0", + "@lerna/query-graph": "3.16.0", "figgy-pudding": "^3.5.1", "p-queue": "^4.0.0" } }, "@lerna/symlink-binary": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.14.0.tgz", - "integrity": "sha512-AHFb4NlazxYmC+7guoamM3laIRbMSeKERMooKHJ7moe0ayGPBWsCGOH+ZFKZ+eXSDek+FnxdzayR3wf8B3LkTg==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.16.2.tgz", + "integrity": "sha512-kz9XVoFOGSF83gg4gBqH+mG6uxfJfTp8Uy+Cam40CvMiuzfODrGkjuBEFoM/uO2QOAwZvbQDYOBpKUa9ZxHS1Q==", "dev": true, "requires": { - "@lerna/create-symlink": "3.14.0", - "@lerna/package": "3.13.0", - "fs-extra": "^7.0.0", - "p-map": "^1.2.0" + "@lerna/create-symlink": "3.16.2", + "@lerna/package": "3.16.0", + "fs-extra": "^8.1.0", + "p-map": "^2.1.0" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true } } }, "@lerna/symlink-dependencies": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.14.0.tgz", - "integrity": "sha512-kuSXxwAWiVZqFcXfUBKH4yLUH3lrnGyZmCYon7UnZitw3AK3LQY7HvV2LNNw/oatfjOAiKhPBxnYjYijKiV4oA==", + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.16.2.tgz", + "integrity": "sha512-wnZqGJQ+Jvr1I3inxrkffrFZfmQI7Ta8gySw/UWCy95QtZWF/f5yk8zVIocCAsjzD0wgb3jJE3CFJ9W5iwWk1A==", "dev": true, "requires": { - "@lerna/create-symlink": "3.14.0", - "@lerna/resolve-symlink": "3.13.0", - "@lerna/symlink-binary": "3.14.0", - "fs-extra": "^7.0.0", + "@lerna/create-symlink": "3.16.2", + "@lerna/resolve-symlink": "3.16.0", + "@lerna/symlink-binary": "3.16.2", + "fs-extra": "^8.1.0", "p-finally": "^1.0.0", - "p-map": "^1.2.0", + "p-map": "^2.1.0", "p-map-series": "^1.0.0" }, "dependencies": { "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } + }, + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true } } }, @@ -2979,41 +3674,53 @@ } }, "@lerna/version": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.14.1.tgz", - "integrity": "sha512-H/jykoxVIt4oDEYkBgwDfO5dmZFl3G6vP1UEttRVP1FIkI+gCN+olby8S0Qd8XprDuR5OrLboiDWQs3p7nJhLw==", - "dev": true, - "requires": { - "@lerna/batch-packages": "3.14.0", - "@lerna/check-working-tree": "3.14.1", - "@lerna/child-process": "3.13.3", - "@lerna/collect-updates": "3.14.0", - "@lerna/command": "3.14.0", - "@lerna/conventional-commits": "3.14.0", - "@lerna/github-client": "3.13.3", + "version": "3.16.4", + "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.16.4.tgz", + "integrity": "sha512-ikhbMeIn5ljCtWTlHDzO4YvTmpGTX1lWFFIZ79Vd1TNyOr+OUuKLo/+p06mCl2WEdZu0W2s5E9oxfAAQbyDxEg==", + "dev": true, + "requires": { + "@lerna/check-working-tree": "3.14.2", + "@lerna/child-process": "3.14.2", + "@lerna/collect-updates": "3.16.0", + "@lerna/command": "3.16.0", + "@lerna/conventional-commits": "3.16.4", + "@lerna/github-client": "3.16.0", + "@lerna/gitlab-client": "3.15.0", "@lerna/output": "3.13.0", - "@lerna/prerelease-id-from-version": "3.14.0", + "@lerna/prerelease-id-from-version": "3.16.0", "@lerna/prompt": "3.13.0", - "@lerna/run-lifecycle": "3.14.0", - "@lerna/run-topologically": "3.14.0", + "@lerna/run-lifecycle": "3.16.2", + "@lerna/run-topologically": "3.16.0", "@lerna/validation-error": "3.13.0", "chalk": "^2.3.1", "dedent": "^0.7.0", "minimatch": "^3.0.4", "npmlog": "^4.1.2", - "p-map": "^1.2.0", + "p-map": "^2.1.0", "p-pipe": "^1.2.0", "p-reduce": "^1.0.0", "p-waterfall": "^1.0.0", - "semver": "^5.5.0", - "slash": "^1.0.0", + "semver": "^6.2.0", + "slash": "^2.0.0", "temp-write": "^3.4.0" }, "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true } } @@ -3080,9 +3787,9 @@ } }, "@octokit/plugin-enterprise-rest": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-2.2.2.tgz", - "integrity": "sha512-CTZr64jZYhGWNTDGlSJ2mvIlFsm9OEO3LqWn9I/gmoHI4jRBp4kpHoFYNemG4oA75zUAcmbuWblb7jjP877YZw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-3.6.2.tgz", + "integrity": "sha512-3wF5eueS5OHQYuAEudkpN+xVeUsg8vYEMMenEzLphUZ7PRZ8OJtDcsreL3ad9zxXmBbaFWzLmFcdob5CLyZftA==", "dev": true }, "@octokit/request": { @@ -3420,6 +4127,23 @@ "@babel/types": "^7.3.0" } }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", + "dev": true + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", @@ -3445,6 +4169,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, "@types/node": { "version": "10.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.2.tgz", @@ -4353,6 +5083,17 @@ "lodash": "^4.17.14" } }, + "@zkochan/cmd-shim": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz", + "integrity": "sha512-o8l0+x7C7sMZU3v9GuJIAU10qQLtwR1dtRQIOmlNMtyaqhmpXOzx1HWiYoWfmmf9HHZoAkXpc9TM9PQYF9d4Jg==", + "dev": true, + "requires": { + "is-windows": "^1.0.0", + "mkdirp-promise": "^5.0.1", + "mz": "^2.5.0" + } + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -4619,6 +5360,12 @@ "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -4700,9 +5447,9 @@ "dev": true }, "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-2.1.0.tgz", + "integrity": "sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==", "dev": true }, "array-equal": { @@ -6098,9 +6845,9 @@ "dev": true }, "byte-size": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.4.tgz", - "integrity": "sha512-82RPeneC6nqCdSwCX2hZUz3JPOvN5at/nTEw/CMf05Smu3Hrpo9Psb7LjN+k+XndNArG1EY8L4+BM3aTM4BCvw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", + "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", "dev": true }, "bytes": { @@ -7170,16 +7917,6 @@ "integrity": "sha512-FXDYw4TjR8wgPZYui2LeTqWh1BLpfQ8lB6upMtlpDF6WlOOxghmTTxWyngdKTgozqBgKnHbTVwTE+hOHqAykuQ==", "dev": true }, - "cmd-shim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", - "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7635,18 +8372,18 @@ } }, "conventional-changelog-core": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.2.2.tgz", - "integrity": "sha512-cssjAKajxaOX5LNAJLB+UOcoWjAIBvXtDMedv/58G+YEmAXMNfC16mmPl0JDOuVJVfIqM0nqQiZ8UCm8IXbE0g==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.2.3.tgz", + "integrity": "sha512-LMMX1JlxPIq/Ez5aYAYS5CpuwbOk6QFp8O4HLAcZxe3vxoCtABkhfjetk8IYdRB9CDQGwJFLR3Dr55Za6XKgUQ==", "dev": true, "requires": { - "conventional-changelog-writer": "^4.0.5", - "conventional-commits-parser": "^3.0.2", + "conventional-changelog-writer": "^4.0.6", + "conventional-commits-parser": "^3.0.3", "dateformat": "^3.0.0", "get-pkg-repo": "^1.0.0", "git-raw-commits": "2.0.0", "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^2.0.2", + "git-semver-tags": "^2.0.3", "lodash": "^4.2.1", "normalize-package-data": "^2.3.5", "q": "^1.5.1", @@ -7722,21 +8459,21 @@ } }, "conventional-changelog-preset-loader": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.1.1.tgz", - "integrity": "sha512-K4avzGMLm5Xw0Ek/6eE3vdOXkqnpf9ydb68XYmCc16cJ99XMMbc2oaNMuPwAsxVK6CC1yA4/I90EhmWNj0Q6HA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.2.0.tgz", + "integrity": "sha512-zXB+5vF7D5Y3Cb/rJfSyCCvFphCVmF8mFqOdncX3BmjZwAtGAPfYrBcT225udilCKvBbHgyzgxqz2GWDB5xShQ==", "dev": true }, "conventional-changelog-writer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.6.tgz", - "integrity": "sha512-ou/sbrplJMM6KQpR5rKFYNVQYesFjN7WpNGdudQSWNi6X+RgyFUcSv871YBYkrUYV9EX8ijMohYVzn9RUb+4ag==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.7.tgz", + "integrity": "sha512-p/wzs9eYaxhFbrmX/mCJNwJuvvHR+j4Fd0SQa2xyAhYed6KBiZ780LvoqUUvsayP4R1DtC27czalGUhKV2oabw==", "dev": true, "requires": { "compare-func": "^1.3.1", "conventional-commits-filter": "^2.0.2", "dateformat": "^3.0.0", - "handlebars": "^4.1.0", + "handlebars": "^4.1.2", "json-stringify-safe": "^5.0.1", "lodash": "^4.2.1", "meow": "^4.0.0", @@ -7937,17 +8674,17 @@ } }, "conventional-recommended-bump": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-4.1.1.tgz", - "integrity": "sha512-JT2vKfSP9kR18RXXf55BRY1O3AHG8FPg5btP3l7LYfcWJsiXI6MCf30DepQ98E8Qhowvgv7a8iev0J1bEDkTFA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-5.0.1.tgz", + "integrity": "sha512-RVdt0elRcCxL90IrNP0fYCpq1uGt2MALko0eyeQ+zQuDVWtMGAy9ng6yYn3kax42lCj9+XBxQ8ZN6S9bdKxDhQ==", "dev": true, "requires": { "concat-stream": "^2.0.0", "conventional-changelog-preset-loader": "^2.1.1", "conventional-commits-filter": "^2.0.2", - "conventional-commits-parser": "^3.0.2", + "conventional-commits-parser": "^3.0.3", "git-raw-commits": "2.0.0", - "git-semver-tags": "^2.0.2", + "git-semver-tags": "^2.0.3", "meow": "^4.0.0", "q": "^1.5.1" }, @@ -8031,9 +8768,9 @@ } }, "readable-stream": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", - "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -9287,6 +10024,12 @@ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, + "env-paths": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", + "dev": true + }, "envinfo": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.3.1.tgz", @@ -11825,13 +12568,13 @@ } }, "git-semver-tags": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-2.0.2.tgz", - "integrity": "sha512-34lMF7Yo1xEmsK2EkbArdoU79umpvm0MfzaDkSNYSJqtM5QLAVTPWgpiXSVI5o/O9EvZPSrP4Zvnec/CqhSd5w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-2.0.3.tgz", + "integrity": "sha512-tj4FD4ww2RX2ae//jSrXZzrocla9db5h0V7ikPl1P/WwoZar9epdUhwR7XHXSgc+ZkNq72BEEerqQuicoEQfzA==", "dev": true, "requires": { "meow": "^4.0.0", - "semver": "^5.5.0" + "semver": "^6.0.0" }, "dependencies": { "load-json-file": { @@ -11900,12 +12643,6 @@ "read-pkg": "^3.0.0" } }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -12015,24 +12752,60 @@ "dev": true }, "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" }, "dependencies": { + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true } } @@ -12536,13 +13309,67 @@ "dev": true }, "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", "dev": true, "requires": { - "pkg-dir": "^2.0.0", + "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "imurmurhash": { @@ -12575,6 +13402,12 @@ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", "dev": true }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -14888,27 +15721,27 @@ "dev": true }, "lerna": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.14.1.tgz", - "integrity": "sha512-lQxmGeEECjOMI3pRh2+I6jazoEWhEfvZNIs7XaX71op33AVwyjlY/nQ1GJGrPhxYBuQnlPgH0vH/nC/lcLaVkw==", + "version": "3.16.4", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.16.4.tgz", + "integrity": "sha512-0HfwXIkqe72lBLZcNO9NMRfylh5Ng1l8tETgYQ260ZdHRbPuaLKE3Wqnd2YYRRkWfwPyEyZO8mZweBR+slVe1A==", "dev": true, "requires": { - "@lerna/add": "3.14.0", - "@lerna/bootstrap": "3.14.0", - "@lerna/changed": "3.14.1", - "@lerna/clean": "3.14.0", + "@lerna/add": "3.16.2", + "@lerna/bootstrap": "3.16.2", + "@lerna/changed": "3.16.4", + "@lerna/clean": "3.16.0", "@lerna/cli": "3.13.0", - "@lerna/create": "3.14.0", - "@lerna/diff": "3.14.0", - "@lerna/exec": "3.14.0", - "@lerna/import": "3.14.0", - "@lerna/init": "3.14.0", - "@lerna/link": "3.14.0", - "@lerna/list": "3.14.0", - "@lerna/publish": "3.14.1", - "@lerna/run": "3.14.0", - "@lerna/version": "3.14.1", - "import-local": "^1.0.0", + "@lerna/create": "3.16.0", + "@lerna/diff": "3.16.0", + "@lerna/exec": "3.16.0", + "@lerna/import": "3.16.0", + "@lerna/init": "3.16.0", + "@lerna/link": "3.16.2", + "@lerna/list": "3.16.0", + "@lerna/publish": "3.16.4", + "@lerna/run": "3.16.0", + "@lerna/version": "3.16.4", + "import-local": "^2.0.0", "npmlog": "^4.1.2" } }, @@ -14928,104 +15761,6 @@ "type-check": "~0.3.2" } }, - "libnpmaccess": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.1.tgz", - "integrity": "sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "get-stream": "^4.0.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.8.0" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "libnpmpublish": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.1.tgz", - "integrity": "sha512-nefbvJd/wY38zdt+b9SHL6171vqBrMtZ56Gsgfd0duEKb/pB8rDT4/ObUQLrHz1tOfht1flt2zM+UGaemzAG5g==", - "dev": true, - "requires": { - "aproba": "^2.0.0", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.0.0", - "lodash.clonedeep": "^4.5.0", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-registry-fetch": "^3.8.0", - "semver": "^5.5.1", - "ssri": "^6.0.1" - }, - "dependencies": { - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - } - } - }, "line-height": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz", @@ -15403,22 +16138,22 @@ "dev": true }, "lodash.template": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", - "integrity": "sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A=", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { - "lodash._reinterpolate": "~3.0.0", + "lodash._reinterpolate": "^3.0.0", "lodash.templatesettings": "^4.0.0" } }, "lodash.templatesettings": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", - "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { - "lodash._reinterpolate": "~3.0.0" + "lodash._reinterpolate": "^3.0.0" } }, "lodash.throttle": { @@ -15731,17 +16466,17 @@ } }, "make-fetch-happen": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", - "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz", + "integrity": "sha512-nFr/vpL1Jc60etMVKeaLOqfGjMMb3tAHFVJWxHOFCFS04Zmd7kGlMxo0l1tzfhoQje0/UPnd0X8OeGUiXXnfPA==", "dev": true, "requires": { "agentkeepalive": "^3.4.1", - "cacache": "^11.0.1", + "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.1", - "lru-cache": "^4.1.2", + "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", "promise-retry": "^1.1.1", @@ -15756,42 +16491,32 @@ "dev": true }, "cacache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", - "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.2.tgz", + "integrity": "sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==", "dev": true, "requires": { - "bluebird": "^3.5.3", + "bluebird": "^3.5.5", "chownr": "^1.1.1", "figgy-pudding": "^3.5.1", - "glob": "^7.1.3", + "glob": "^7.1.4", "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", + "rimraf": "^2.6.3", "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - } } }, "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "glob": { @@ -15809,11 +16534,20 @@ } }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", "dev": true }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -15842,6 +16576,15 @@ "once": "^1.3.1" } }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -17166,6 +17909,15 @@ } } }, + "mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", + "dev": true, + "requires": { + "mkdirp": "*" + } + }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -17241,15 +17993,15 @@ "dev": true }, "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz", + "integrity": "sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==", "dev": true, "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" + "array-differ": "^2.0.3", + "array-union": "^1.0.2", + "arrify": "^1.0.1", + "minimatch": "^3.0.4" } }, "mute-stream": { @@ -17258,6 +18010,17 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nan": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", @@ -17814,14 +18577,14 @@ "dev": true }, "npm-lifecycle": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-2.1.1.tgz", - "integrity": "sha512-+Vg6I60Z75V/09pdcH5iUo/99Q/vop35PaI99elvxk56azSVVsdsSsS/sXqKDNwbRRNN1qSxkcO45ZOu0yOWew==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.2.tgz", + "integrity": "sha512-nhfOcoTHrW1lJJlM2o77vTE2RWR4YOVyj7YzmY0y5itsMjEuoJHteio/ez0BliENEPsNxIUQgwhyEW9dShj3Ww==", "dev": true, "requires": { "byline": "^5.0.0", "graceful-fs": "^4.1.15", - "node-gyp": "^4.0.0", + "node-gyp": "^5.0.2", "resolve-from": "^4.0.0", "slide": "^1.1.6", "uid-number": "0.0.6", @@ -17830,29 +18593,29 @@ }, "dependencies": { "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", "dev": true }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", "dev": true }, "node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-2XiryJ8sICNo6ej8d0idXDEMKfVfFK7kekGCtJAuelGsYHQxhj13KTf95swTCN2dZ/4lTfZ84Fu31jqJEEgjWA==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.0.3.tgz", + "integrity": "sha512-z/JdtkFGUm0QaQUusvloyYuGDub3nUbOo5de1Fz57cM++osBTvQatBUSTlF1k/w8vFHPxxXW6zxGvkxXSpaBkQ==", "dev": true, "requires": { + "env-paths": "^1.0.0", "glob": "^7.0.3", "graceful-fs": "^4.1.2", "mkdirp": "^0.5.0", "nopt": "2 || 3", "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", "request": "^2.87.0", "rimraf": "2", "semver": "~5.3.0", @@ -17873,18 +18636,18 @@ "dev": true }, "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", "dev": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "yallist": { @@ -18000,9 +18763,9 @@ } }, "npm-packlist": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.1.tgz", - "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", "dev": true, "requires": { "ignore-walk": "^3.0.1", @@ -18037,20 +18800,6 @@ } } }, - "npm-registry-fetch": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz", - "integrity": "sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw==", - "dev": true, - "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^4.1.3", - "make-fetch-happen": "^4.0.1", - "npm-package-arg": "^6.1.0" - } - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -18518,194 +19267,6 @@ "p-reduce": "^1.0.0" } }, - "pacote": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.0.tgz", - "integrity": "sha512-aUplXozRbzhaJO48FaaeClmN+2Mwt741MC6M3bevIGZwdCaP7frXzbUOfOWa91FPHoLITzG0hYaKY363lxO3bg==", - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "cacache": "^11.3.2", - "figgy-pudding": "^3.5.1", - "get-stream": "^4.1.0", - "glob": "^7.1.3", - "lru-cache": "^5.1.1", - "make-fetch-happen": "^4.0.1", - "minimatch": "^3.0.4", - "minipass": "^2.3.5", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.12", - "npm-pick-manifest": "^2.2.3", - "npm-registry-fetch": "^3.8.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.1", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.6.0", - "ssri": "^6.0.1", - "tar": "^4.4.8", - "unique-filename": "^1.1.1", - "which": "^1.3.1" - }, - "dependencies": { - "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", - "dev": true - }, - "cacache": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", - "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", - "dev": true, - "requires": { - "bluebird": "^3.5.3", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.3", - "graceful-fs": "^4.1.15", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true - } - } - }, "pako": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz", @@ -21434,16 +21995,14 @@ } }, "read-package-tree": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.2.tgz", - "integrity": "sha512-rW3XWUUkhdKmN2JKB4FL563YAgtINifso5KShykufR03nJ5loGFlkUMe1g/yxmqX073SoYYTsgXu7XdDinKZuA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", + "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", "dev": true, "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "once": "^1.3.0", "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0" + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" } }, "read-pkg": { @@ -21517,9 +22076,9 @@ } }, "readdir-scoped-modules": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz", - "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "dev": true, "requires": { "debuglog": "^1.0.1", @@ -24329,6 +24888,24 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "thread-loader": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-2.1.2.tgz", @@ -25087,6 +25664,15 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util-promisify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", + "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", @@ -25872,32 +26458,45 @@ } }, "write-json-file": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", - "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz", + "integrity": "sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==", "dev": true, "requires": { "detect-indent": "^5.0.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "pify": "^3.0.0", + "graceful-fs": "^4.1.15", + "make-dir": "^2.1.0", + "pify": "^4.0.1", "sort-keys": "^2.0.0", - "write-file-atomic": "^2.0.0" + "write-file-atomic": "^2.4.2" }, "dependencies": { + "graceful-fs": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.1.tgz", + "integrity": "sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==", + "dev": true + }, "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true } } @@ -25910,6 +26509,37 @@ "requires": { "sort-keys": "^2.0.0", "write-json-file": "^2.2.0" + }, + "dependencies": { + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "write-json-file": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", + "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", + "dev": true, + "requires": { + "detect-indent": "^5.0.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "pify": "^3.0.0", + "sort-keys": "^2.0.0", + "write-file-atomic": "^2.0.0" + } + } } }, "ws": { diff --git a/package.json b/package.json index 360c76bff716d9..d6404161ab9a7c 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "jest-junit": "6.4.0", "jest-serializer-enzyme": "1.0.0", "jsdom": "11.12.0", - "lerna": "3.14.1", + "lerna": "3.16.4", "lint-staged": "8.1.5", "lodash": "4.17.14", "make-dir": "3.0.0", From 1ecb543dcf69cc111f0f555967528a3ac08b529e Mon Sep 17 00:00:00 2001 From: Daniel Richards <daniel.richards@automattic.com> Date: Tue, 6 Aug 2019 20:53:49 +0800 Subject: [PATCH 622/664] Fix issue with jest caching of block.json files (#16899) * Generate cache key for block index files by concatenating the index with the block json file * Move implementation to the test/unit folder --- test/unit/jest.config.js | 3 ++ test/unit/scripts/babel-transformer.js | 40 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 test/unit/scripts/babel-transformer.js diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js index 4b1f0974325e99..2ce43ae6f8cc45 100644 --- a/test/unit/jest.config.js +++ b/test/unit/jest.config.js @@ -25,4 +25,7 @@ module.exports = { '<rootDir>/.*/build-module/', '<rootDir>/.+\.native\.js$', ], + transform: { + '^.+\\.[jt]sx?$': '<rootDir>/test/unit/scripts/babel-transformer.js', + }, }; diff --git a/test/unit/scripts/babel-transformer.js b/test/unit/scripts/babel-transformer.js new file mode 100644 index 00000000000000..8c433576cedf64 --- /dev/null +++ b/test/unit/scripts/babel-transformer.js @@ -0,0 +1,40 @@ +const fs = require( 'fs' ); +const babelJest = require( 'babel-jest' ); +const babelJestTransformer = babelJest.createTransformer(); + +module.exports = { + // This transformer extends the babel-jest transformer. + ...babelJestTransformer, + + /** + * A cache key generator that extends babel-jest's cache key generator, + * but adds some additional handling for invalidating the cache when a + * change to a block.json file is made. + * + * @param {string} src The source of the file being transformed. + * @param {string} filename The filename of the file being transformed. + * @param {...any} args Any other args passed to the function. + * + * @return {string} The cache key for the file. + */ + getCacheKey( src, filename, ...args ) { + const isBlockIndex = /block-library[\/\\]src[\/\\].+[\/\\]index\.js/.test( filename ); + + if ( ! isBlockIndex ) { + return babelJestTransformer.getCacheKey( src, filename, ...args ); + } + + const blockJSONFilename = filename.replace( 'index.js', 'block.json' ); + + if ( ! fs.existsSync( blockJSONFilename ) ) { + return babelJestTransformer.getCacheKey( src, filename, ...args ); + } + + // If the file is a block index file and there's a block json file, generate the + // jest cache key for this file by concatenating the block index and block json + // src together. This will result in the cache key changing and the cache being + // invalidated for the index when any changes to the json are made. + const blockJSONSrc = fs.readFileSync( blockJSONFilename ); + return babelJestTransformer.getCacheKey( `${ src }\n${ blockJSONSrc }`, filename, ...args ); + }, +}; From 0b853e85f2bbf0938440d5598dbfa6a68471e252 Mon Sep 17 00:00:00 2001 From: Enrique Piqueras <epiqueras@users.noreply.github.com> Date: Tue, 6 Aug 2019 09:27:18 -0400 Subject: [PATCH 623/664] Core Data: Add support for autosaving entities. (#16903) * Core Data: Make current offset selector private. * Core Data: Add support for autosaving entities. * Core Data: Clarify why and how autosave payload data is gathered, with a comment. --- .../developers/data/data-core.md | 35 ++++---- packages/core-data/README.md | 35 ++++---- packages/core-data/src/actions.js | 88 +++++++++++++++---- packages/core-data/src/reducer.js | 1 + packages/core-data/src/selectors.js | 30 +++++-- 5 files changed, 133 insertions(+), 56 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index ceb9daef907720..bdfbe68438a392 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -71,22 +71,6 @@ _Returns_ - `?Array`: An array of autosaves for the post, or undefined if there is none. -<a name="getCurrentUndoOffset" href="#getCurrentUndoOffset">#</a> **getCurrentUndoOffset** - -Returns the current undo offset for the -entity records edits history. The offset -represents how many items from the end -of the history stack we are at. 0 is the -last edit, -1 is the second last, and so on. - -_Parameters_ - -- _state_ `Object`: State tree. - -_Returns_ - -- `number`: The current undo offset. - <a name="getCurrentUser" href="#getCurrentUser">#</a> **getCurrentUser** Returns the current user. @@ -359,6 +343,21 @@ _Returns_ - `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made. +<a name="isAutosavingEntityRecord" href="#isAutosavingEntityRecord">#</a> **isAutosavingEntityRecord** + +Returns true if the specified entity record is autosaving, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `boolean`: Whether the entity record is autosaving or not. + <a name="isPreviewEmbedFallback" href="#isPreviewEmbedFallback">#</a> **isPreviewEmbedFallback** Determines if the returned preview is an oEmbed link fallback. @@ -403,7 +402,7 @@ _Parameters_ _Returns_ -- `?Object`: Whether the entity record is saving or not. +- `boolean`: Whether the entity record is saving or not. <!-- END TOKEN(Autogenerated selectors) --> @@ -561,6 +560,7 @@ _Parameters_ - _kind_ `string`: Kind of the entity. - _name_ `string`: Name of the entity. - _recordId_ `Object`: ID of the record. +- _options_ `Object`: Saving options. <a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord** @@ -571,6 +571,7 @@ _Parameters_ - _kind_ `string`: Kind of the received entity. - _name_ `string`: Name of the received entity. - _record_ `Object`: Record to be saved. +- _options_ `Object`: Saving options. <a name="undo" href="#undo">#</a> **undo** diff --git a/packages/core-data/README.md b/packages/core-data/README.md index cf9f0a9989b28f..cc89ec095f5117 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -191,6 +191,7 @@ _Parameters_ - _kind_ `string`: Kind of the entity. - _name_ `string`: Name of the entity. - _recordId_ `Object`: ID of the record. +- _options_ `Object`: Saving options. <a name="saveEntityRecord" href="#saveEntityRecord">#</a> **saveEntityRecord** @@ -201,6 +202,7 @@ _Parameters_ - _kind_ `string`: Kind of the received entity. - _name_ `string`: Name of the received entity. - _record_ `Object`: Record to be saved. +- _options_ `Object`: Saving options. <a name="undo" href="#undo">#</a> **undo** @@ -280,22 +282,6 @@ _Returns_ - `?Array`: An array of autosaves for the post, or undefined if there is none. -<a name="getCurrentUndoOffset" href="#getCurrentUndoOffset">#</a> **getCurrentUndoOffset** - -Returns the current undo offset for the -entity records edits history. The offset -represents how many items from the end -of the history stack we are at. 0 is the -last edit, -1 is the second last, and so on. - -_Parameters_ - -- _state_ `Object`: State tree. - -_Returns_ - -- `number`: The current undo offset. - <a name="getCurrentUser" href="#getCurrentUser">#</a> **getCurrentUser** Returns the current user. @@ -568,6 +554,21 @@ _Returns_ - `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made. +<a name="isAutosavingEntityRecord" href="#isAutosavingEntityRecord">#</a> **isAutosavingEntityRecord** + +Returns true if the specified entity record is autosaving, and false otherwise. + +_Parameters_ + +- _state_ `Object`: State tree. +- _kind_ `string`: Entity kind. +- _name_ `string`: Entity name. +- _recordId_ `number`: Record ID. + +_Returns_ + +- `boolean`: Whether the entity record is autosaving or not. + <a name="isPreviewEmbedFallback" href="#isPreviewEmbedFallback">#</a> **isPreviewEmbedFallback** Determines if the returned preview is an oEmbed link fallback. @@ -612,7 +613,7 @@ _Parameters_ _Returns_ -- `?Object`: Whether the entity record is saving or not. +- `boolean`: Whether the entity record is saving or not. <!-- END TOKEN(Autogenerated selectors) --> diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 8e78957bbb6756..43861be226ca5d 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, merge, isEqual, find } from 'lodash'; +import { castArray, get, merge, isEqual, find } from 'lodash'; /** * Internal dependencies @@ -127,7 +127,11 @@ export function receiveEmbedPreview( url, preview ) { * @return {Object} Action object. */ export function* editEntityRecord( kind, name, recordId, edits ) { - const { transientEdits = {}, mergedEdits = {} } = yield select( 'getEntity', kind, name ); + const { transientEdits = {}, mergedEdits = {} } = yield select( + 'getEntity', + kind, + name + ); const record = yield select( 'getEntityRecord', kind, name, recordId ); const editedRecord = yield select( 'getEditedEntityRecord', @@ -143,10 +147,11 @@ export function* editEntityRecord( kind, name, recordId, edits ) { // Clear edits when they are equal to their persisted counterparts // so that the property is not considered dirty. edits: Object.keys( edits ).reduce( ( acc, key ) => { + const recordValue = get( record[ key ], 'raw', record[ key ] ); const value = mergedEdits[ key ] ? - merge( record[ key ], edits[ key ] ) : + merge( recordValue, edits[ key ] ) : edits[ key ]; - acc[ key ] = isEqual( record[ key ], value ) ? undefined : value; + acc[ key ] = isEqual( recordValue, value ) ? undefined : value; return acc; }, {} ), transientEdits, @@ -209,29 +214,77 @@ export function* redo() { * @param {string} kind Kind of the received entity. * @param {string} name Name of the received entity. * @param {Object} record Record to be saved. + * @param {Object} options Saving options. */ -export function* saveEntityRecord( kind, name, record ) { +export function* saveEntityRecord( + kind, + name, + record, + { isAutosave = false } = { isAutosave: false } +) { const entities = yield getKindEntities( kind ); const entity = find( entities, { kind, name } ); if ( ! entity ) { return; } - const key = entity.key || DEFAULT_ENTITY_KEY; - const recordId = record[ key ]; + const entityIdKey = entity.key || DEFAULT_ENTITY_KEY; + const recordId = record[ entityIdKey ]; - yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId }; + yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId, isAutosave }; let error; try { - const updatedRecord = yield apiFetch( { - path: `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`, - method: recordId ? 'PUT' : 'POST', - data: record, - } ); - yield receiveEntityRecords( kind, name, updatedRecord, undefined, true ); + const path = `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`; + if ( isAutosave ) { + const persistedRecord = yield select( + 'getEntityRecord', + kind, + name, + recordId + ); + const currentUser = yield select( 'getCurrentUser' ); + const currentUserId = currentUser ? currentUser.id : undefined; + const autosavePost = yield select( + 'getAutosave', + persistedRecord.type, + persistedRecord.id, + currentUserId + ); + // Autosaves need all expected fields to be present. + // So we fallback to the previous autosave and then + // to the actual persisted entity if the edits don't + // have a value. + let data = { ...persistedRecord, ...autosavePost, ...record }; + data = Object.keys( data ).reduce( ( acc, key ) => { + if ( key in [ 'title', 'excerpt', 'content' ] ) { + acc[ key ] = get( data[ key ], 'raw', data[ key ] ); + } + return acc; + }, {} ); + const autosave = yield apiFetch( { + path: `${ path }/autosaves`, + method: 'POST', + data, + } ); + yield receiveAutosaves( persistedRecord.id, autosave ); + } else { + const updatedRecord = yield apiFetch( { + path, + method: recordId ? 'PUT' : 'POST', + data: record, + } ); + yield receiveEntityRecords( kind, name, updatedRecord, undefined, true ); + } } catch ( _error ) { error = _error; } - yield { type: 'SAVE_ENTITY_RECORD_FINISH', kind, name, recordId, error }; + yield { + type: 'SAVE_ENTITY_RECORD_FINISH', + kind, + name, + recordId, + error, + isAutosave, + }; } /** @@ -240,8 +293,9 @@ export function* saveEntityRecord( kind, name, record ) { * @param {string} kind Kind of the entity. * @param {string} name Name of the entity. * @param {Object} recordId ID of the record. + * @param {Object} options Saving options. */ -export function* saveEditedEntityRecord( kind, name, recordId ) { +export function* saveEditedEntityRecord( kind, name, recordId, options ) { if ( ! ( yield select( 'hasEditsForEntityRecord', kind, name, recordId ) ) ) { return; } @@ -252,7 +306,7 @@ export function* saveEditedEntityRecord( kind, name, recordId ) { recordId ); const record = { id: recordId, ...edits }; - yield* saveEntityRecord( kind, name, record ); + yield* saveEntityRecord( kind, name, record, options ); } /** diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 96ed12e5ef78c7..7f0972e19e481b 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -213,6 +213,7 @@ function entity( entityConfig ) { [ action.recordId ]: { pending: action.type === 'SAVE_ENTITY_RECORD_START', error: action.error, + isAutosave: action.isAutosave, }, }; } diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index e0ac2aa856ae1c..c8aec286a1107c 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -207,6 +207,25 @@ export const getEditedEntityRecord = createSelector( ( state ) => [ state.entities.data ] ); +/** + * Returns true if the specified entity record is autosaving, and false otherwise. + * + * @param {Object} state State tree. + * @param {string} kind Entity kind. + * @param {string} name Entity name. + * @param {number} recordId Record ID. + * + * @return {boolean} Whether the entity record is autosaving or not. + */ +export function isAutosavingEntityRecord( state, kind, name, recordId ) { + const { pending, isAutosave } = get( + state.entities.data, + [ kind, name, 'saving', recordId ], + {} + ); + return Boolean( pending && isAutosave ); +} + /** * Returns true if the specified entity record is saving, and false otherwise. * @@ -215,14 +234,15 @@ export const getEditedEntityRecord = createSelector( * @param {string} name Entity name. * @param {number} recordId Record ID. * - * @return {Object?} Whether the entity record is saving or not. + * @return {boolean} Whether the entity record is saving or not. */ export function isSavingEntityRecord( state, kind, name, recordId ) { - return get( + const { pending, isAutosave } = get( state.entities.data, - [ kind, name, 'saving', recordId, 'pending' ], - false + [ kind, name, 'saving', recordId ], + {} ); + return Boolean( pending && ! isAutosave ); } /** @@ -250,7 +270,7 @@ export function getLastEntitySaveError( state, kind, name, recordId ) { * * @return {number} The current undo offset. */ -export function getCurrentUndoOffset( state ) { +function getCurrentUndoOffset( state ) { return state.undo.offset; } From c148164001ef981c4b903672cb5449da30737a10 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Tue, 6 Aug 2019 17:15:04 +0100 Subject: [PATCH 624/664] Fix: Alt text written on images while the image was temporary is deleted when the upload finishes. (#16051) --- packages/block-library/src/image/edit.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 0a51b05d50c73d..f385b291a900a1 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -8,6 +8,7 @@ import { isEmpty, map, last, + omit, pick, } from 'lodash'; @@ -295,7 +296,6 @@ export class ImageEdit extends Component { attributes, mediaUpload, noticeOperations, - setAttributes, } = this.props; const { id, url = '' } = attributes; @@ -306,7 +306,7 @@ export class ImageEdit extends Component { mediaUpload( { filesList: [ file ], onFileChange: ( [ image ] ) => { - setAttributes( pickRelevantMediaFiles( image ) ); + this.onSelectImage( image ); }, allowedTypes: ALLOWED_MEDIA_TYPES, onError: ( message ) => { @@ -357,7 +357,21 @@ export class ImageEdit extends Component { isEditing: false, } ); - const { id, url } = this.props.attributes; + const { id, url, alt, caption } = this.props.attributes; + + let mediaAttributes = pickRelevantMediaFiles( media ); + + // If the current image is temporary but an alt or caption text was meanwhile written by the user, + // make sure the text is not overwritten. + if ( isTemporaryImage( id, url ) ) { + if ( alt ) { + mediaAttributes = omit( mediaAttributes, [ 'alt' ] ); + } + if ( caption ) { + mediaAttributes = omit( mediaAttributes, [ 'caption' ] ); + } + } + let additionalAttributes; // Reset the dimension attributes if changing to a different image. if ( ! media.id || media.id !== id ) { @@ -372,7 +386,7 @@ export class ImageEdit extends Component { } this.props.setAttributes( { - ...pickRelevantMediaFiles( media ), + ...mediaAttributes, ...additionalAttributes, } ); } From 005e0302eba30a24cb4fe64e42cf909a58d4b60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Wed, 7 Aug 2019 08:08:48 +0200 Subject: [PATCH 625/664] Upgrade Puppeteer (#16875) * Update demo test to make it work with Vimeo mock * Add e2e test * Update selection on mouseup/keyup/touchend * Try to fix e2e tests * Attempt to upgrade Puppeteer * Fix package-lock.json file * Fix preview e2e * Update snapshot * Stabilise block hierarchy tests * Remove new e2e test * Simplify loop * Remove puppeteer from devDependencies * Remove cancelAnimationFrame * Add e2e test for #16857 * Restore animation frame fix --- package-lock.json | 265 ++++++++++++------ packages/e2e-test-utils/README.md | 4 - packages/e2e-test-utils/package.json | 2 +- .../e2e-test-utils/src/press-key-times.js | 26 +- packages/e2e-tests/package.json | 4 +- .../plugins/deprecated-node-matcher/index.js | 2 - .../__snapshots__/rich-text.test.js.snap | 12 + .../specs/block-hierarchy-navigation.test.js | 3 +- packages/e2e-tests/specs/demo.test.js | 19 +- .../__snapshots__/plugins-api.test.js.snap | 6 +- packages/e2e-tests/specs/preview.test.js | 3 +- packages/e2e-tests/specs/rich-text.test.js | 29 ++ packages/e2e-tests/specs/writing-flow.test.js | 1 - packages/jest-puppeteer-axe/package.json | 2 +- packages/rich-text/src/component/index.js | 20 +- packages/scripts/package.json | 4 +- 16 files changed, 257 insertions(+), 145 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83fca7b32a5d4c..556ca1bb192725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4705,7 +4705,7 @@ "@wordpress/jest-console": "file:packages/jest-console", "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", "@wordpress/scripts": "file:packages/scripts", - "expect-puppeteer": "^4.0.0", + "expect-puppeteer": "^4.3.0", "lodash": "^4.17.14" } }, @@ -5015,10 +5015,10 @@ "cross-spawn": "^5.1.0", "eslint": "^5.16.0", "jest": "^24.7.1", - "jest-puppeteer": "^4.0.0", + "jest-puppeteer": "^4.3.0", "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", - "puppeteer": "1.6.1", + "puppeteer": "1.19.0", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", "source-map-loader": "^0.2.4", @@ -10916,9 +10916,9 @@ } }, "expect-puppeteer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.0.0.tgz", - "integrity": "sha512-K7D6WRUWR0CtQLqf9ZMBQNkpbhQzxWkKEt9/PnVl+aWMF0xcdV7JW9AClxNprzXkrUq/ILws3H2u6dWcEEjRSQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.3.0.tgz", + "integrity": "sha512-p8N/KSVPG9PAOJlftK5f1n3JrULJ6Qq1EQ8r/n9xzkX2NmXbK8PcnJnkSAEzEHrMycELKGnlJV7M5nkgm+wEWA==", "dev": true }, "express": { @@ -11474,15 +11474,14 @@ } }, "find-process": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.2.1.tgz", - "integrity": "sha512-z4RXYStNAcoi4+smpKbzJXbMT8DdvwqTE7wL7DWZMD0SkTRfQ49z9S7YaK24kuRseKr23YSZlnyL/TaJZtgM1g==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.2.tgz", + "integrity": "sha512-O83EVJr4dWvHJ7QpUzANNAMeQVKukRzRqRx4AIzdLYRrQorRdbqDwLPigkd9PYPhJRhmNPAoVjOm9bcwSmcZaw==", "dev": true, "requires": { "chalk": "^2.0.1", "commander": "^2.11.0", - "debug": "^2.6.8", - "lodash": "^4.17.11" + "debug": "^2.6.8" }, "dependencies": { "debug": { @@ -11493,12 +11492,6 @@ "requires": { "ms": "2.0.0" } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true } } }, @@ -14600,32 +14593,20 @@ } }, "jest-dev-server": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.0.0.tgz", - "integrity": "sha512-tq3fHPM8BDbu/71yIxgGgZW62s1Em6rLNDce0/ff/4No093OyjUEPM8yIUaoBt4pxwwRGkaS1EZB5PzCmRLGkg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jest-dev-server/-/jest-dev-server-4.3.0.tgz", + "integrity": "sha512-bC9flKY2G1honQ/UI0gEhb0wFnDhpFr7xidC8Nk+evi7TgnNtfsGIzzF2dcIhF1G9BGF0n/M7CJrMAzwQhyTPA==", "dev": true, "requires": { "chalk": "^2.4.2", "cwd": "^0.10.0", - "find-process": "^1.2.1", - "inquirer": "^6.2.2", + "find-process": "^1.4.2", + "prompts": "^2.1.0", "spawnd": "^4.0.0", "tree-kill": "^1.2.1", - "wait-port": "^0.2.2" + "wait-on": "^3.3.0" }, "dependencies": { - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -14637,43 +14618,14 @@ "supports-color": "^5.3.0" } }, - "inquirer": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", - "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", - "dev": true, - "requires": { - "ansi-escapes": "^3.2.0", - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.11", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - } - }, - "rxjs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", - "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "prompts": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", + "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", "dev": true, "requires": { - "ansi-regex": "^4.0.0" + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" } }, "tree-kill": { @@ -14754,14 +14706,14 @@ } }, "jest-environment-puppeteer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.0.0.tgz", - "integrity": "sha512-IdbfZW1TjT1lmdPlvlHi4S+CAHuGk63fzGUpQAUeadm77saSJISDMuYS5b7kZ6lXZtc4myu53TkMWDYhh60XOA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-puppeteer/-/jest-environment-puppeteer-4.3.0.tgz", + "integrity": "sha512-ZighMsU39bnacn2ylyHb88CB+ldgCfXGD3lS78k4PEo8A8xyt6+2mxmSR62FH3Y7K+W2gPDu5+QM3/LZuq42fQ==", "dev": true, "requires": { "chalk": "^2.4.2", "cwd": "^0.10.0", - "jest-dev-server": "^4.0.0", + "jest-dev-server": "^4.3.0", "merge-deep": "^3.0.2" }, "dependencies": { @@ -14909,13 +14861,13 @@ "dev": true }, "jest-puppeteer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.0.0.tgz", - "integrity": "sha512-B/5BlePsYmxTVmWD0E3zEuYfrTPk66EZskRlZhdDuXLoUAWPRu2+J/egnXh5GO+pUqkELffn5gfPulpn21rY7A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jest-puppeteer/-/jest-puppeteer-4.3.0.tgz", + "integrity": "sha512-WXhaWlbQl01xadZyNmdZntrtIr8uWUmgjPogDih7dOnr3G/xRr3A034SCqdjwV6fE0tqz7c5hwO8oBTyGZPRgA==", "dev": true, "requires": { - "expect-puppeteer": "^4.0.0", - "jest-environment-puppeteer": "^4.0.0" + "expect-puppeteer": "^4.3.0", + "jest-environment-puppeteer": "^4.3.0" } }, "jest-regex-util": { @@ -21122,25 +21074,40 @@ "dev": true }, "puppeteer": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.6.1.tgz", - "integrity": "sha512-qz6DLwK+PhlBMjJZOMOsgVCnweYLtmiqnmJYUDPT++ElMz+cQgbsCNKPw4YDVpg3RTbsRX/pqQqr20zrp0cuKw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.19.0.tgz", + "integrity": "sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw==", "dev": true, "requires": { - "debug": "^3.1.0", + "debug": "^4.1.0", "extract-zip": "^1.6.6", "https-proxy-agent": "^2.2.1", "mime": "^2.0.3", - "progress": "^2.0.0", + "progress": "^2.0.1", "proxy-from-env": "^1.0.0", "rimraf": "^2.6.1", - "ws": "^5.1.1" + "ws": "^6.1.0" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { "async-limiter": "~1.0.0" @@ -25813,6 +25780,128 @@ "browser-process-hrtime": "^0.1.2" } }, + "wait-on": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-3.3.0.tgz", + "integrity": "sha512-97dEuUapx4+Y12aknWZn7D25kkjMk16PbWoYzpSdA8bYpVfS6hpl2a2pOWZ3c+Tyt3/i4/pglyZctG3J4V1hWQ==", + "dev": true, + "requires": { + "@hapi/joi": "^15.0.3", + "core-js": "^2.6.5", + "minimist": "^1.2.0", + "request": "^2.88.0", + "rx": "^4.1.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "core-js": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", + "dev": true + } + } + }, "wait-port": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.2.tgz", diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 71c4380f8a9387..c874edd521d564 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -353,10 +353,6 @@ _Parameters_ - _key_ `string`: Key to press. - _count_ `number`: Number of times to press. -_Returns_ - -- `Promise`: Promise resolving when key presses complete. - <a name="pressKeyWithModifier" href="#pressKeyWithModifier">#</a> **pressKeyWithModifier** Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index f773eb99a82750..3ae0f0c26ee764 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -36,7 +36,7 @@ }, "peerDependencies": { "jest": ">=24", - "puppeteer": ">=1.6" + "puppeteer": ">=1.19.0" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-test-utils/src/press-key-times.js b/packages/e2e-test-utils/src/press-key-times.js index f78b66d09dede4..6930e6021894d4 100644 --- a/packages/e2e-test-utils/src/press-key-times.js +++ b/packages/e2e-test-utils/src/press-key-times.js @@ -1,31 +1,11 @@ -/** - * External dependencies - */ -import { times } from 'lodash'; - -/** - * Given an array of functions, each returning a promise, performs all - * promises in sequence (waterfall) order. - * - * @param {Function[]} sequence Array of promise creators. - * - * @return {Promise} Promise resolving once all in the sequence complete. - */ -async function promiseSequence( sequence ) { - return sequence.reduce( - ( current, next ) => current.then( next ), - Promise.resolve() - ); -} - /** * Presses the given keyboard key a number of times in sequence. * * @param {string} key Key to press. * @param {number} count Number of times to press. - * - * @return {Promise} Promise resolving when key presses complete. */ export async function pressKeyTimes( key, count ) { - return promiseSequence( times( count, () => () => page.keyboard.press( key ) ) ); + while ( count-- ) { + await page.keyboard.press( key ); + } } diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 17ec9ba8188c88..b74cc44e18fa1a 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -26,12 +26,12 @@ "@wordpress/jest-console": "file:../jest-console", "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", "@wordpress/scripts": "file:../scripts", - "expect-puppeteer": "^4.0.0", + "expect-puppeteer": "^4.3.0", "lodash": "^4.17.14" }, "peerDependencies": { "jest": ">=24", - "puppeteer": ">=1.6" + "puppeteer": ">=1.19.0" }, "publishConfig": { "access": "public" diff --git a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js index 5eba1b8ec67128..0cb60aeff9a614 100644 --- a/packages/e2e-tests/plugins/deprecated-node-matcher/index.js +++ b/packages/e2e-tests/plugins/deprecated-node-matcher/index.js @@ -2,7 +2,6 @@ var registerBlockType = wp.blocks.registerBlockType; var RichText = wp.blockEditor.RichText; var el = wp.element.createElement; - var el = wp.element.createElement; registerBlockType( 'core/deprecated-children-matcher', { title: 'Deprecated Children Matcher', @@ -82,4 +81,3 @@ }, } ); } )(); - diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap index 3c7044b5fb8053..c2013529d0a3f8 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -30,6 +30,12 @@ exports[`RichText should handle change in tag name gracefully 1`] = ` <!-- /wp:heading -->" `; +exports[`RichText should keep internal selection after blur 1`] = ` +"<!-- wp:paragraph --> +<p>1<strong>2</strong></p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should not format text after code backtick 1`] = ` "<!-- wp:paragraph --> <p>A <code>backtick</code> and more.</p> @@ -65,3 +71,9 @@ exports[`RichText should transform backtick to code 2`] = ` <p>A \`backtick\`</p> <!-- /wp:paragraph -->" `; + +exports[`RichText should update internal selection after fresh focus 1`] = ` +"<!-- wp:paragraph --> +<p>1<strong>2</strong></p> +<!-- /wp:paragraph -->" +`; diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js index 378180b58cd824..e54fffb8cd8930 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/block-hierarchy-navigation.test.js @@ -11,6 +11,7 @@ import { async function openBlockNavigator() { await pressKeyWithModifier( 'access', 'o' ); + await page.waitForSelector( '.block-editor-block-navigation__item-button.is-selected' ); } describe( 'Navigating the block hierarchy', () => { @@ -90,6 +91,7 @@ describe( 'Navigating the block hierarchy', () => { await openBlockNavigator(); await pressKeyTimes( 'Tab', 4 ); await page.keyboard.press( 'Enter' ); + await page.waitForSelector( '.is-selected[data-type="core/column"]' ); // Insert text in the last column block await pressKeyTimes( 'Tab', 3 ); // Tab to inserter. @@ -116,7 +118,6 @@ describe( 'Navigating the block hierarchy', () => { // Return to first block. await openBlockNavigator(); - await page.waitForSelector( '.editor-block-navigation__container' ); await page.keyboard.press( 'Space' ); // Replace its content. diff --git a/packages/e2e-tests/specs/demo.test.js b/packages/e2e-tests/specs/demo.test.js index 73fc77966cf6de..f5181a8a0e9683 100644 --- a/packages/e2e-tests/specs/demo.test.js +++ b/packages/e2e-tests/specs/demo.test.js @@ -1,7 +1,12 @@ /** * WordPress dependencies */ -import { visitAdminPage, createURLMatcher, setUpResponseMocking, mockOrTransform } from '@wordpress/e2e-test-utils'; +import { + createEmbeddingMatcher, + createJSONResponse, + setUpResponseMocking, + visitAdminPage, +} from '@wordpress/e2e-test-utils'; const MOCK_VIMEO_RESPONSE = { url: 'https://vimeo.com/22439234', @@ -12,20 +17,12 @@ const MOCK_VIMEO_RESPONSE = { version: '1.0', }; -const couldNotBePreviewed = ( embedObject ) => { - return 'Embed Handler' === embedObject.provider_name; -}; - -const stripIframeFromEmbed = ( embedObject ) => { - return { ...embedObject, html: embedObject.html.replace( /src=[^\s]+/, '' ) }; -}; - describe( 'new editor state', () => { beforeAll( async () => { await setUpResponseMocking( [ { - match: createURLMatcher( 'oembed%2F1.0%2Fproxy' ), - onRequestMatch: mockOrTransform( couldNotBePreviewed, MOCK_VIMEO_RESPONSE, stripIframeFromEmbed ), + match: createEmbeddingMatcher( 'https://vimeo.com/22439234' ), + onRequestMatch: createJSONResponse( MOCK_VIMEO_RESPONSE ), }, ] ); await visitAdminPage( 'post-new.php', 'gutenberg-demo' ); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap index 0d8533597b0411..2faff688fbd77a 100644 --- a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap +++ b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap @@ -1,9 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = ` -" -My Custom Panel -" -`; +exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`; exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-toggled\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-star-filled\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; diff --git a/packages/e2e-tests/specs/preview.test.js b/packages/e2e-tests/specs/preview.test.js index f7ad7d830bf452..8b9a9f484e0ff4 100644 --- a/packages/e2e-tests/specs/preview.test.js +++ b/packages/e2e-tests/specs/preview.test.js @@ -45,9 +45,8 @@ async function openPreviewPage( editorPage ) { * @return {Promise} Promise resolving once navigation completes. */ async function waitForPreviewNavigation( previewPage ) { - const navigationCompleted = previewPage.waitForNavigation(); await page.click( '.editor-post-preview' ); - return navigationCompleted; + return previewPage.waitForNavigation(); } /** diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index a7acef1d2aeaf0..d9b674b7940e06 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -216,4 +216,33 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should update internal selection after fresh focus', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Tab' ); + await pressKeyWithModifier( 'shift', 'Tab' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'b' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should keep internal selection after blur', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + // Simulate moving focus to a different app, then moving focus back, + // without selection being changed. + await page.evaluate( () => { + const activeElement = document.activeElement; + activeElement.blur(); + activeElement.focus(); + } ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'b' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index 3c8c4b39e1b36b..f96b1f1602e661 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -316,7 +316,6 @@ describe( 'Writing Flow', () => { it( 'should navigate empty paragraph', async () => { await clickBlockAppender(); await page.keyboard.press( 'Enter' ); - await page.waitForFunction( () => document.activeElement.isContentEditable ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.type( '1' ); await page.keyboard.press( 'ArrowRight' ); diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index eaf4025995ad47..18b43faa6a967e 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -34,7 +34,7 @@ }, "peerDependencies": { "jest": ">=24", - "puppeteer": ">=1.6" + "puppeteer": ">=1.19.0" }, "publishConfig": { "access": "public" diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 513cb480279049..1689efc01056d2 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -288,7 +288,7 @@ class RichText extends Component { this.recalculateBoundaryStyle(); // We know for certain that on focus, the old selection is invalid. It - // will be recalculated on the next animation frame. + // will be recalculated on the next mouseup, keyup, or touchend event. const index = undefined; const activeFormats = undefined; @@ -392,8 +392,17 @@ class RichText extends Component { /** * Handles the `selectionchange` event: sync the selection to local state. + * + * @param {Event} event `selectionchange` event. */ - onSelectionChange() { + onSelectionChange( event ) { + if ( + event.type !== 'selectionchange' && + ! this.props.__unstableIsSelected + ) { + return; + } + const { start, end } = this.createRecord(); const value = this.getRecord(); @@ -911,6 +920,13 @@ class RichText extends Component { onMouseDown={ this.onPointerDown } onTouchStart={ this.onPointerDown } setRef={ this.setRef } + // Selection updates must be done at these events as they + // happen before the `selectionchange` event. In some cases, + // the `selectionchange` event may not even fire, for + // example when the window receives focus again on click. + onKeyUp={ this.onSelectionChange } + onMouseUp={ this.onSelectionChange } + onTouchEnd={ this.onSelectionChange } /> { isSelected && <FormatEdit allowedFormats={ allowedFormats } diff --git a/packages/scripts/package.json b/packages/scripts/package.json index b1ab422f0af068..8880595864e34d 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -44,10 +44,10 @@ "cross-spawn": "^5.1.0", "eslint": "^5.16.0", "jest": "^24.7.1", - "jest-puppeteer": "^4.0.0", + "jest-puppeteer": "^4.3.0", "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", - "puppeteer": "1.6.1", + "puppeteer": "1.19.0", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", "source-map-loader": "^0.2.4", From a2dea5d31409e4fc1379ab326536ae12dbc7aded Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Wed, 7 Aug 2019 08:28:40 +0100 Subject: [PATCH 626/664] Fix some problems in enableExperimentalFeatures e2e function (#16930) Make enableExperimentalFeatures wait for the request to complete and don't disable already enabled features. --- .../src/enable-experimental-features.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/e2e-test-utils/src/enable-experimental-features.js b/packages/e2e-test-utils/src/enable-experimental-features.js index fcb47843b54b9f..be74f20f82fd92 100644 --- a/packages/e2e-test-utils/src/enable-experimental-features.js +++ b/packages/e2e-test-utils/src/enable-experimental-features.js @@ -21,7 +21,14 @@ export async function enableExperimentalFeatures( features ) { await Promise.all( features.map( async ( feature ) => { await page.waitForSelector( feature ); - await page.click( feature ); - await page.click( '#submit' ); + const checkedSelector = `${ feature }[checked=checked]`; + const isChecked = !! ( await page.$( checkedSelector ) ); + if ( ! isChecked ) { + await page.click( feature ); + } } ) ); + await Promise.all( [ + page.waitForNavigation( { waitUntil: 'networkidle0' } ), + page.click( '#submit' ), + ] ); } From 766476a4a1cfe8f7e17d92d46e95cd4db2d2bfe0 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Wed, 7 Aug 2019 03:53:16 -0400 Subject: [PATCH 627/664] Add eslint-plugin-jsdoc for better JSDoc linting (#16869) * feat(eslint-plugin): add eslint-plugin-jsdoc for improved jsdoc linting * chore: fix all JSDoc eslint errors/warnings across the entire repository --- package-lock.json | 87 +++++++++++++++++++ packages/eslint-plugin/CHANGELOG.md | 2 + packages/eslint-plugin/configs/es5.js | 24 ----- packages/eslint-plugin/configs/jsdoc.js | 29 +++++++ packages/eslint-plugin/configs/recommended.js | 1 + packages/eslint-plugin/package.json | 2 + 6 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 packages/eslint-plugin/configs/jsdoc.js diff --git a/package-lock.json b/package-lock.json index 556ca1bb192725..dd6154b74c2de3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4805,10 +4805,29 @@ "dev": true, "requires": { "babel-eslint": "^10.0.1", + "eslint-plugin-jsdoc": "^15.8.0", "eslint-plugin-jsx-a11y": "^6.2.1", "eslint-plugin-react": "^7.12.4", "eslint-plugin-react-hooks": "^1.6.0", + "globals": "^12.0.0", "requireindex": "^1.2.0" + }, + "dependencies": { + "globals": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.0.0.tgz", + "integrity": "sha512-c9xoi32iDwlETiyYfO0pd3M8GcEuytJinSoqq7k3fz4H8p2p31NyfKr7JVd7Y0QvmtWcWXcwqW4L33eeDYgh1A==", + "dev": true, + "requires": { + "type-fest": "^0.6.0" + } + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, "@wordpress/format-library": { @@ -8056,6 +8075,12 @@ "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", "dev": true }, + "comment-parser": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.6.1.tgz", + "integrity": "sha512-Putzd7Ilyvknmb1KxGf5el9uw0sPx9gEVnDrm8tlvXGN1i8Uaa2VBxB32hUhfzTlrEhhxNQ+pKq4ZNe8wNxjmw==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -10666,6 +10691,44 @@ "integrity": "sha512-4fxfe2RcqzU+IVNQL5n4pqibLcUhKKxihYsA510+6kC/FTdGInszDDHgO4ntBzPWu8mcHAvKJLs8o3AQw6eHTg==", "dev": true }, + "eslint-plugin-jsdoc": { + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-15.8.0.tgz", + "integrity": "sha512-J6ozWkaAgBh1eLdQE+C2wcXhoEgDmGJOSB6zMF5ktEtMBnU62xT3wfHcUacuTnv6rt+ollC0uZThaEpGA+sTNg==", + "dev": true, + "requires": { + "comment-parser": "^0.6.1", + "debug": "^4.1.1", + "flat-map-polyfill": "^0.3.8", + "jsdoctypeparser": "5.0.1", + "lodash": "^4.17.15", + "object.entries-ponyfill": "^1.0.1", + "regextras": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "eslint-plugin-jsx-a11y": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", @@ -11527,6 +11590,12 @@ } } }, + "flat-map-polyfill": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/flat-map-polyfill/-/flat-map-polyfill-0.3.8.tgz", + "integrity": "sha512-ZfmD5MnU7GglUEhiky9C7yEPaNq1/wh36RDohe+Xr3nJVdccwHbdTkFIYvetcdsoAckUKT51fuf44g7Ni5Doyg==", + "dev": true + }, "flatted": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", @@ -15460,6 +15529,12 @@ "integrity": "sha512-wkjURqwaB1daNkDi2OYYbsLnIdC/lUM2nPXQKRs5pqEU9chDg435bjvo+LSaHotDENygHQDHe+ntUkkw2gwMtg==", "dev": true }, + "jsdoctypeparser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-5.0.1.tgz", + "integrity": "sha512-dYwcK6TKzvq+ZKtbp4sbQSW9JMo6s+4YFfUs5D/K7bZsn3s1NhEhZ+jmIPzby0HbkbECBe+hNPEa6a+E21o94w==", + "dev": true + }, "jsdom": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", @@ -18916,6 +18991,12 @@ "has": "^1.0.1" } }, + "object.entries-ponyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.entries-ponyfill/-/object.entries-ponyfill-1.0.1.tgz", + "integrity": "sha1-Kavfd8v70mVm3RqiTp2I9lQz0lY=", + "dev": true + }, "object.fromentries": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", @@ -22368,6 +22449,12 @@ "unicode-match-property-value-ecmascript": "^1.1.0" } }, + "regextras": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.6.1.tgz", + "integrity": "sha512-EzIHww9xV2Kpqx+corS/I7OBmf2rZ0pKKJPsw5Dc+l6Zq1TslDmtRIP9maVn3UH+72MIXmn8zzDgP07ihQogUA==", + "dev": true + }, "regjsgen": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index b8693b5eef709f..836394fc27ae0f 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,7 @@ ### Breaking Changes - The [`@wordpress/no-unused-vars-before-return` rule](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) has been improved to exempt object destructuring only if destructuring to more than one property. +- Stricter JSDoc linting using [`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc). ### New Features @@ -17,6 +18,7 @@ ### Improvements - The recommended `react` configuration specifies an option to [`@wordpress/no-unused-vars-before-return`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) to exempt React hooks usage, by convention of hooks beginning with "use" prefix. +- The plugin now uses [`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc), rather than the `valid-jsdoc` rule, for more reliable linting of JSDoc blocks. ## 2.3.0 (2019-06-12) diff --git a/packages/eslint-plugin/configs/es5.js b/packages/eslint-plugin/configs/es5.js index a13168d1223e07..c81f5c2249f9d0 100644 --- a/packages/eslint-plugin/configs/es5.js +++ b/packages/eslint-plugin/configs/es5.js @@ -73,30 +73,6 @@ module.exports = { '!': true, }, } ], - 'valid-jsdoc': [ 'error', { - prefer: { - arg: 'param', - argument: 'param', - extends: 'augments', - returns: 'return', - }, - preferType: { - array: 'Array', - bool: 'boolean', - Boolean: 'boolean', - float: 'number', - Float: 'number', - int: 'number', - integer: 'number', - Integer: 'number', - Number: 'number', - object: 'Object', - String: 'string', - Void: 'void', - }, - requireParamDescription: false, - requireReturn: false, - } ], 'valid-typeof': 'error', 'vars-on-top': 'error', 'wrap-iife': 'error', diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js new file mode 100644 index 00000000000000..2fda492cd445dc --- /dev/null +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -0,0 +1,29 @@ +const globals = require( 'globals' ); + +module.exports = { + extends: [ + 'plugin:jsdoc/recommended', + ], + settings: { + jsdoc: { + preferredTypes: { + object: 'Object', + }, + tagNamePreference: { + returns: 'return', + yields: 'yield', + }, + }, + }, + rules: { + 'jsdoc/no-undefined-types': [ 'warning', { + // Required to reference browser types because we don't have the `browser` environment enabled for the project. + // Here we filter out all browser globals that don't begin with an uppercase letter because those + // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). + definedTypes: Object.keys( globals.browser ).filter( ( k ) => /^[A-Z]/.test( k ) ), + } ], + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-returns': 'off', + }, +}; diff --git a/packages/eslint-plugin/configs/recommended.js b/packages/eslint-plugin/configs/recommended.js index f7c264684ecaf9..96a64022379413 100644 --- a/packages/eslint-plugin/configs/recommended.js +++ b/packages/eslint-plugin/configs/recommended.js @@ -5,6 +5,7 @@ module.exports = { require.resolve( './custom.js' ), require.resolve( './react.js' ), require.resolve( './esnext.js' ), + require.resolve( './jsdoc.js' ), ], env: { node: true, diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index b8c4bc4b485129..a8139e218160eb 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -25,9 +25,11 @@ "main": "index.js", "dependencies": { "babel-eslint": "^10.0.1", + "eslint-plugin-jsdoc": "^15.8.0", "eslint-plugin-jsx-a11y": "^6.2.1", "eslint-plugin-react": "^7.12.4", "eslint-plugin-react-hooks": "^1.6.0", + "globals": "^12.0.0", "requireindex": "^1.2.0" }, "publishConfig": { From 612d8bcd4c31d6736d0a74cffcc6aa5b4b5c25ac Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Wed, 7 Aug 2019 05:00:37 -0400 Subject: [PATCH 628/664] Notice: Update dismiss button to match other Gutenberg UI (#16926) * Update hover/active/focus color to dark gray. * Update close button color to be darker. This matches our other close buttons throughout the interface. * Use no-alt instead of no dashicon. --- packages/components/src/notice/index.js | 2 +- packages/components/src/notice/style.scss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/src/notice/index.js b/packages/components/src/notice/index.js index 99d5f9968f1111..4f55ead17a1a34 100644 --- a/packages/components/src/notice/index.js +++ b/packages/components/src/notice/index.js @@ -69,7 +69,7 @@ function Notice( { { isDismissible && ( <IconButton className="components-notice__dismiss" - icon="no" + icon="no-alt" label={ __( 'Dismiss this notice' ) } onClick={ onRemove } tooltip={ false } diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index ad96505a1dae3b..3eff8da877313f 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -45,12 +45,12 @@ position: absolute; top: 0; right: 0; - color: $dark-gray-300; + color: $dark-gray-500; &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover, &:not(:disabled):not([aria-disabled="true"]):not(.is-default):active, &:not(:disabled):not([aria-disabled="true"]):focus { - color: $alert-red; + color: $dark-gray-900; background-color: transparent; } From 496bae56eed7165c3f1385e359748c778ec7e91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Wed, 7 Aug 2019 15:07:32 +0200 Subject: [PATCH 629/664] Update snapshot after #16926 (#16939) --- packages/components/src/notice/test/__snapshots__/index.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/notice/test/__snapshots__/index.js.snap b/packages/components/src/notice/test/__snapshots__/index.js.snap index fe2329681dd543..3e9dd6d03fabbc 100644 --- a/packages/components/src/notice/test/__snapshots__/index.js.snap +++ b/packages/components/src/notice/test/__snapshots__/index.js.snap @@ -19,7 +19,7 @@ exports[`Notice should match snapshot 1`] = ` </div> <ForwardRef(IconButton) className="components-notice__dismiss" - icon="no" + icon="no-alt" label="Dismiss this notice" onClick={[Function]} tooltip={false} From 3e271ce68d75bf5712d165d29f0f4cf03dc1fb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 7 Aug 2019 15:27:04 +0200 Subject: [PATCH 630/664] Docs: Update CHANGELOG for @wordpress/scripts after Puppeteer upgrade (#16937) * Docs: Update CHANGELOG for @wordpress/scripts after Puppeteer upgrade * Scripts: Enable semver friendly updates to puppeteer dependency --- package-lock.json | 2 +- packages/scripts/CHANGELOG.md | 7 +++++++ packages/scripts/package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd6154b74c2de3..4a75608b24553f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5037,7 +5037,7 @@ "jest-puppeteer": "^4.3.0", "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", - "puppeteer": "1.19.0", + "puppeteer": "^1.19.0", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", "source-map-loader": "^0.2.4", diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 31bb82b82be37d..eec995c04d432f 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,3 +1,10 @@ +## Master + +### New Features + +- The bundled `puppeteer` dependency has been updated from requiring `1.6.1` to requiring `^1.19.0` ([#16875](https://github.com/WordPress/gutenberg/pull/16875)). It uses Chromium v77 instead of Chromium v69. +- The bundled `jest-puppeteer` dependency has been updated from requiring `^4.0.0` to requiring `^4.3.0` ([#16875](https://github.com/WordPress/gutenberg/pull/16875)). + ## 3.4.0 (2019-08-05) ### New Features diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 8880595864e34d..6dcaf54c9ccf1a 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -47,7 +47,7 @@ "jest-puppeteer": "^4.3.0", "minimist": "^1.2.0", "npm-package-json-lint": "^3.6.0", - "puppeteer": "1.19.0", + "puppeteer": "^1.19.0", "read-pkg-up": "^1.0.1", "resolve-bin": "^0.4.0", "source-map-loader": "^0.2.4", From 3eb43e1307b7dbab525c580d1518bf1d95394f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Wed, 7 Aug 2019 15:49:19 +0200 Subject: [PATCH 631/664] Chore: Upgrade ESLint to the latest version (6.1.0) (#16921) * Chore: Upgrade ESLint to the latest version (6.1.0) * Refactor tests to use native describe.each functionality * Enable jest/valid-describe rule again * Use describe.each in more places * Write describe name on one line * Disable jest/valid-describe individually for special cases * Fix typo * Remove unused import * Remove unnecessary nested describe * Run npm install * Chore: Fix package-lock.json file * Fix failing e2e tests for navigable toolbar * Fix JSDoc rule in ESLint plugin --- package-lock.json | 737 ++++++++++++------ package.json | 2 +- .../src/components/block-list/block.js | 4 +- packages/block-library/src/audio/edit.js | 4 +- packages/block-library/src/image/edit.js | 4 +- packages/block-library/src/video/edit.js | 4 +- .../test/index.js | 2 +- .../shared-tests.js | 2 +- .../test/index.js | 2 +- packages/blocks/src/api/test/utils.js | 2 - packages/components/src/autocomplete/index.js | 4 +- .../higher-order/navigate-regions/index.js | 4 +- .../with-spoken-messages/test/index.js | 4 +- .../components/src/slot-fill/test/slot.js | 9 +- .../components/with-dispatch/test/index.js | 4 +- .../data/src/namespace-store/test/index.js | 8 +- packages/data/src/test/registry.js | 4 +- .../e2e-tests/specs/navigable-toolbar.test.js | 51 +- packages/e2e-tests/specs/publishing.test.js | 36 +- packages/editor/src/store/test/actions.js | 12 +- packages/eslint-plugin/configs/jsdoc.js | 2 +- packages/eslint-plugin/package.json | 8 +- packages/jest-console/src/matchers.js | 8 +- packages/jest-console/src/test/index.test.js | 116 ++- packages/scripts/CHANGELOG.md | 3 +- packages/scripts/package.json | 2 +- packages/scripts/scripts/test-e2e.js | 1 + packages/scripts/scripts/test-unit-jest.js | 1 + 28 files changed, 633 insertions(+), 407 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a75608b24553f..2413d9d0c96d27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, "requires": { "@babel/highlight": "^7.0.0" } @@ -134,17 +133,6 @@ "@babel/helper-split-export-declaration": "^7.4.4" } }, - "@babel/helper-define-map": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz", - "integrity": "sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.4.4", - "lodash": "^4.17.11" - } - }, "@babel/helper-explode-assignable-expression": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", @@ -170,7 +158,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, "requires": { "@babel/types": "^7.0.0" } @@ -311,7 +298,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, "requires": { "chalk": "^2.0.0", "esutils": "^2.0.2", @@ -573,19 +559,130 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz", - "integrity": "sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", + "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.4.4", + "@babel/helper-define-map": "^7.5.5", "@babel/helper-function-name": "^7.1.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.4.4", + "@babel/helper-replace-supers": "^7.5.5", "@babel/helper-split-export-declaration": "^7.4.4", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", + "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-define-map": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", + "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.1.0", + "@babel/types": "^7.5.5", + "lodash": "^4.17.13" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", + "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", + "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.5.5", + "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/traverse": "^7.5.5", + "@babel/types": "^7.5.5" + } + }, + "@babel/parser": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", + "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", + "dev": true + }, + "@babel/traverse": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", + "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.5.5", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.5", + "@babel/types": "^7.5.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", + "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "@babel/plugin-transform-computed-properties": { @@ -1161,10 +1258,16 @@ "ms": "^2.1.1" } }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -1173,7 +1276,6 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", - "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.11", @@ -4169,6 +4271,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/json-schema": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", + "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -4226,6 +4334,47 @@ "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", "dev": true }, + "@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.4.3.tgz", @@ -4804,30 +4953,13 @@ "version": "file:packages/eslint-plugin", "dev": true, "requires": { - "babel-eslint": "^10.0.1", + "babel-eslint": "^10.0.2", "eslint-plugin-jsdoc": "^15.8.0", - "eslint-plugin-jsx-a11y": "^6.2.1", - "eslint-plugin-react": "^7.12.4", - "eslint-plugin-react-hooks": "^1.6.0", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react-hooks": "^1.6.1", "globals": "^12.0.0", "requireindex": "^1.2.0" - }, - "dependencies": { - "globals": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.0.0.tgz", - "integrity": "sha512-c9xoi32iDwlETiyYfO0pd3M8GcEuytJinSoqq7k3fz4H8p2p31NyfKr7JVd7Y0QvmtWcWXcwqW4L33eeDYgh1A==", - "dev": true, - "requires": { - "type-fest": "^0.6.0" - } - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } } }, "@wordpress/format-library": { @@ -5032,7 +5164,7 @@ "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", - "eslint": "^5.16.0", + "eslint": "^6.1.0", "jest": "^24.7.1", "jest-puppeteer": "^4.3.0", "minimist": "^1.2.0", @@ -5353,7 +5485,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -5557,12 +5688,53 @@ "dev": true }, "array.prototype.find": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", - "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", + "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.13.0" + }, + "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "requires": { + "has-symbols": "^1.0.0" + } + } } }, "array.prototype.flat": { @@ -5774,9 +5946,9 @@ } }, "babel-eslint": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", - "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz", + "integrity": "sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -7034,7 +7206,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -7989,7 +8160,6 @@ "version": "1.9.2", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", - "dev": true, "requires": { "color-name": "1.1.1" } @@ -7997,8 +8167,7 @@ "color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", - "dev": true + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" }, "color-string": { "version": "0.3.0", @@ -9413,9 +9582,9 @@ "dev": true }, "damerau-levenshtein": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", - "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", + "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", "dev": true }, "dargs": { @@ -10345,8 +10514,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.10.0", @@ -10371,53 +10539,54 @@ } }, "eslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", - "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", + "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "ajv": "^6.9.1", + "ajv": "^6.10.0", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", + "eslint-scope": "^5.0.0", "eslint-utils": "^1.3.1", "eslint-visitor-keys": "^1.0.0", - "espree": "^5.0.1", + "espree": "^6.0.0", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", + "glob-parent": "^5.0.0", "globals": "^11.7.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", - "js-yaml": "^3.13.0", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", "progress": "^2.0.0", "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", - "text-table": "^0.2.0" + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { "acorn": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", - "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.1.tgz", + "integrity": "sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==", "dev": true }, "acorn-jsx": { @@ -10427,9 +10596,9 @@ "dev": true }, "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -10461,6 +10630,14 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } } }, "debug": { @@ -10482,9 +10659,9 @@ } }, "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -10492,9 +10669,9 @@ } }, "espree": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", - "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz", + "integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==", "dev": true, "requires": { "acorn": "^6.0.7", @@ -10508,25 +10685,31 @@ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "glob-parent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", + "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "is-glob": "^4.0.1" + }, + "dependencies": { + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + } } }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true }, "ignore": { "version": "4.0.6", @@ -10535,9 +10718,9 @@ "dev": true }, "inquirer": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.3.1.tgz", - "integrity": "sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", + "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", "dev": true, "requires": { "ansi-escapes": "^3.2.0", @@ -10546,7 +10729,7 @@ "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.12", "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", @@ -10565,15 +10748,6 @@ "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, @@ -10584,49 +10758,24 @@ "dev": true }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, "rxjs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.1.tgz", - "integrity": "sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", "dev": true, "requires": { "tslib": "^1.9.0" } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "slice-ansi": { @@ -10640,14 +10789,29 @@ "is-fullwidth-code-point": "^2.0.0" } }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, "table": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", - "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.5.tgz", + "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==", "dev": true, "requires": { - "ajv": "^6.9.1", - "lodash": "^4.17.11", + "ajv": "^6.10.2", + "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" }, @@ -10662,34 +10826,25 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } } } }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true } } }, "eslint-plugin-jest": { - "version": "21.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-21.5.0.tgz", - "integrity": "sha512-4fxfe2RcqzU+IVNQL5n4pqibLcUhKKxihYsA510+6kC/FTdGInszDDHgO4ntBzPWu8mcHAvKJLs8o3AQw6eHTg==", - "dev": true + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.14.1.tgz", + "integrity": "sha512-mpLjhADl+HjagrlaGNx95HIji089S18DhnU/Ee8P8VP+dhEnuEzb43BXEaRmDgQ7BiSUPcSCvt1ydtgPkjOF/Q==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^1.13.0" + } }, "eslint-plugin-jsdoc": { "version": "15.8.0", @@ -10730,11 +10885,12 @@ } }, "eslint-plugin-jsx-a11y": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz", - "integrity": "sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", "dev": true, "requires": { + "@babel/runtime": "^7.4.5", "aria-query": "^3.0.0", "array-includes": "^3.0.3", "ast-types-flow": "^0.0.7", @@ -10742,24 +10898,59 @@ "damerau-levenshtein": "^1.0.4", "emoji-regex": "^7.0.2", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1" + "jsx-ast-utils": "^2.2.1" } }, "eslint-plugin-react": { - "version": "7.12.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz", - "integrity": "sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==", + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", + "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", "dev": true, "requires": { "array-includes": "^3.0.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", + "jsx-ast-utils": "^2.1.0", + "object.entries": "^1.1.0", "object.fromentries": "^2.0.0", - "prop-types": "^15.6.2", - "resolve": "^1.9.0" + "object.values": "^1.1.0", + "prop-types": "^15.7.2", + "resolve": "^1.10.1" }, "dependencies": { + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -10767,9 +10958,9 @@ "dev": true }, "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -10778,9 +10969,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.0.tgz", - "integrity": "sha512-lHBVRIaz5ibnIgNG07JNiAuBUeKhEf8l4etNx5vfAEwqQ5tcuK3jV9yjmopPgQDagQb7HwIuQVsE3IVcGrRnag==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.1.tgz", + "integrity": "sha512-wHhmGJyVuijnYIJXZJHDUF2WM+rJYTjulUTqF9k61d3BTk8etydz+M4dXUVH7M76ZRS85rqBTCx0Es/lLsrjnA==", "dev": true }, "eslint-scope": { @@ -10794,10 +10985,13 @@ } }, "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", - "dev": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz", + "integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.0.0" + } }, "eslint-visitor-keys": { "version": "1.0.0", @@ -10848,8 +11042,7 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { "version": "1.8.1", @@ -11369,6 +11562,15 @@ "escape-string-regexp": "^1.0.5" } }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "fileset": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", @@ -11590,6 +11792,42 @@ } } }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, "flat-map-polyfill": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/flat-map-polyfill/-/flat-map-polyfill-0.3.8.tgz", @@ -12808,10 +13046,21 @@ "dev": true }, "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.0.0.tgz", + "integrity": "sha512-c9xoi32iDwlETiyYfO0pd3M8GcEuytJinSoqq7k3fz4H8p2p31NyfKr7JVd7Y0QvmtWcWXcwqW4L33eeDYgh1A==", + "dev": true, + "requires": { + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } }, "globby": { "version": "9.2.0", @@ -13024,8 +13273,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", @@ -13347,9 +13595,9 @@ "dev": true }, "import-fresh": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", - "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -15692,12 +15940,13 @@ } }, "jsx-ast-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", - "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", + "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", "dev": true, "requires": { - "array-includes": "^3.0.3" + "array-includes": "^3.0.3", + "object.assign": "^4.1.0" } }, "kind-of": { @@ -16189,6 +16438,12 @@ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", "dev": true }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -19586,6 +19841,12 @@ "ms": "^2.1.1" } }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -19637,20 +19898,12 @@ } }, "parent-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", - "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" - }, - "dependencies": { - "callsites": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", - "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", - "dev": true - } } }, "parse-asn1": { @@ -20320,7 +20573,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", "@babel/template": "^7.1.0", @@ -20354,42 +20606,22 @@ "@babel/parser": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz", - "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==", - "dev": true + "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==" }, "@babel/template": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.2.2", "@babel/types": "^7.2.2" } }, - "@babel/traverse": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.2.3.tgz", - "integrity": "sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.2.2", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.2.3", - "@babel/types": "^7.2.2", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, "@babel/types": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz", "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==", - "dev": true, "requires": { "esutils": "^2.0.2", "lodash": "^4.17.10", @@ -24528,7 +24760,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -25076,8 +25307,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -26622,6 +26852,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", diff --git a/package.json b/package.json index d6404161ab9a7c..82f89731e6a5f3 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "deep-freeze": "0.0.1", "doctrine": "2.1.0", "enzyme": "3.9.0", - "eslint-plugin-jest": "21.5.0", + "eslint-plugin-jest": "22.14.1", "espree": "4.0.0", "fast-glob": "2.2.7", "fbjs": "0.8.17", diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 55495602436a5f..d95ce1f3a93813 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -486,7 +486,7 @@ function BlockListBlock( { // jsx-a11y/no-static-element-interactions: // - Each block can be selected by clicking on it - /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <IgnoreNestedEvents @@ -618,7 +618,7 @@ function BlockListBlock( { ) } </IgnoreNestedEvents> ); - /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } const applyWithSelect = withSelect( diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 164b8c22c9a7ba..17d24e6c9a7a79 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -151,7 +151,7 @@ class AudioEdit extends Component { ); } - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <> <BlockControls> @@ -210,7 +210,7 @@ class AudioEdit extends Component { </figure> </> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } export default compose( [ diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index f385b291a900a1..48615eae98011d 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -778,7 +778,7 @@ export class ImageEdit extends Component { ); // Disable reason: Each block can be selected by clicking on it - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <> { controls } @@ -924,7 +924,7 @@ export class ImageEdit extends Component { { mediaPlaceholder } </> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 9931508d2c6c3f..d1cbdc7ae47066 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -198,7 +198,7 @@ class VideoEdit extends Component { } const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <> <BlockControls> @@ -314,7 +314,7 @@ class VideoEdit extends Component { </figure> </> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/block-serialization-default-parser/test/index.js b/packages/block-serialization-default-parser/test/index.js index 068db2ef14c72a..33c494cf51ab56 100644 --- a/packages/block-serialization-default-parser/test/index.js +++ b/packages/block-serialization-default-parser/test/index.js @@ -14,6 +14,6 @@ import { jsTester, phpTester } from '@wordpress/block-serialization-spec-parser/ */ import { parse } from '../src'; -describe( 'block-serialization-default-parser-js', jsTester( parse ) ); +describe( 'block-serialization-default-parser-js', jsTester( parse ) ); // eslint-disable-line jest/valid-describe phpTester( 'block-serialization-default-parser-php', path.join( __dirname, 'test-parser.php' ) ); diff --git a/packages/block-serialization-spec-parser/shared-tests.js b/packages/block-serialization-spec-parser/shared-tests.js index 0e5a29b75f052f..74c991b6bbd72f 100644 --- a/packages/block-serialization-spec-parser/shared-tests.js +++ b/packages/block-serialization-spec-parser/shared-tests.js @@ -176,7 +176,7 @@ const hasPHP = 'test' === process.env.NODE_ENV ? ( () => { // skipping if `php` isn't available to us, such as in local dev without it // skipping preserves snapshots while commenting out or simply // not injecting the tests prompts `jest` to remove "obsolete snapshots" -// eslint-disable-next-line jest/no-disabled-tests +// eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe const makeTest = hasPHP ? ( ...args ) => describe( ...args ) : ( ...args ) => describe.skip( ...args ); export const phpTester = ( name, filename ) => makeTest( diff --git a/packages/block-serialization-spec-parser/test/index.js b/packages/block-serialization-spec-parser/test/index.js index 9d00c5a5434c33..2b60ce2c11f1e3 100644 --- a/packages/block-serialization-spec-parser/test/index.js +++ b/packages/block-serialization-spec-parser/test/index.js @@ -9,6 +9,6 @@ import path from 'path'; import { parse } from '../'; import { jsTester, phpTester } from '../shared-tests'; -describe( 'block-serialization-spec-parser-js', jsTester( parse ) ); +describe( 'block-serialization-spec-parser-js', jsTester( parse ) ); // eslint-disable-line jest/valid-describe phpTester( 'block-serialization-spec-parser-php', path.join( __dirname, 'test-parser.php' ) ); diff --git a/packages/blocks/src/api/test/utils.js b/packages/blocks/src/api/test/utils.js index ea3fe579084410..25f57337a8636d 100644 --- a/packages/blocks/src/api/test/utils.js +++ b/packages/blocks/src/api/test/utils.js @@ -1,5 +1,3 @@ -/* eslint-disable react/forbid-elements2 */ - /** * External dependencies */ diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index 0ae8e2b80bdb90..6752ad5274ce94 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -446,7 +446,7 @@ export class Autocomplete extends Component { const activeId = isExpanded ? `components-autocomplete-item-${ instanceId }-${ selectedKey }` : null; // Disable reason: Clicking the editor should reset the autocomplete when the menu is suppressed - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <div ref={ this.bindNode } @@ -487,7 +487,7 @@ export class Autocomplete extends Component { ) } </div> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js index 1d201be72e7133..c716e2d1ea9f14 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.js @@ -61,7 +61,7 @@ export default createHigherOrderComponent( } ); // Disable reason: Clicking the editor should dismiss the regions focus style - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <div ref={ this.bindContainer } className={ className } onClick={ this.onClick }> <KeyboardShortcuts @@ -76,7 +76,7 @@ export default createHigherOrderComponent( <WrappedComponent { ...this.props } /> </div> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } }; }, 'navigateRegions' diff --git a/packages/components/src/higher-order/with-spoken-messages/test/index.js b/packages/components/src/higher-order/with-spoken-messages/test/index.js index 1dfcad87bcbc1a..b13164b27c0f7e 100644 --- a/packages/components/src/higher-order/with-spoken-messages/test/index.js +++ b/packages/components/src/higher-order/with-spoken-messages/test/index.js @@ -21,7 +21,7 @@ describe( 'withSpokenMessages', () => { render( <DumpComponent /> ); // Unrendered element. - expect( testSpeak ).toBeCalledWith( true ); - expect( testDebouncedSpeak ).toBeCalledWith( true ); + expect( testSpeak ).toHaveBeenCalledWith( true ); + expect( testDebouncedSpeak ).toHaveBeenCalledWith( true ); } ); } ); diff --git a/packages/components/src/slot-fill/test/slot.js b/packages/components/src/slot-fill/test/slot.js index bacc5e55e0fe3c..c4a61e8e2a9a55 100644 --- a/packages/components/src/slot-fill/test/slot.js +++ b/packages/components/src/slot-fill/test/slot.js @@ -210,8 +210,9 @@ describe( 'Slot', () => { expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); - [ false, true ].forEach( ( bubblesVirtually ) => { - describe( 'bubblesVirtually ' + bubblesVirtually, () => { + describe.each( [ false, true ] )( + 'bubblesVirtually %p', + ( bubblesVirtually ) => { it( 'should subsume another slot by the same name', () => { const testRenderer = ReactTestRenderer.create( <Provider> @@ -251,6 +252,6 @@ describe( 'Slot', () => { expect( testRenderer.getInstance().slots ).toHaveProperty( 'egg' ); } ); - } ); - } ); + } + ); } ); diff --git a/packages/data/src/components/with-dispatch/test/index.js b/packages/data/src/components/with-dispatch/test/index.js index 66fba4028e84f8..186098bd3facb1 100644 --- a/packages/data/src/components/with-dispatch/test/index.js +++ b/packages/data/src/components/with-dispatch/test/index.js @@ -35,7 +35,7 @@ describe( 'withDispatch', () => { return { increment: () => { const actionReturnedFromDispatch = Promise.resolve( _dispatch( 'counter' ).increment( count ) ); - expect( actionReturnedFromDispatch ).resolves.toEqual( + return expect( actionReturnedFromDispatch ).resolves.toEqual( { type: 'increment', count, @@ -166,7 +166,7 @@ describe( 'withDispatch', () => { const actionReturnedFromDispatch = Promise.resolve( _dispatch( 'counter' ).update( innerCount + 1 ) ); - expect( actionReturnedFromDispatch ).resolves.toEqual( { + return expect( actionReturnedFromDispatch ).resolves.toEqual( { type: 'update', count: innerCount + 1, } ); diff --git a/packages/data/src/namespace-store/test/index.js b/packages/data/src/namespace-store/test/index.js index 2a9f14ae821f64..62cca98f4c31f2 100644 --- a/packages/data/src/namespace-store/test/index.js +++ b/packages/data/src/namespace-store/test/index.js @@ -36,7 +36,7 @@ describe( 'controls', () => { } ); registry.dispatch( 'store2' ).action2(); - expect( action1 ).toBeCalled(); + expect( action1 ).toHaveBeenCalled(); } ); } ); @@ -107,8 +107,7 @@ describe( 'controls', () => { expect( registry.select( 'store' ).getItems.hasResolver ).toBe( false ); } ); } ); - describe( 'various action types have expected response and resolve as ' + - 'expected with controls middleware', () => { + describe( 'various action types have expected response and resolve as expected with controls middleware', () => { const actions = { *withPromise() { yield { type: 'SOME_ACTION' }; @@ -164,8 +163,7 @@ describe( 'controls', () => { .toEqual( { type: 'NORMAL' } ); } ); } ); - describe( 'action type resolves as expected with just promise ' + - 'middleware', () => { + describe( 'action type resolves as expected with just promise middleware', () => { const actions = { normal: () => ( { type: 'NORMAL' } ), withPromiseAndAction: () => new Promise( diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index 23a6d399ce7786..9be52ac7d022b1 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -438,10 +438,10 @@ describe( 'createRegistry', () => { } ); expect( registry.select( 'reducer1' ).selector1() ).toEqual( 'result1' ); - expect( selector1 ).toBeCalledWith( store.getState() ); + expect( selector1 ).toHaveBeenCalledWith( store.getState() ); expect( registry.select( 'reducer1' ).selector2() ).toEqual( 'result2' ); - expect( selector2 ).toBeCalledWith( store.getState() ); + expect( selector2 ).toHaveBeenCalledWith( store.getState() ); } ); it( 'should run the registry selectors properly', () => { diff --git a/packages/e2e-tests/specs/navigable-toolbar.test.js b/packages/e2e-tests/specs/navigable-toolbar.test.js index 1936fc5064caa1..1b8efda52e1bb9 100644 --- a/packages/e2e-tests/specs/navigable-toolbar.test.js +++ b/packages/e2e-tests/specs/navigable-toolbar.test.js @@ -1,18 +1,11 @@ -/** - * External dependencies - */ -import { forEach } from 'lodash'; - /** * WordPress dependencies */ import { createNewPost, pressKeyWithModifier } from '@wordpress/e2e-test-utils'; -describe( 'block toolbar', () => { - forEach( { - unified: true, - contextual: false, - }, ( isUnifiedToolbar, label ) => { +describe.each( [ [ 'unified', true ], [ 'contextual', false ] ] )( + 'block toolbar (%s: %p)', + ( label, isUnifiedToolbar ) => { beforeEach( async () => { await createNewPost(); @@ -25,24 +18,28 @@ describe( 'block toolbar', () => { }, isUnifiedToolbar ); } ); - const isInBlockToolbar = () => page.evaluate( () => ( - !! document.activeElement.closest( '.block-editor-block-toolbar' ) - ) ); + const isInBlockToolbar = () => page.evaluate( ( _isUnifiedToolbar ) => { + if ( _isUnifiedToolbar ) { + return !! document.activeElement + .closest( '.edit-post-header-toolbar' ) + .querySelector( '.block-editor-block-toolbar' ); + } + return !! document.activeElement.closest( '.block-editor-block-toolbar' ); + }, isUnifiedToolbar ); - describe( label, () => { - it( 'navigates in and out of toolbar by keyboard (Alt+F10, Escape)', async () => { - // Assumes new post focus starts in title. Create first new - // block by ArrowDown. - await page.keyboard.press( 'ArrowDown' ); + it( 'navigates in and out of toolbar by keyboard (Alt+F10, Escape)', async () => { + // Assumes new post focus starts in title. Create first new + // block by ArrowDown. + await page.keyboard.press( 'ArrowDown' ); - // [TEMPORARY]: A new paragraph is not technically a block yet - // until starting to type within it. - await page.keyboard.type( 'Example' ); + // [TEMPORARY]: A new paragraph is not technically a block yet + // until starting to type within it. + await page.keyboard.type( 'Example' ); - // Upward - await pressKeyWithModifier( 'alt', 'F10' ); - expect( await isInBlockToolbar() ).toBe( true ); - } ); + // Upward + await pressKeyWithModifier( 'alt', 'F10' ); + expect( await isInBlockToolbar() ).toBe( true ); } ); - } ); -} ); + } +); + diff --git a/packages/e2e-tests/specs/publishing.test.js b/packages/e2e-tests/specs/publishing.test.js index b484428e4fcc50..64b8705ef8bab0 100644 --- a/packages/e2e-tests/specs/publishing.test.js +++ b/packages/e2e-tests/specs/publishing.test.js @@ -12,9 +12,11 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'Publishing', () => { - [ 'post', 'page' ].forEach( ( postType ) => { - let werePrePublishChecksEnabled; - describe( `a ${ postType }`, () => { + describe.each( [ 'post', 'page' ] )( + 'a %s', + ( postType ) => { + let werePrePublishChecksEnabled; + beforeEach( async () => { await createNewPost( postType ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); @@ -43,12 +45,14 @@ describe( 'Publishing', () => { // The post-publishing panel is not visible anymore. expect( await page.$( '.editor-post-publish-panel' ) ).toBeNull(); } ); - } ); - } ); + } + ); + + describe.each( [ 'post', 'page' ] )( + 'a %s with pre-publish checks disabled', + ( postType ) => { + let werePrePublishChecksEnabled; - [ 'post', 'page' ].forEach( ( postType ) => { - let werePrePublishChecksEnabled; - describe( `a ${ postType } with pre-publish checks disabled`, () => { beforeEach( async () => { await createNewPost( postType ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); @@ -75,12 +79,14 @@ describe( 'Publishing', () => { // The post-publishing panel should have been not shown. expect( await page.$( '.editor-post-publish-panel' ) ).toBeNull(); } ); - } ); - } ); + } + ); + + describe.each( [ 'post', 'page' ] )( + 'a %s in small viewports', + ( postType ) => { + let werePrePublishChecksEnabled; - [ 'post', 'page' ].forEach( ( postType ) => { - let werePrePublishChecksEnabled; - describe( `a ${ postType } in small viewports`, () => { beforeEach( async () => { await createNewPost( postType ); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); @@ -101,6 +107,6 @@ describe( 'Publishing', () => { expect( await page.$( '.editor-post-publish-panel__toggle' ) ).not.toBeNull(); expect( await page.$( '.editor-post-publish-button' ) ).toBeNull(); } ); - } ); - } ); + } + ); } ); diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index ff10dcf0a9044b..ecb588c0912235 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -436,8 +436,7 @@ describe( 'Post generator actions', () => { } ); }; - describe( 'yields with expected responses when edited post is not ' + - 'saveable', () => { + describe( 'yields with expected responses when edited post is not saveable', () => { it( 'yields action for selecting if edited post is saveable', () => { reset( false ); const { value } = fulfillment.next(); @@ -451,8 +450,7 @@ describe( 'Post generator actions', () => { expect( value ).toBeUndefined(); } ); } ); - describe( 'yields with expected responses for when not autosaving ' + - 'and edited post is new', () => { + describe( 'yields with expected responses for when not autosaving and edited post is new', () => { beforeEach( () => { isAutosave = false; isEditedPostNew = true; @@ -469,8 +467,7 @@ describe( 'Post generator actions', () => { } ); } ); - describe( 'yields with expected responses for when not autosaving ' + - 'and edited post is not new', () => { + describe( 'yields with expected responses for when not autosaving and edited post is not new', () => { beforeEach( () => { isAutosave = false; isEditedPostNew = false; @@ -486,8 +483,7 @@ describe( 'Post generator actions', () => { fetchSuccessConditions.forEach( testRunRoutine ); } ); } ); - describe( 'yields with expected responses for when autosaving is true ' + - 'and edited post is not new', () => { + describe( 'yields with expected responses for when autosaving is true and edited post is not new', () => { beforeEach( () => { isAutosave = true; isEditedPostNew = false; diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index 2fda492cd445dc..a8a34afe94f125 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -16,7 +16,7 @@ module.exports = { }, }, rules: { - 'jsdoc/no-undefined-types': [ 'warning', { + 'jsdoc/no-undefined-types': [ 'warn', { // Required to reference browser types because we don't have the `browser` environment enabled for the project. // Here we filter out all browser globals that don't begin with an uppercase letter because those // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a8139e218160eb..3e5ee9add8aac6 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -24,11 +24,11 @@ ], "main": "index.js", "dependencies": { - "babel-eslint": "^10.0.1", + "babel-eslint": "^10.0.2", "eslint-plugin-jsdoc": "^15.8.0", - "eslint-plugin-jsx-a11y": "^6.2.1", - "eslint-plugin-react": "^7.12.4", - "eslint-plugin-react-hooks": "^1.6.0", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react-hooks": "^1.6.1", "globals": "^12.0.0", "requireindex": "^1.2.0" }, diff --git a/packages/jest-console/src/matchers.js b/packages/jest-console/src/matchers.js index ec66cdf86523e0..fa2d6487340b8c 100644 --- a/packages/jest-console/src/matchers.js +++ b/packages/jest-console/src/matchers.js @@ -9,7 +9,7 @@ import { isEqual, reduce, some } from 'lodash'; */ import supportedMatchers from './supported-matchers'; -const createToBeCalledMatcher = ( matcherName, methodName ) => +const createToHaveBeenCalledMatcher = ( matcherName, methodName ) => ( received ) => { const spy = received[ methodName ]; const calls = spy.mock.calls; @@ -33,7 +33,7 @@ const createToBeCalledMatcher = ( matcherName, methodName ) => }; }; -const createToBeCalledWithMatcher = ( matcherName, methodName ) => +const createToHaveBeenCalledWith = ( matcherName, methodName ) => ( received, ...expected ) => { const spy = received[ methodName ]; const calls = spy.mock.calls; @@ -69,8 +69,8 @@ expect.extend( return { ...result, - [ matcherName ]: createToBeCalledMatcher( `.${ matcherName }`, methodName ), - [ matcherNameWith ]: createToBeCalledWithMatcher( `.${ matcherNameWith }`, methodName ), + [ matcherName ]: createToHaveBeenCalledMatcher( `.${ matcherName }`, methodName ), + [ matcherNameWith ]: createToHaveBeenCalledWith( `.${ matcherNameWith }`, methodName ), }; }, {} ) ); diff --git a/packages/jest-console/src/test/index.test.js b/packages/jest-console/src/test/index.test.js index 8baf2da040134f..5e02832f7e2051 100644 --- a/packages/jest-console/src/test/index.test.js +++ b/packages/jest-console/src/test/index.test.js @@ -1,89 +1,77 @@ /* eslint-disable no-console */ -/** - * External dependencies - */ -import { forEach } from 'lodash'; - /** * Internal dependencies */ import '../matchers'; describe( 'jest-console', () => { - forEach( - { - error: 'toHaveErrored', - info: 'toHaveInformed', - log: 'toHaveLogged', - warn: 'toHaveWarned', - }, - ( matcherName, methodName ) => { - describe( `console.${ methodName }`, () => { - const matcherNameWith = `${ matcherName }With`; - const message = `This is ${ methodName }!`; - - test( `${ matcherName } works`, () => { - console[ methodName ]( message ); - - expect( console )[ matcherName ](); - } ); + describe.each( [ [ 'error', 'toHaveErrored' ], [ 'info', 'toHaveInformed' ], [ 'log', 'toHaveLogged' ], [ 'warn', 'toHaveWarned' ] ] )( + 'console.%s', + ( methodName, matcherName ) => { + const matcherNameWith = `${ matcherName }With`; + const message = `This is ${ methodName }!`; - test( `${ matcherName } works when not called`, () => { - expect( console ).not[ matcherName ](); - expect( - () => expect( console )[ matcherName ]() - ).toThrowError( 'Expected mock function to be called.' ); - } ); + test( `${ matcherName } works`, () => { + console[ methodName ]( message ); - test( `${ matcherNameWith } works with arguments that match`, () => { - console[ methodName ]( message ); + expect( console )[ matcherName ](); + } ); - expect( console )[ matcherNameWith ]( message ); - } ); + test( `${ matcherName } works when not called`, () => { + expect( console ).not[ matcherName ](); + expect( + () => expect( console )[ matcherName ]() + ).toThrow( 'Expected mock function to be called.' ); + } ); - test( `${ matcherNameWith } works when not called`, () => { - expect( console ).not[ matcherNameWith ]( message ); - expect( - () => expect( console )[ matcherNameWith ]( message ) - ).toThrowError( /Expected mock function to be called with:.*but it was called with:/s ); - } ); + test( `${ matcherNameWith } works with arguments that match`, () => { + console[ methodName ]( message ); - test( `${ matcherNameWith } works with many arguments that do not match`, () => { - console[ methodName ]( 'Unknown message.' ); - console[ methodName ]( message, 'Unknown param.' ); + expect( console )[ matcherNameWith ]( message ); + } ); - expect( console ).not[ matcherNameWith ]( message ); - expect( - () => expect( console )[ matcherNameWith ]( message ) - ).toThrowError( /Expected mock function to be called with:.*but it was called with:.*Unknown param./s ); - } ); + test( `${ matcherNameWith } works when not called`, () => { + expect( console ).not[ matcherNameWith ]( message ); + expect( + () => expect( console )[ matcherNameWith ]( message ) + ).toThrow( /Expected mock function to be called with:.*but it was called with:/s ); + } ); + + test( `${ matcherNameWith } works with many arguments that do not match`, () => { + console[ methodName ]( 'Unknown message.' ); + console[ methodName ]( message, 'Unknown param.' ); - test( 'assertions number gets incremented after every matcher call', () => { - const spy = console[ methodName ]; + expect( console ).not[ matcherNameWith ]( message ); + expect( + () => expect( console )[ matcherNameWith ]( message ) + ).toThrow( /Expected mock function to be called with:.*but it was called with:.*Unknown param./s ); + } ); - expect( spy.assertionsNumber ).toBe( 0 ); + test( 'assertions number gets incremented after every matcher call', () => { + const spy = console[ methodName ]; - console[ methodName ]( message ); + expect( spy.assertionsNumber ).toBe( 0 ); - expect( console )[ matcherName ](); - expect( spy.assertionsNumber ).toBe( 1 ); + console[ methodName ]( message ); - expect( console )[ matcherNameWith ]( message ); - expect( spy.assertionsNumber ).toBe( 2 ); - } ); + expect( console )[ matcherName ](); + expect( spy.assertionsNumber ).toBe( 1 ); - describe( 'lifecycle', () => { - beforeAll( () => { - // This is a difficult one to test, since the matcher's - // own lifecycle is defined to run before ours. Infer - // that we're being watched by testing the console - // method as being a spy. - expect( console[ methodName ].assertionsNumber ).toBeGreaterThanOrEqual( 0 ); - } ); + expect( console )[ matcherNameWith ]( message ); + expect( spy.assertionsNumber ).toBe( 2 ); + } ); - it( 'captures logging in lifecycle', () => {} ); + describe( 'lifecycle', () => { + beforeAll( () => { + // This is a difficult one to test, since the matcher's + // own lifecycle is defined to run before ours. Infer + // that we're being watched by testing the console + // method as being a spy. + expect( console[ methodName ].assertionsNumber ).toBeGreaterThanOrEqual( 0 ); } ); + + it( 'captures logging in lifecycle', () => {} ); } ); } ); diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index eec995c04d432f..7ccc3d4d75efa7 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -4,6 +4,7 @@ - The bundled `puppeteer` dependency has been updated from requiring `1.6.1` to requiring `^1.19.0` ([#16875](https://github.com/WordPress/gutenberg/pull/16875)). It uses Chromium v77 instead of Chromium v69. - The bundled `jest-puppeteer` dependency has been updated from requiring `^4.0.0` to requiring `^4.3.0` ([#16875](https://github.com/WordPress/gutenberg/pull/16875)). +- The bundled `eslint` dependency has been updated from requiring `^5.16.0` to requiring `^6.1.0`. ## 3.4.0 (2019-08-05) @@ -26,7 +27,7 @@ ## 3.2.0 (2019-05-21) -### New Feature +### New Features - Leverage `@wordpress/dependency-extraction-webpack-plugin` plugin to extract WordPress dependencies. diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 6dcaf54c9ccf1a..c07897b0b60930 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -42,7 +42,7 @@ "chalk": "^2.4.1", "check-node-version": "^3.1.1", "cross-spawn": "^5.1.0", - "eslint": "^5.16.0", + "eslint": "^6.1.0", "jest": "^24.7.1", "jest-puppeteer": "^4.3.0", "minimist": "^1.2.0", diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js index efa0c834df2394..df3fc9733ab644 100644 --- a/packages/scripts/scripts/test-e2e.js +++ b/packages/scripts/scripts/test-e2e.js @@ -12,6 +12,7 @@ process.on( 'unhandledRejection', ( err ) => { /** * External dependencies */ +/* eslint-disable jest/no-jest-import */ const jest = require( 'jest' ); /** diff --git a/packages/scripts/scripts/test-unit-jest.js b/packages/scripts/scripts/test-unit-jest.js index e7edf3c69ccdb4..68c0108824c941 100644 --- a/packages/scripts/scripts/test-unit-jest.js +++ b/packages/scripts/scripts/test-unit-jest.js @@ -12,6 +12,7 @@ process.on( 'unhandledRejection', ( err ) => { /** * External dependencies */ +/* eslint-disable jest/no-jest-import */ const jest = require( 'jest' ); /** From 0214d79f059f75f49e9d7014243b78eb839a3a25 Mon Sep 17 00:00:00 2001 From: Courtney <9257264+cburton4@users.noreply.github.com> Date: Wed, 7 Aug 2019 10:39:11 -0400 Subject: [PATCH 632/664] Added resources for Figma (#16892) * Added resources for Figma Added resources and information to make it easier for folks to use Figma as a design tool. * Removed content and copy updates Removed the section that references Sketch resources as they are out of date. I also used phrasing from other documentation to make updates to the copy. * Update docs/designers-developers/designers/design-resources.md Co-Authored-By: Kjell Reigstad <kjell.reigstad@automattic.com> --- .../designers/design-resources.md | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/designers-developers/designers/design-resources.md b/docs/designers-developers/designers/design-resources.md index a6bad10ef02f9d..6915c7af678939 100644 --- a/docs/designers-developers/designers/design-resources.md +++ b/docs/designers-developers/designers/design-resources.md @@ -1,11 +1,51 @@ # Resources -The [SketchPress](https://github.com/10up/SketchPress) project includes a library of Gutenberg design components helpful for designing and prototyping: +## Figma +The [WordPress Design team](https://make.wordpress.org/design/) uses [Figma](https://www.figma.com/) to collaborate and share work. If you'd like to contribute, join the [#design channel](https://app.slack.com/client/T024MFP4J/C024MFP4Q) in [Slack](https://make.wordpress.org/chat/) and ask the team to set you up with a free Figma account. This will give you access to a helpful library of components used in WordPress. They are stable, fully supported, up to date, and ready for use in designs and prototypes. -[Download Sketch mockups & patterns files](https://github.com/10up/SketchPress) +### How to contribute -These components are built to be used in [Sketch](https://www.sketchapp.com) for Mac. Users on other platforms may have luck importing the file to [Figma](https://www.figma.com) (Mac/Windows/Linux) or [Lunacy](https://icons8.com/lunacy) (Windows). +### Resources for learning how to use Figma +[Getting started with Figma](https://help.figma.com/category/9-getting-started) + +[Top Online Tutorials to Learn Figma for UI/UX Design](https://medium.com/quick-design/top-online-tutorials-to-learn-figma-for-ui-ux-design-4e9c6721a72d) + +[Take a Tour Around Figma](https://help.figma.com/article/12-getting-familiar-with-figma) + +### Learning how to use files and projects +[Getting started with Figma files and projects](https://help.figma.com/article/298-getting-started-with-files-and-projects) + +[What are files?](https://help.figma.com/article/298-getting-started-with-files-and-projects#files) + +[What are projects?](https://help.figma.com/article/298-getting-started-with-files-and-projects#projects) + +[Video tutorial](https://www.youtube.com/watch?v=c5HS6smhq2E) + +[FAQ](https://help.figma.com/article/298-getting-started-with-files-and-projects#faq) + +### Learning how to use components +[Getting started with components](https://help.figma.com/article/66-components) + +[What are components?](https://help.figma.com/article/66-components#components) + +[Video tutorial](https://help.figma.com/article/66-components#videos) + +### Learning how to use WordPress Figma libraries +**How to turn on the WordPress Components library in Figma** + +![How to turn on Figma libraries gif](https://wordpress.org/gutenberg/files/2019/08/figma-howtoturnonlibraries.gif) + +1. Click the **Team Library** icon in the **Assets** Panel: + +![Hovering over the team library icon](https://wordpress.org/gutenberg/files/2019/08/figma-turn-on-libraries-e1564770916643.png) + +2. The **Library** modal will open and allow you to view a list of available libraries. Toggle to _Enable_ or _Disable_ a specific library: + +![Switching on the WordPress components libray in Figma](https://wordpress.org/gutenberg/files/2019/08/figma-libraries-e1564770879415.png) + +**How to refine or contribute to the WordPress components React library _(Coming soon)_** + +WordPress components in Figma mirror the live React components. Documentation for how to refine or contribute to WordPress components in React is coming soon. -Please note that these are mockups, so they may not be 1:1 accurate. It is also possible that the Sketch files aren’t up to date with the latest version of Gutenberg itself, as development sometimes moves faster than the Sketch updates. If you have questions, please don’t hesitate to ask in the #design channel on the WordPress community Slack. From 68dc4f7f5708d30fe18e070241b4a52401d1b251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Su=C3=A1rez?= <rdsuarez@gmail.com> Date: Wed, 7 Aug 2019 18:08:10 -0300 Subject: [PATCH 633/664] Try/even better previews (#16873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * First stab at a proof of concept. * Attempt to programmatically determine dest dimensions * Add README stub. * Updates to use default args for width and height Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r298130949 * Update class namespaces to match package Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r298131236 * Add width and height as Effect Hook deps Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r300334136 * Use correct `createRef` from @wordpress/element Was previously using React createRef. Addresses https://github.com/WordPress/gutenberg/pull/16113#discussion_r300325510 * Revert "Update class namespaces to match package" This reverts commit 24252fa887986548839e7d9a6fe2639807ed0bc6. * Migrate to useRef hook * Adds basic unit test Still a WIP * Refactor to avoid unecessary state and simplify implementation of scaling * Add some padding to the preview Avoids Blocks butting up against the edges of the preview, especially if the preview wrapper has rounded corners which can cause the Blocks to be clipped. * Refactor to utilise JS template strings * WIP: Adds zoom for small individual blocks With small individual Blocks showing the preview at “actual size” (even when scaled to fit) is a bit odd. Everything still looks very zoomed out and far away. Updates to check DOM for largest element within the Block and use that width to calculate a “zoomed” scale value so that the contents of the Block itself is shown zoomed in. Still WIP * Updates to target block contents more reliably and to calc entire box model in width * block-preview: render scaled preview in a shadow dom * try: setting styles to shadow dom elements * Revert "try: setting styles to shadow dom elements" This reverts commit ea573f9dd0401356ebab81b9df3e5079baf5de66. The reason being that there is no benefit of using shadow DOM and it introduces additional complexity. * Removes attempt at auto zoom on Block contents After much experimenting we’ve decided that attempting to reliably detect the visual size of the Block’s visual contents is impossible. This is due to inconsistencies in the DOM markup of each Block. See https://github.com/WordPress/gutenberg/pull/16113#issuecomment-516916262 * Updates to remove scale by default in favour of opt in via prop As discussed here https://github.com/WordPress/gutenberg/pull/16113#issuecomment-516917681, there is little point in having thumbnail preview and large popover preview showing the same thing. Update to introduce a prop to opt in to scaling the preview. By default the preview is no auto-scaled. Apply `true` value to `BlockPreview` to ensure that Block Styles popover preview is scaled to fit. * Adds scaleFactor prop to provide control for non dynamically scaled preview * Adjust scaleFactor for Block Styles preview * Fix to ensure hooks are called unconditionally to obey rules of Hooks See https://reactjs.org/docs/hooks-rules.html * Removes padding and border from preview on designer advice * Only apply dimensions to preview content when dynamically scaling * block-preview: a different approach attempt. * Correct typo in util function name * Seek the firstChild of the Block DOM node * Tidy up effect timers and improve comments * Dynamically calculate widths and offsets of preview container Removes hardcoded values. * Updates to account for widths of all Blocks passed into to preview Preivously we only considered a single Block scenario. However, the recent addition of multi block support for Previews means we need to ensure we’re measuring the widths of _all_ Blocks. * Revert class additions to Blocks * Adds optional scope to DOM utils. Use to limit scope in Block width comparison. * Update packages/block-editor/src/components/block-preview/index.js Co-Authored-By: Riad Benguella <benguella@gmail.com> * Allow prop based opt out from scaling * Fix to ensure smallest Block is found even if smaller than container Preivously we were default to the container width being the largest item. In fact we always want to determine the largest Block contents element and use that to determine the scale. * Remove unecessary Math.min usage * core/button: adjust dims of button wrapper * apply vertical alignment * set scale factor to 0.9 * rollback testing purpose comment * cpre/button: keep adjusting styles for preview * rename vars and css class names * adjust rebase * restore isScaled factor scale * set the scale adjustment in the proper place * update README file * core/button: tidy edit preview styles * coer/button: tweak button preview * preview-block: ensure make the preview visible. * block-preview: hide dropzone * core/button: adjust preview for thumb and full sizes * core/button: set same width for preview * core/button: because it's !important * core/button: set nowrap button in preview * core/button: set nowrap onlu for button * core/quote: adjust quote size * Make previews overflow to the bottom. Currently still has a bug where the large `.block-editor-block-switcher__preview` pane behaves like it's previewing a taller block, when it's not * Revert "core/button: set nowrap onlu for button" It breaks previews of long buttons This reverts commit 758468c803a6abec1ad9aed00f240f3af44cf86d. * Fix unit test * core/button: apply nowrap only to buttons * Try a fixed canvas width * Fix blocks editor styles * core/button: centering preview * core/button: adjust only into teh styles preview * block-preview: remove scaleAdjustment property * block-preview: hide inserte element in preview * block-preview: just pick up the first block to scale * block-preview: fix set tall class. X position (wip) * popover: remove commented lines * Refactor the preview * Remove debug code * simplify preview resets * Fix the preview recomputing * Vertical alignining small blocks * block-editor: restore viewportWidth as a property * Update Readme docs Props @marekhrabe. * Remove tests for now We'll revisit when we have a better idea what specifically we should be testing * block-preview: simplify comparision * Remove readme updates that break tests * Update docs properly. See 6f29c27 --- packages/block-editor/README.md | 9 +- .../src/components/block-preview/README.md | 14 +- .../src/components/block-preview/index.js | 129 ++++++++++++++---- .../src/components/block-preview/style.scss | 77 +++++++++-- .../src/components/block-styles/index.js | 7 +- .../src/components/block-styles/style.scss | 24 ++-- packages/block-editor/src/utils/dom.js | 15 +- packages/block-library/src/button/edit.js | 4 + packages/block-library/src/button/editor.scss | 20 +-- 9 files changed, 217 insertions(+), 82 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 477c8657e40db3..1d230d45fc3a23 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -124,11 +124,16 @@ Undocumented declaration. <a name="BlockPreview" href="#BlockPreview">#</a> **BlockPreview** -BlockPreview renders a preview given an array of blocks. +BlockPreview renders a preview of a block or array of blocks. + +_Related_ + +- <https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/block-preview/README.md> _Parameters_ -- _props_ `Object`: Component props. +- _blocks_ `(Array|Object)`: A block instance (object) or an array of blocks to be previewed. +- _viewportWidth_ `number`: Width of the preview container in pixels. Controls at what size the blocks will be rendered inside the preview. Default: 700. _Returns_ diff --git a/packages/block-editor/src/components/block-preview/README.md b/packages/block-editor/src/components/block-preview/README.md index 10aaec2dd4001c..b415f2bbc22379 100644 --- a/packages/block-editor/src/components/block-preview/README.md +++ b/packages/block-editor/src/components/block-preview/README.md @@ -10,20 +10,22 @@ Render the component passing in the required props: ```jsx <BlockPreview blocks={ blocks } - isScaled={ false } + viewportWidth={ 800 } /> ``` ## Props ### `blocks` -* **Type:** `array|object` +* **Type:** `Array|Object` * **Default:** `undefined` A block instance (object) or a blocks array you would like to render a preview. -### `isScaled` -* **Type:** `Boolean` -* **Default:** `false` +### `viewportWidth` +* **Type:** `Int` +* **Default:** `700` -Use this if you need to render previews in smaller areas, like block thumbnails. +Width of the preview container in pixels. Controls at what size the blocks will be rendered inside the preview. + +`viewportWidth` can be used to simulate how blocks look on different device sizes or to make sure make sure multiple previews will be rendered with the same scale, regardless of their content. diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index b63fd4abbf7dd5..d8235eea0c1ddd 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -9,46 +9,129 @@ import classnames from 'classnames'; */ import { Disabled } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; +import { useLayoutEffect, useState, useRef, useReducer, useMemo } from '@wordpress/element'; /** * Internal dependencies */ import BlockEditorProvider from '../provider'; import BlockList from '../block-list'; +import { getBlockPreviewContainerDOMNode } from '../../utils/dom'; -export function BlockPreview( { blocks, settings, className, isScaled } ) { - if ( ! blocks ) { +function ScaledBlockPreview( { blocks, viewportWidth } ) { + const previewRef = useRef( null ); + + const [ isTallPreview, setIsTallPreview ] = useState( false ); + const [ isReady, setIsReady ] = useState( false ); + const [ previewScale, setPreviewScale ] = useState( 1 ); + const [ { x, y }, setPosition ] = useState( { x: 0, y: 0 } ); + + // Dynamically calculate the scale factor + useLayoutEffect( () => { + // Timer - required to account for async render of `BlockEditorProvider` + const timerId = setTimeout( () => { + const containerElement = previewRef.current; + if ( ! containerElement ) { + return; + } + + // If we're previewing a single block, scale the preview to fit it. + if ( blocks.length === 1 ) { + const block = blocks[ 0 ]; + const previewElement = getBlockPreviewContainerDOMNode( block.clientId, containerElement ); + if ( ! previewElement ) { + return; + } + + const containerElementRect = containerElement.getBoundingClientRect(); + const scaledElementRect = previewElement.getBoundingClientRect(); + + const scale = containerElementRect.width / scaledElementRect.width || 1; + const offsetX = scaledElementRect.left - containerElementRect.left; + const offsetY = ( containerElementRect.height > scaledElementRect.height * scale ) ? + ( containerElementRect.height - ( scaledElementRect.height * scale ) ) / 2 : 0; + + setIsTallPreview( scaledElementRect.height * scale > containerElementRect.height ); + setPreviewScale( scale ); + setPosition( { x: offsetX * scale, y: offsetY } ); + + // Hack: we need to reset the scaled elements margins + previewElement.style.marginTop = '0'; + } else { + const containerElementRect = containerElement.getBoundingClientRect(); + setPreviewScale( containerElementRect.width / viewportWidth ); + setIsTallPreview( true ); + } + + setIsReady( true ); + }, 100 ); + + // Cleanup + return () => { + if ( timerId ) { + window.clearTimeout( timerId ); + } + }; + }, [] ); + + if ( ! blocks || blocks.length === 0 ) { return null; } + + const previewStyles = { + transform: `scale(${ previewScale })`, + visibility: isReady ? 'visible' : 'hidden', + left: -x, + top: y, + width: viewportWidth, + }; + + const contentClassNames = classnames( 'block-editor-block-preview__content editor-styles-wrapper', { + 'is-tall-preview': isTallPreview, + 'is-ready': isReady, + } ); + return ( - <Disabled - aria-hidden - className={ - classnames( - className, - 'block-editor-block-preview', - 'editor-styles-wrapper', - { - 'is-scaled': isScaled, - } - ) - } + <div ref={ previewRef } className="block-editor-block-preview__container" aria-hidden> + <Disabled style={ previewStyles } className={ contentClassNames }> + <BlockList /> + </Disabled> + </div> + ); +} + +export function BlockPreview( { blocks, viewportWidth = 700, settings } ) { + const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); + const [ recompute, triggerRecompute ] = useReducer( ( state ) => state + 1, 0 ); + useLayoutEffect( triggerRecompute, [ blocks ] ); + return ( + <BlockEditorProvider + value={ renderedBlocks } + settings={ settings } > - <BlockEditorProvider - value={ castArray( blocks ) } - settings={ settings } - > - <BlockList renderAppender={ false } /> - </BlockEditorProvider> - </Disabled> + { + /* + * The key prop is used to force recomputing the preview + * by remounting the component, ScaledBlockPreview is not meant to + * be rerendered. + */ + } + <ScaledBlockPreview + key={ recompute } + blocks={ renderedBlocks } + viewportWidth={ viewportWidth } + /> + </BlockEditorProvider> ); } /** - * BlockPreview renders a preview given an array of blocks. + * BlockPreview renders a preview of a block or array of blocks. * - * @param {Object} props Component props. + * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/block-preview/README.md * + * @param {Array|Object} blocks A block instance (object) or an array of blocks to be previewed. + * @param {number} viewportWidth Width of the preview container in pixels. Controls at what size the blocks will be rendered inside the preview. Default: 700. * @return {WPElement} Rendered element. */ export default withSelect( ( select ) => { diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index ff42e7b3b4dccd..76c2f4ccaf10a5 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -1,34 +1,87 @@ -.block-editor-block-preview { +// This is the preview that shows up to the right of the thumbnail when hovering. +.block-editor-block-switcher__preview { padding: $block-padding; font-family: $editor-font; overflow: hidden; width: 100%; + pointer-events: none; + display: none; + + @include break-medium { + display: block; + } + + .block-editor-block-preview__content { + font-family: $editor-font; + + > div { + font-family: $editor-font; + } + + &:not(.is-tall-preview) { + // Vertical alignment. + margin-top: 50%; + } + } + + .block-editor-block-preview__title { + margin-bottom: 10px; + color: $dark-gray-300; + } +} + +// These rules ensure the preview scales smoothly regardless of the container size. +.block-editor-block-preview__container { + // In the component, a top padding is provided as an inline style to provid an aspect-ratio. + // This positioning enables the content to sit on top of that padding to fit. + position: relative; + + // The preview component measures the pixel width of this item, so as to calculate the scale factor. + // But without this baseline width, it collapses to 0. + width: 100%; +} + +.block-editor-block-preview__content { + // This element receives inline styles for width, height, and transform-scale. + // Those inline styles are calculated to fit a perfect thumbnail. + + // Position above the padding. + position: absolute; + + // Vertical alignment. It works with the transform: translate(-50%, -50%)`, + top: 0; + left: 0; + + // Important to set the origin. + transform-origin: top left; + + // Resetting paddings, margins, and other. + text-align: initial; + margin: 0; + overflow: visible; + min-height: auto; - // Resetting the block editor paddings and margins .block-editor-block-list__layout, .block-editor-block-list__block { padding: 0; } + .editor-block-list__block-edit [data-block] { - margin-top: 0; + margin: 0; } > div section { height: auto; } - > .reusable-block-indicator { - display: none; + &.is-tall-preview { + top: 4px; } + .block-editor-block-list__insertion-point, + .block-editor-block-drop-zone, + .reusable-block-indicator, .block-list-appender { display: none; } - - &.is-scaled { - > div { - transform: scale(0.9); - transform-origin: center top; - } - } } diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index f9270094e47082..c5f7797aa1db84 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -122,12 +122,7 @@ function BlockStyles( { aria-label={ style.label || style.name } > <div className="editor-block-styles__item-preview block-editor-block-styles__item-preview"> - <BlockPreview - isScaled - blocks={ cloneBlock( block, { - className: styleClassName, - } ) } - /> + <BlockPreview blocks={ cloneBlock( block, { className: styleClassName } ) } /> </div> <div className="editor-block-styles__item-label block-editor-block-styles__item-label"> { style.label || style.name } diff --git a/packages/block-editor/src/components/block-styles/style.scss b/packages/block-editor/src/components/block-styles/style.scss index 988e7563878fc1..e5eb662d93b1f8 100644 --- a/packages/block-editor/src/components/block-styles/style.scss +++ b/packages/block-editor/src/components/block-styles/style.scss @@ -12,6 +12,7 @@ overflow: hidden; border-radius: $radius-round-rectangle; padding: $grid-size-small * 1.5; + padding-top: calc(50% * 0.75 - #{ $grid-size-small } * 1.5); &:focus { @include block-style__focus(); @@ -30,28 +31,21 @@ } } +// Show a little preview thumbnail for style variations. .block-editor-block-styles__item-preview { outline: $border-width solid transparent; // Shown in Windows High Contrast mode. - border: 1px solid rgba($dark-gray-900, 0.2); - overflow: hidden; padding: 0; - text-align: initial; + border: $border-width solid rgba($dark-gray-900, 0.2); border-radius: $radius-round-rectangle; display: flex; - height: 60px; + overflow: hidden; background: $white; + padding-top: 75%; + margin-top: -75%; - // Actual preview contents. - .block-editor-block-preview__content { - transform: scale(0.7); - transform-origin: center center; - width: 100%; - - // Unset some of the styles that might be inherited from the editor style. - margin: 0; - padding: 0; - overflow: visible; - min-height: auto; + .block-editor-block-preview__container { + padding-top: 0; + margin-top: -75%; } } diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index f6d3328d2fd86b..fd732b40c5318d 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -4,11 +4,22 @@ * in cases where isolated behaviors need remote access to a block node. * * @param {string} clientId Block client ID. + * @param {Element} scope an optional DOM Element to which the selector should be scoped * * @return {Element} Block DOM node. */ -export function getBlockDOMNode( clientId ) { - return document.querySelector( '[data-block="' + clientId + '"]' ); +export function getBlockDOMNode( clientId, scope = document ) { + return scope.querySelector( '[data-block="' + clientId + '"]' ); +} + +export function getBlockPreviewContainerDOMNode( clientId, scope ) { + const domNode = getBlockDOMNode( clientId, scope ); + + if ( ! domNode ) { + return; + } + + return domNode.firstChild || domNode; } /** diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index b258486b989203..e70cc00b278540 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -33,6 +33,10 @@ import { const { getComputedStyle } = window; const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { + if ( node ) { + node.classList.add( 'wp-block-button-wrapper' ); + } + const { textColor, backgroundColor } = ownProps; const backgroundColorValue = backgroundColor && backgroundColor.color; const textColorValue = textColor && textColor.color; diff --git a/packages/block-library/src/button/editor.scss b/packages/block-library/src/button/editor.scss index 9fe1a9afabd1bd..da2116d2fc492d 100644 --- a/packages/block-library/src/button/editor.scss +++ b/packages/block-library/src/button/editor.scss @@ -39,22 +39,6 @@ [data-rich-text-placeholder]::after { opacity: 0.8; } - - // Don't let the placeholder text wrap in the variation preview. - .block-editor-block-preview__content & { - max-width: 100%; - - .wp-block-button__link { - max-width: 100%; - overflow: hidden; - // Override is allowed here only because the rich text instance in - // a preview is not editable. - // To do: use the `save` function to preview a block transform, not - // the `edit` function. - white-space: nowrap !important; - text-overflow: ellipsis; - } - } } .wp-block-button__inline-link { @@ -91,3 +75,7 @@ margin-top: $grid-size-large; } } + +.wp-block-button-wrapper { + display: table; +} From 0ca4756032f388a777965fee5b38fb4c932adaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Wed, 7 Aug 2019 23:47:16 +0200 Subject: [PATCH 634/664] RichText: Fix intermittent test failure (#16952) * Fix intermittent test failure * Add comment --- packages/e2e-tests/specs/rich-text.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index d9b674b7940e06..4a45faa12a3011 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -239,6 +239,9 @@ describe( 'RichText', () => { activeElement.blur(); activeElement.focus(); } ); + // Wait for the next animation frame, see the focus event listener in + // RichText. + await page.evaluate( () => new Promise( window.requestAnimationFrame ) ); await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( '2' ); await pressKeyWithModifier( 'primary', 'b' ); From 4665d7074dfeae43eac02b1d2e4ad5713832078e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 8 Aug 2019 07:19:46 +0200 Subject: [PATCH 635/664] Docs: Clarify how to update a dependency for existing packages (#16923) --- packages/README.md | 52 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/README.md b/packages/README.md index b5296fa094b3de..3937b7adee5a3f 100644 --- a/packages/README.md +++ b/packages/README.md @@ -1,8 +1,8 @@ -## Managing Packages +# Managing Packages This repository uses [lerna] to manage WordPress modules and publish them as packages to [npm]. -### Creating a New Package +## Creating a New Package When creating a new package, you need to provide at least the following: @@ -48,13 +48,17 @@ When creating a new package, you need to provide at least the following: - Usage example - `Code is Poetry` logo (`<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>`) -### Adding New Dependencies +## Managing Dependencies There are two types of dependencies that you might want to add to one of the existing WordPress packages. -#### Production Dependencies +### Production Dependencies -Production dependencies are stored in the `dependencies` section of the package’s `package.json` file. The simplest way to add such a dependency to one of the packages is to run a very convenient [lerna add](https://github.com/lerna/lerna/tree/master/commands/add#readme) command from the root of the project. +Production dependencies are stored in the `dependencies` section of the package’s `package.json` file. + +#### Adding New Dependencies + +The simplest way to add a production dependency to one of the packages is to run a very convenient [lerna add](https://github.com/lerna/lerna/tree/master/commands/add#readme) command from the root of the project. _Example:_ @@ -64,7 +68,33 @@ lerna add lodash packages/a11y This command adds the latest version of `lodash` as a dependency to the `@wordpress/a11y` package, which is located in `packages/a11y` folder. -#### Development Dependencies +#### Removing Existing Dependencies + +Removing a dependency from one of the WordPress packages requires some manual work. You need to remove the line in the corresponding `dependencies` section of the `package.json` file. + +_Example:_ + +```diff ++++ b/packages/scripts/package.json +@@ -43,7 +43,6 @@ + "check-node-version": "^3.1.1", + "cross-spawn": "^5.1.0", + "eslint": "^5.16.0", +- "jest": "^24.7.1", + "jest-puppeteer": "^4.0.0", + "minimist": "^1.2.0", + "npm-package-json-lint": "^3.6.0", +``` + +Next, you need to run `npm install` in the root of the project to ensure that `package-lock.json` file gets properly regenerated. + +#### Updating Existing Dependencies + +This is the most confusing part of working with [lerna] which causes a lot of hassles for contributors. The most successful strategy so far is to do the following: + 1. First, remove the existing dependency as described in the previous section. + 2. Next, add the same dependency back as described in the first section of this chapter. This time it wil get the latest version applied unless you enforce a different version explicitly. + +### Development Dependencies In contrast to production dependencies, development dependencies shouldn't be stored in individual WordPress packages. Instead they should be installed in the project's `package.json` file using the usual `npm install` command. In effect, all development tools are configured to work with every package at the same time to ensure they share the same characteristics and integrate correctly with each other. @@ -76,7 +106,7 @@ npm install glob --save-dev This commands adds the latest version of `glob` as a development dependency to the `package.json` file. It has to be executed from the root of the project. -### Maintaining Changelogs +## Maintaining Changelogs In maintaining dozens of npm packages, it can be tough to keep track of changes. To simplify the release process, each package includes a `CHANGELOG.md` file which details all published releases and the unreleased ("Master") changes, if any exist. @@ -106,13 +136,13 @@ When in doubt, refer to [Semantic Versioning specification](https://semver.org/) If you are publishing new versions of packages, note that there are versioning recommendations outlined in the [Gutenberg Release Process document](https://github.com/WordPress/gutenberg/blob/master/docs/contributors/release.md) which prescribe _minimum_ version bumps for specific types of releases. The chosen version should be the greater of the two between the semantic versioning and Gutenberg release minimum version bumps. -### Releasing Packages +## Releasing Packages Lerna automatically releases all outdated packages. To check which packages are outdated and will be released, type `npm run publish:check`. If you have the ability to publish packages, you _must_ have [2FA enabled](https://docs.npmjs.com/getting-started/using-two-factor-authentication) on your [npm account][npm]. -#### Before Releasing +### Before Releasing Confirm that you're logged in to [npm], by running `npm whoami`. If you're not logged in, run `npm adduser` to login. @@ -128,7 +158,7 @@ If you're publishing a new package, ensure that its `package.json` file contains You can check your package configs by running `npm run lint-pkg-json`. -#### Development Release +### Development Release Run the following command to release a dev version of the outdated packages. @@ -140,7 +170,7 @@ Lerna will ask you which version number you want to choose for each package. For Lerna will then publish to [npm], commit the `package.json` changes and create the git tags. -#### Production Release +### Production Release To release a production version for the outdated packages, run the following command: From 5e79106a4e760403ec2aed385bc6632f87b03163 Mon Sep 17 00:00:00 2001 From: Mark Uraine <uraine@gmail.com> Date: Thu, 8 Aug 2019 02:42:13 -0700 Subject: [PATCH 636/664] Modifies shortcut hierarchy in modal (#16724) * Fixes #16691. Moves the shortcut to open shortcut modal to it's own section right under the modal header. * Only render shortcut title when a value is provided * Use classname to modify style for first shortcut * Update snapshots * Using variable for elements top-margin for positioning. --- .../keyboard-shortcut-help-modal/config.js | 13 ++++++++++--- .../keyboard-shortcut-help-modal/index.js | 13 ++++++++----- .../keyboard-shortcut-help-modal/style.scss | 7 +++++-- .../test/__snapshots__/index.js.snap | 16 ++++++++++++---- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js index 340efee1c5d55d..b540f6103aff04 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js @@ -20,13 +20,19 @@ const { ctrlShift, } = displayShortcutList; -const globalShortcuts = { - title: __( 'Global shortcuts' ), +const mainShortcut = { + className: 'edit-post-keyboard-shortcut-help__main-shortcuts', shortcuts: [ { keyCombination: access( 'h' ), - description: __( 'Display this help.' ), + description: __( 'Display these keyboard shortcuts.' ), }, + ], +}; + +const globalShortcuts = { + title: __( 'Global shortcuts' ), + shortcuts: [ { keyCombination: primary( 's' ), description: __( 'Save your changes.' ), @@ -148,6 +154,7 @@ const textFormattingShortcuts = { }; export default [ + mainShortcut, globalShortcuts, selectionShortcuts, blockShortcuts, diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index a077cc6fe6fe95..33321ea53aed83 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -2,6 +2,7 @@ * External dependencies */ import { castArray } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies @@ -64,11 +65,13 @@ const ShortcutList = ( { shortcuts } ) => ( </ul> ); -const ShortcutSection = ( { title, shortcuts } ) => ( - <section className="edit-post-keyboard-shortcut-help__section"> - <h2 className="edit-post-keyboard-shortcut-help__section-title"> - { title } - </h2> +const ShortcutSection = ( { title, shortcuts, className } ) => ( + <section className={ classnames( 'edit-post-keyboard-shortcut-help__section', className ) }> + { !! title && ( + <h2 className="edit-post-keyboard-shortcut-help__section-title"> + { title } + </h2> + ) } <ShortcutList shortcuts={ shortcuts } /> </section> ); diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss index e6bcc5f43137e9..cef4621547840d 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss @@ -1,9 +1,12 @@ .edit-post-keyboard-shortcut-help { - &__section { margin: 0 0 2rem 0; } + &__main-shortcuts .edit-post-keyboard-shortcut-help__shortcut-list { + // Push the shortcut to be flush with top modal header. + margin-top: -$panel-padding -$border-width; + } &__section-title { font-size: 0.9rem; @@ -31,7 +34,7 @@ flex: 1; margin: 0; - // IE 11 flex item fix - ensure the item does not collapse + // IE 11 flex item fix - ensure the item does not collapse. flex-basis: auto; } diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap index e08b7e6501cdcb..63bb5751a130a3 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap @@ -17,11 +17,12 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ title="Keyboard Shortcuts" > <ShortcutSection + className="edit-post-keyboard-shortcut-help__main-shortcuts" key="0" shortcuts={ Array [ Object { - "description": "Display this help.", + "description": "Display these keyboard shortcuts.", "keyCombination": Array [ "Shift", "+", @@ -30,6 +31,13 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ "H", ], }, + ] + } + /> + <ShortcutSection + key="1" + shortcuts={ + Array [ Object { "description": "Save your changes.", "keyCombination": Array [ @@ -142,7 +150,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ title="Global shortcuts" /> <ShortcutSection - key="1" + key="2" shortcuts={ Array [ Object { @@ -163,7 +171,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ title="Selection shortcuts" /> <ShortcutSection - key="2" + key="3" shortcuts={ Array [ Object { @@ -216,7 +224,7 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ title="Block shortcuts" /> <ShortcutSection - key="3" + key="4" shortcuts={ Array [ Object { From 8a052be83d49b10fca76521b5b3182ac9d1ccb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 8 Aug 2019 11:46:01 +0200 Subject: [PATCH 637/664] Components: Stop using `react-click-outside` as a depenency (#16878) --- package-lock.json | 16 ---------------- packages/components/CHANGELOG.md | 1 + packages/components/package.json | 1 - packages/components/src/modal/frame.js | 12 ++++-------- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2413d9d0c96d27..99732d520f9267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4727,7 +4727,6 @@ "moment": "^2.22.1", "mousetrap": "^1.6.2", "re-resizable": "^5.0.1", - "react-click-outside": "^3.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", "rememo": "^3.0.0", @@ -21573,21 +21572,6 @@ "prop-types": "^15.5.6" } }, - "react-click-outside": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/react-click-outside/-/react-click-outside-3.0.1.tgz", - "integrity": "sha512-d0KWFvBt+esoZUF15rL2UBB7jkeAqLU8L/Ny35oLK6fW6mIbOv/ChD+ExF4sR9PD26kVx+9hNfD0FTIqRZEyRQ==", - "requires": { - "hoist-non-react-statics": "^2.1.1" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - } - } - }, "react-dates": { "version": "17.2.0", "resolved": "https://registry.npmjs.org/react-dates/-/react-dates-17.2.0.tgz", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index de312a2b2e7615..afe7a14e81421a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -17,6 +17,7 @@ - The `Dropdown` component has been refactored to focus changes using the `Popover` component's `onFocusOutside` prop. - The `MenuItem` component will now always use an `IconButton`. This prevents a focus loss when clicking a menu item. +- Package no longer depends on external `react-click-outside` library. ## 8.0.0 (2019-06-12) diff --git a/packages/components/package.json b/packages/components/package.json index 7421ef260b5439..c8c1df080d67a5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -42,7 +42,6 @@ "moment": "^2.22.1", "mousetrap": "^1.6.2", "re-resizable": "^5.0.1", - "react-click-outside": "^3.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", "rememo": "^3.0.0", diff --git a/packages/components/src/modal/frame.js b/packages/components/src/modal/frame.js index 04f738e2fa254f..35cb4b96c88d0a 100644 --- a/packages/components/src/modal/frame.js +++ b/packages/components/src/modal/frame.js @@ -6,14 +6,10 @@ import { ESCAPE } from '@wordpress/keycodes'; import { focus } from '@wordpress/dom'; import { withGlobalEvents, compose } from '@wordpress/compose'; -/** - * External dependencies - */ -import clickOutside from 'react-click-outside'; - /** * Internal dependencies */ +import withFocusOutside from '../higher-order/with-focus-outside'; import withFocusReturn from '../higher-order/with-focus-return'; import withConstrainedTabbing from '../higher-order/with-constrained-tabbing'; @@ -23,7 +19,7 @@ class ModalFrame extends Component { this.containerRef = createRef(); this.handleKeyDown = this.handleKeyDown.bind( this ); - this.handleClickOutside = this.handleClickOutside.bind( this ); + this.handleFocusOutside = this.handleFocusOutside.bind( this ); this.focusFirstTabbable = this.focusFirstTabbable.bind( this ); } @@ -52,7 +48,7 @@ class ModalFrame extends Component { * * @param {Object} event Mouse click event. */ - handleClickOutside( event ) { + handleFocusOutside( event ) { if ( this.props.shouldCloseOnClickOutside ) { this.onRequestClose( event ); } @@ -133,7 +129,7 @@ class ModalFrame extends Component { export default compose( [ withFocusReturn, withConstrainedTabbing, - clickOutside, + withFocusOutside, withGlobalEvents( { keydown: 'handleKeyDown', } ), From dc988298c0408b88c75a4e850295cf59ee694b96 Mon Sep 17 00:00:00 2001 From: Manzoor Wani <manzoorwani.jk@gmail.com> Date: Thu, 8 Aug 2019 15:46:50 +0530 Subject: [PATCH 638/664] Extend Moment.js localization (#16728) * Update l10n strings for moment.js The incorrect format specifiers for existing values of `relativeTime` are fixed. Also the other strings were hard-coded and thus ignored by `setSettings` when passed in `dateSettings ` * Create tests for Moment.js localization * Fix the typo * Remove the exposed moment API * Update tests for localization --- packages/date/src/index.js | 35 +++++++++++++++++---------------- packages/date/src/test/index.js | 32 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/packages/date/src/index.js b/packages/date/src/index.js index 14aa677d2e4218..39b4425ea2da22 100644 --- a/packages/date/src/index.js +++ b/packages/date/src/index.js @@ -11,13 +11,28 @@ const WP_ZONE = 'WP'; // well because it uses the `setSettings()` function to change these settings. let settings = { l10n: { - locale: 'en_US', + locale: 'en', months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], weekdays: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], meridiem: { am: 'am', pm: 'pm', AM: 'AM', PM: 'PM' }, - relative: { future: ' % s from now', past: '% s ago' }, + relative: { + future: '%s from now', + past: '%s ago', + s: 'a few seconds', + ss: '%d seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years', + }, }, formats: { time: 'g: i a', @@ -61,21 +76,7 @@ export function setSettings( dateSettings ) { }, // From human_time_diff? // Set to `(number, withoutSuffix, key, isFuture) => {}` instead. - relativeTime: { - future: dateSettings.l10n.relative.future, - past: dateSettings.l10n.relative.past, - s: 'seconds', - m: 'a minute', - mm: '%d minutes', - h: 'an hour', - hh: '%d hours', - d: 'a day', - dd: '%d days', - M: 'a month', - MM: '%d months', - y: 'a year', - yy: '%d years', - }, + relativeTime: dateSettings.l10n.relative, } ); momentLib.locale( currentLocale ); diff --git a/packages/date/src/test/index.js b/packages/date/src/test/index.js index f18e2b0915825f..b5e17280d67829 100644 --- a/packages/date/src/test/index.js +++ b/packages/date/src/test/index.js @@ -38,3 +38,35 @@ describe( 'isInTheFuture', () => { setSettings( settings ); } ); } ); + +describe( 'Moment.js Localization', () => { + it( 'should change the relative time strings', () => { + const settings = __experimentalGetSettings(); + + // Change the locale strings for tests. + setSettings( { + ...settings, + l10n: { + ...settings.l10n, + relative: { + ...settings.l10n.relative, + mm: '%d localized minutes', + hh: '%d localized hours', + }, + }, + } ); + + // Get the freshly changed setings. + const newSettings = __experimentalGetSettings(); + + // Test the unchanged values. + expect( newSettings.l10n.locale ).toBe( settings.l10n.locale ); + + // Test the changed values. + expect( newSettings.l10n.relative.mm ).toBe( '%d localized minutes' ); + expect( newSettings.l10n.relative.hh ).toBe( '%d localized hours' ); + + // Restore default settings + setSettings( settings ); + } ); +} ); From ea6f2d9d2c12513b0a1ae342e229d66fd233700e Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler <dekervit@gmail.com> Date: Thu, 8 Aug 2019 12:57:59 +0200 Subject: [PATCH 639/664] Fix toolbar bottom inset for iPhone X devices (#16961) --- packages/edit-post/src/components/layout/index.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js index 84f7e5bcde21dd..3242628c021b89 100644 --- a/packages/edit-post/src/components/layout/index.native.js +++ b/packages/edit-post/src/components/layout/index.native.js @@ -108,6 +108,7 @@ class Layout extends Component { ...styles.toolbarKeyboardAvoidingView, left: this.state.safeAreaInsets.left, right: this.state.safeAreaInsets.right, + bottom: this.state.safeAreaInsets.bottom, }; return ( From 67b2328cfc2c0fef6c0ab54f69e1c5aa808f2fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 8 Aug 2019 13:51:20 +0200 Subject: [PATCH 640/664] Chore: Run npm audit fix to fix vulnerabilities (#16963) --- package-lock.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99732d520f9267..843205061c297e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11238,9 +11238,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extend-shallow": { @@ -21046,9 +21046,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true } } @@ -23094,9 +23094,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "safer-eval": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/safer-eval/-/safer-eval-1.3.2.tgz", - "integrity": "sha512-mAkc9NX+ULPw4qX+lkFVuIhXQbjFaX97fWFj6atFXMG11lUX4rXfQY6Q0VFf1wfJfqCHYO7cxtC7cA8fIkWsLQ==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/safer-eval/-/safer-eval-1.3.5.tgz", + "integrity": "sha512-BJ//K2Y+EgCbOHEsDGS5YahYBcYy7JcFpKDo2ba5t4MnOGHYtk7HvQkcxTDFvjQvJ0CRcdas/PyF+gTTCay+3w==", "dev": true, "requires": { "clones": "^1.2.0" @@ -24890,9 +24890,9 @@ "dev": true }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "slice-ansi": { From 509c9cb18beb140f4e6ec6e87aad2986650cc833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 8 Aug 2019 14:49:25 +0200 Subject: [PATCH 641/664] Block library: Refactor Quote block to use class names for text align (#16779) * Block library: Refactor Quote block to use class names for text align * Refactor CSS theme styles to use new text align classes --- .../block-library/src/quote/deprecated.js | 13 +++++++++++ packages/block-library/src/quote/edit.js | 12 +++++++++- packages/block-library/src/quote/save.js | 11 +++++++++- packages/block-library/src/quote/theme.scss | 8 +++---- .../blocks/core__quote__deprecated-2.html | 6 +++++ .../blocks/core__quote__deprecated-2.json | 14 ++++++++++++ .../core__quote__deprecated-2.parsed.json | 22 +++++++++++++++++++ .../core__quote__deprecated-2.serialized.html | 3 +++ 8 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.serialized.html diff --git a/packages/block-library/src/quote/deprecated.js b/packages/block-library/src/quote/deprecated.js index 61024ff336aed1..5b8637138a2ae8 100644 --- a/packages/block-library/src/quote/deprecated.js +++ b/packages/block-library/src/quote/deprecated.js @@ -28,6 +28,19 @@ const blockAttributes = { }; const deprecated = [ + { + attributes: blockAttributes, + save( { attributes } ) { + const { align, value, citation } = attributes; + + return ( + <blockquote style={ { textAlign: align ? align : null } }> + <RichText.Content multiline value={ value } /> + { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + </blockquote> + ); + }, + }, { attributes: { ...blockAttributes, diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 079db78e86493e..9210230f2ba02b 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -7,6 +12,7 @@ import { BlockQuotation } from '@wordpress/components'; export default function QuoteEdit( { attributes, setAttributes, isSelected, mergeBlocks, onReplace, className } ) { const { align, value, citation } = attributes; + return ( <> <BlockControls> @@ -17,7 +23,11 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg } } /> </BlockControls> - <BlockQuotation className={ className } style={ { textAlign: align } }> + <BlockQuotation + className={ classnames( className, { + [ `has-text-align-${ align }` ]: align, + } ) } + > <RichText identifier="value" multiline diff --git a/packages/block-library/src/quote/save.js b/packages/block-library/src/quote/save.js index be0fe834fd7647..15d8445d7a740a 100644 --- a/packages/block-library/src/quote/save.js +++ b/packages/block-library/src/quote/save.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -6,8 +11,12 @@ import { RichText } from '@wordpress/block-editor'; export default function save( { attributes } ) { const { align, value, citation } = attributes; + const className = classnames( { + [ `has-text-align-${ align }` ]: align, + } ); + return ( - <blockquote style={ { textAlign: align ? align : null } }> + <blockquote className={ className }> <RichText.Content multiline value={ value } /> { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } </blockquote> diff --git a/packages/block-library/src/quote/theme.scss b/packages/block-library/src/quote/theme.scss index c3b684b8e2b159..fe3d90cd6a2567 100644 --- a/packages/block-library/src/quote/theme.scss +++ b/packages/block-library/src/quote/theme.scss @@ -13,16 +13,16 @@ font-style: normal; } - &[style*="text-align:right"], - &[style*="text-align: right"] { + &.has-text-align-right, + &.has-text-align-right { border-left: none; border-right: 4px solid $black; padding-left: 0; padding-right: 1em; } - &[style*="text-align:center"], - &[style*="text-align: center"] { + &.has-text-align-center, + &.has-text-align-center { border: none; padding-left: 0; } diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.html b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.html new file mode 100644 index 00000000000000..1aa27ba31ca7ef --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.html @@ -0,0 +1,6 @@ +<!-- wp:core/quote {"align":"right"} --> +<blockquote style="text-align:right" class="wp-block-quote"> + <p>Testing deprecated quote block...</p> + <cite>...with a caption</cite> +</blockquote> +<!-- /wp:core/quote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.json b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.json new file mode 100644 index 00000000000000..7270825d5ea7ec --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/quote", + "isValid": true, + "attributes": { + "value": "<p>Testing deprecated quote block...</p>", + "citation": "...with a caption", + "align": "right" + }, + "innerBlocks": [], + "originalContent": "<blockquote style=\"text-align:right\" class=\"wp-block-quote\">\n\t<p>Testing deprecated quote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.parsed.json b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.parsed.json new file mode 100644 index 00000000000000..37bd661148acaa --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/quote", + "attrs": { + "align": "right" + }, + "innerBlocks": [], + "innerHTML": "\n<blockquote style=\"text-align:right\" class=\"wp-block-quote\">\n\t<p>Testing deprecated quote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>\n", + "innerContent": [ + "\n<blockquote style=\"text-align:right\" class=\"wp-block-quote\">\n\t<p>Testing deprecated quote block...</p>\n\t<cite>...with a caption</cite>\n</blockquote>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.serialized.html new file mode 100644 index 00000000000000..08271ab418b33b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__quote__deprecated-2.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:quote {"align":"right"} --> +<blockquote class="wp-block-quote has-text-align-right"><p>Testing deprecated quote block...</p><cite>...with a caption</cite></blockquote> +<!-- /wp:quote --> From b5e96136bdcdd1aaa9b922863584517b71c895d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Thu, 8 Aug 2019 15:53:02 +0200 Subject: [PATCH 642/664] Block library: Refactor Paragraph block to use class name for text align (#16794) * Block library: Refactor Paragraph block to use class name for text align * Fix demo post to use text align class for Paragraph blocks * Update snapshots for failing e2e tests * Improve code readabilit for Paragraph block --- .../block-library/src/paragraph/deprecated.js | 49 +++++++++++++++++++ packages/block-library/src/paragraph/edit.js | 2 +- packages/block-library/src/paragraph/save.js | 2 +- .../fixtures/blocks/core__cover.html | 2 +- .../fixtures/blocks/core__cover.json | 2 +- .../fixtures/blocks/core__cover.parsed.json | 4 +- .../blocks/core__cover.serialized.html | 2 +- .../core__cover__deprecated-1.serialized.html | 2 +- .../core__cover__deprecated-2.serialized.html | 2 +- .../core__cover__deprecated-3.serialized.html | 2 +- .../blocks/core__cover__video-overlay.html | 2 +- .../blocks/core__cover__video-overlay.json | 2 +- .../core__cover__video-overlay.parsed.json | 4 +- ...core__cover__video-overlay.serialized.html | 2 +- .../fixtures/blocks/core__cover__video.html | 2 +- .../fixtures/blocks/core__cover__video.json | 2 +- .../blocks/core__cover__video.parsed.json | 4 +- .../blocks/core__cover__video.serialized.html | 2 +- ..._media-text__media-right-custom-width.html | 2 +- ..._media-text__media-right-custom-width.json | 2 +- ...text__media-right-custom-width.parsed.json | 4 +- ...__media-right-custom-width.serialized.html | 2 +- .../blocks/core__paragraph__align-right.html | 2 +- .../blocks/core__paragraph__align-right.json | 2 +- .../core__paragraph__align-right.parsed.json | 4 +- ...re__paragraph__align-right.serialized.html | 2 +- .../blocks/core__paragraph__deprecated-4.html | 3 ++ .../blocks/core__paragraph__deprecated-4.json | 14 ++++++ .../core__paragraph__deprecated-4.parsed.json | 22 +++++++++ ...e__paragraph__deprecated-4.serialized.html | 3 ++ .../block-transforms.test.js.snap | 18 +++---- post-content.php | 10 ++-- 32 files changed, 135 insertions(+), 44 deletions(-) create mode 100644 packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.html create mode 100644 packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.parsed.json create mode 100644 packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.serialized.html diff --git a/packages/block-library/src/paragraph/deprecated.js b/packages/block-library/src/paragraph/deprecated.js index c3f18db62d62c3..d7871b8854ca3a 100644 --- a/packages/block-library/src/paragraph/deprecated.js +++ b/packages/block-library/src/paragraph/deprecated.js @@ -12,6 +12,7 @@ import { } from '@wordpress/element'; import { getColorClassName, + getFontSizeClass, RichText, } from '@wordpress/block-editor'; @@ -61,6 +62,54 @@ const blockAttributes = { }; const deprecated = [ + { + supports, + attributes: blockAttributes, + save( { attributes } ) { + const { + align, + content, + dropCap, + backgroundColor, + textColor, + customBackgroundColor, + customTextColor, + fontSize, + customFontSize, + direction, + } = attributes; + + const textClass = getColorClassName( 'color', textColor ); + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const fontSizeClass = getFontSizeClass( fontSize ); + + const className = classnames( { + 'has-text-color': textColor || customTextColor, + 'has-background': backgroundColor || customBackgroundColor, + 'has-drop-cap': dropCap, + [ fontSizeClass ]: fontSizeClass, + [ textClass ]: textClass, + [ backgroundClass ]: backgroundClass, + } ); + + const styles = { + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + color: textClass ? undefined : customTextColor, + fontSize: fontSizeClass ? undefined : customFontSize, + textAlign: align, + }; + + return ( + <RichText.Content + tagName="p" + style={ styles } + className={ className ? className : undefined } + value={ content } + dir={ direction } + /> + ); + }, + }, { supports, attributes: { diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 021a7369f3422a..c40fae904906fc 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -163,6 +163,7 @@ class ParagraphBlock extends Component { 'has-text-color': textColor.color, 'has-background': backgroundColor.color, 'has-drop-cap': dropCap, + [ `has-text-align-${ align }` ]: align, [ backgroundColor.class ]: backgroundColor.class, [ textColor.class ]: textColor.class, [ fontSize.class ]: fontSize.class, @@ -171,7 +172,6 @@ class ParagraphBlock extends Component { backgroundColor: backgroundColor.color, color: textColor.color, fontSize: fontSize.size ? fontSize.size + 'px' : undefined, - textAlign: align, direction, } } value={ content } diff --git a/packages/block-library/src/paragraph/save.js b/packages/block-library/src/paragraph/save.js index b4c3b7c02d0831..b2c1f47a4f32a2 100644 --- a/packages/block-library/src/paragraph/save.js +++ b/packages/block-library/src/paragraph/save.js @@ -34,6 +34,7 @@ export default function save( { attributes } ) { 'has-text-color': textColor || customTextColor, 'has-background': backgroundColor || customBackgroundColor, 'has-drop-cap': dropCap, + [ `has-text-align-${ align }` ]: align, [ fontSizeClass ]: fontSizeClass, [ textClass ]: textClass, [ backgroundClass ]: backgroundClass, @@ -43,7 +44,6 @@ export default function save( { attributes } ) { backgroundColor: backgroundClass ? undefined : customBackgroundColor, color: textClass ? undefined : customTextColor, fontSize: fontSizeClass ? undefined : customFontSize, - textAlign: align, }; return ( diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.html b/packages/e2e-tests/fixtures/blocks/core__cover.html index bea14565390663..00094d59af0fa8 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover.html @@ -5,7 +5,7 @@ > <div class="wp-block-cover__inner-container"> <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> - <p style="text-align:center" class="has-large-font-size"> + <p class="has-text-align-center has-large-font-size"> Guten Berg! </p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.json b/packages/e2e-tests/fixtures/blocks/core__cover.json index 93776fce2cdc04..2092e1fc58a1a1 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover.json @@ -22,7 +22,7 @@ "fontSize": "large" }, "innerBlocks": [], - "originalContent": "<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" + "originalContent": "<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" } ], "originalContent": "<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n\tstyle=\"background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)\"\n>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json index d682ebabe0b9f5..c38b616c41d0d7 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover.parsed.json @@ -14,9 +14,9 @@ "fontSize": "large" }, "innerBlocks": [], - "innerHTML": "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", "innerContent": [ - "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" + "\n\t\t<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" ] } ], diff --git a/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html index 0539f03f168f8f..5c22f650550e6a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover.serialized.html @@ -1,6 +1,6 @@ <!-- wp:cover {"url":"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=","dimRatio":40} --> <div class="wp-block-cover has-background-dim-40 has-background-dim" style="background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"> +<p class="has-text-align-center has-large-font-size"> Guten Berg! </p> <!-- /wp:paragraph --></div></div> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html index 8c80c4c53a580b..d1a8ceecd39b99 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-1.serialized.html @@ -1,5 +1,5 @@ <!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> <div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"><strong>Cover Image</strong></p> +<p class="has-text-align-center has-large-font-size"><strong>Cover Image</strong></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html index 90849ff27c4565..29c4fc38d07765 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-2.serialized.html @@ -1,5 +1,5 @@ <!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":34} --> <div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"><strong>Cover Block</strong></p> +<p class="has-text-align-center has-large-font-size"><strong>Cover Block</strong></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html index a85404c31646ad..0498afe4a1c8db 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__deprecated-3.serialized.html @@ -1,5 +1,5 @@ <!-- wp:cover {"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==","id":35} --> <div class="wp-block-cover has-background-dim" style="background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"><strong>Cover Block</strong></p> +<p class="has-text-align-center has-large-font-size"><strong>Cover Block</strong></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html index 1eb26c9730be69..4f603c49dd06d5 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.html @@ -13,7 +13,7 @@ </video> <div class="wp-block-cover__inner-container"> <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> - <p style="text-align:center" class="has-large-font-size"> + <p class="has-text-align-center has-large-font-size"> Guten Berg! </p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json index f7e2b42ff234cc..8e2486b3bc128b 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.json @@ -23,7 +23,7 @@ "fontSize": "large" }, "innerBlocks": [], - "originalContent": "<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" + "originalContent": "<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" } ], "originalContent": "<div\n\tclass=\"wp-block-cover has-background-dim-10 has-background-dim\"\n\tstyle=\"background-color:#3615d9\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json index 4b72e0d4148e7e..1a01fe89ce68a7 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.parsed.json @@ -16,9 +16,9 @@ "fontSize": "large" }, "innerBlocks": [], - "innerHTML": "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", "innerContent": [ - "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" + "\n\t\t<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" ] } ], diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html index 831388b0182b87..1a40bd5a65f740 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video-overlay.serialized.html @@ -1,6 +1,6 @@ <!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":10,"customOverlayColor":"#3615d9","backgroundType":"video"} --> <div class="wp-block-cover has-background-dim-10 has-background-dim" style="background-color:#3615d9"><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"> +<p class="has-text-align-center has-large-font-size"> Guten Berg! </p> <!-- /wp:paragraph --></div></div> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.html b/packages/e2e-tests/fixtures/blocks/core__cover__video.html index eb50abcb28cf36..96c1c72ea34110 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.html @@ -12,7 +12,7 @@ </video> <div class="wp-block-cover__inner-container"> <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> - <p style="text-align:center" class="has-large-font-size"> + <p class="has-text-align-center has-large-font-size"> Guten Berg! </p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.json b/packages/e2e-tests/fixtures/blocks/core__cover__video.json index f91f443e785e15..c5a5abeda49162 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.json @@ -22,7 +22,7 @@ "fontSize": "large" }, "innerBlocks": [], - "originalContent": "<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" + "originalContent": "<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>" } ], "originalContent": "<div\n\tclass=\"wp-block-cover has-background-dim-40 has-background-dim\"\n>\n\t<video\n\t\tclass=\"wp-block-cover__video-background\"\n\t\tautoplay\n\t\tmuted\n\t\tloop\n\t\tsrc=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"\n\t>\n\t</video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json index c48b676a2f1c5b..7121277dd3b401 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.parsed.json @@ -15,9 +15,9 @@ "fontSize": "large" }, "innerBlocks": [], - "innerHTML": "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t", "innerContent": [ - "\n\t\t<p style=\"text-align:center\" class=\"has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" + "\n\t\t<p class=\"has-text-align-center has-large-font-size\">\n\t\t\tGuten Berg!\n\t\t</p>\n\t\t" ] } ], diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html index 5f23cddf2fa1c1..5c155d10fa772f 100644 --- a/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__cover__video.serialized.html @@ -1,6 +1,6 @@ <!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":40,"backgroundType":"video"} --> <div class="wp-block-cover has-background-dim-40 has-background-dim"><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"> +<p class="has-text-align-center has-large-font-size"> Guten Berg! </p> <!-- /wp:paragraph --></div></div> diff --git a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.html b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.html index 45252b7a306b8c..57b21c9f28991c 100644 --- a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.html +++ b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.html @@ -5,7 +5,7 @@ </figure> <div class="wp-block-media-text__content"> <!-- wp:paragraph {"align":"right","placeholder":"Content…","fontSize":"large"} --> - <p style="text-align:right" class="has-large-font-size">My video</p> + <p class="has-text-align-right has-large-font-size">My video</p> <!-- /wp:paragraph --> </div> </div> diff --git a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.json b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.json index c31ac7581e2551..ca372909502bac 100644 --- a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.json +++ b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.json @@ -25,7 +25,7 @@ "fontSize": "large" }, "innerBlocks": [], - "originalContent": "<p style=\"text-align:right\" class=\"has-large-font-size\">My video</p>" + "originalContent": "<p class=\"has-text-align-right has-large-font-size\">My video</p>" } ], "originalContent": "<div class=\"wp-block-media-text alignfull has-media-on-the-right\" style=\"grid-template-columns:auto 41%\">\n\t<figure class=\"wp-block-media-text__media\">\n\t\t<video controls src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t</figure>\n\t<div class=\"wp-block-media-text__content\">\n\t\t\n\t</div>\n</div>" diff --git a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.parsed.json b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.parsed.json index ee577a366f0f8a..6939ba85e95b84 100644 --- a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.parsed.json @@ -16,9 +16,9 @@ "fontSize": "large" }, "innerBlocks": [], - "innerHTML": "\n\t\t<p style=\"text-align:right\" class=\"has-large-font-size\">My video</p>\n\t\t", + "innerHTML": "\n\t\t<p class=\"has-text-align-right has-large-font-size\">My video</p>\n\t\t", "innerContent": [ - "\n\t\t<p style=\"text-align:right\" class=\"has-large-font-size\">My video</p>\n\t\t" + "\n\t\t<p class=\"has-text-align-right has-large-font-size\">My video</p>\n\t\t" ] } ], diff --git a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.serialized.html b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.serialized.html index 857ad7de389cd4..2c2923b7ecf25a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__media-text__media-right-custom-width.serialized.html @@ -1,5 +1,5 @@ <!-- wp:media-text {"align":"full","mediaPosition":"right","mediaType":"video","mediaWidth":41} --> <div class="wp-block-media-text alignfull has-media-on-the-right" style="grid-template-columns:auto 41%"><figure class="wp-block-media-text__media"><video controls src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video></figure><div class="wp-block-media-text__content"><!-- wp:paragraph {"align":"right","placeholder":"Content…","fontSize":"large"} --> -<p style="text-align:right" class="has-large-font-size">My video</p> +<p class="has-text-align-right has-large-font-size">My video</p> <!-- /wp:paragraph --></div></div> <!-- /wp:media-text --> diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.html index d41481a57f2e55..e95db5b1554541 100644 --- a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.html +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.html @@ -1,3 +1,3 @@ <!-- wp:core/paragraph {"align":"right"} --> -<p style="text-align:right;">... like this one, which is separate from the above and right aligned.</p> +<p class="has-text-align-right">... like this one, which is separate from the above and right aligned.</p> <!-- /wp:core/paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.json index 2ab7bad89f1928..d0c3fa1495e3df 100644 --- a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.json +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.json @@ -9,6 +9,6 @@ "dropCap": false }, "innerBlocks": [], - "originalContent": "<p style=\"text-align:right;\">... like this one, which is separate from the above and right aligned.</p>" + "originalContent": "<p class=\"has-text-align-right\">... like this one, which is separate from the above and right aligned.</p>" } ] diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.parsed.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.parsed.json index a8f850f47f72b5..61859738751fb8 100644 --- a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.parsed.json @@ -5,9 +5,9 @@ "align": "right" }, "innerBlocks": [], - "innerHTML": "\n<p style=\"text-align:right;\">... like this one, which is separate from the above and right aligned.</p>\n", + "innerHTML": "\n<p class=\"has-text-align-right\">... like this one, which is separate from the above and right aligned.</p>\n", "innerContent": [ - "\n<p style=\"text-align:right;\">... like this one, which is separate from the above and right aligned.</p>\n" + "\n<p class=\"has-text-align-right\">... like this one, which is separate from the above and right aligned.</p>\n" ] }, { diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.serialized.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.serialized.html index 3e18265b5c44b1..3009f606904f61 100644 --- a/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__align-right.serialized.html @@ -1,3 +1,3 @@ <!-- wp:paragraph {"align":"right"} --> -<p style="text-align:right">... like this one, which is separate from the above and right aligned.</p> +<p class="has-text-align-right">... like this one, which is separate from the above and right aligned.</p> <!-- /wp:paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.html new file mode 100644 index 00000000000000..d41481a57f2e55 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.html @@ -0,0 +1,3 @@ +<!-- wp:core/paragraph {"align":"right"} --> +<p style="text-align:right;">... like this one, which is separate from the above and right aligned.</p> +<!-- /wp:core/paragraph --> diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.json new file mode 100644 index 00000000000000..2ab7bad89f1928 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.json @@ -0,0 +1,14 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "right", + "content": "... like this one, which is separate from the above and right aligned.", + "dropCap": false + }, + "innerBlocks": [], + "originalContent": "<p style=\"text-align:right;\">... like this one, which is separate from the above and right aligned.</p>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.parsed.json b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.parsed.json new file mode 100644 index 00000000000000..a8f850f47f72b5 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/paragraph", + "attrs": { + "align": "right" + }, + "innerBlocks": [], + "innerHTML": "\n<p style=\"text-align:right;\">... like this one, which is separate from the above and right aligned.</p>\n", + "innerContent": [ + "\n<p style=\"text-align:right;\">... like this one, which is separate from the above and right aligned.</p>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.serialized.html b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.serialized.html new file mode 100644 index 00000000000000..3009f606904f61 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__paragraph__deprecated-4.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:paragraph {"align":"right"} --> +<p class="has-text-align-right">... like this one, which is separate from the above and right aligned.</p> +<!-- /wp:paragraph --> diff --git a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap index f1d778e920aeba..3288fac0c027a6 100644 --- a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap @@ -79,7 +79,7 @@ exports[`Block transforms correctly transform block Heading in fixture core__hea exports[`Block transforms correctly transform block Image in fixture core__image into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> <div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -115,7 +115,7 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image__attachment-link into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> <div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -143,7 +143,7 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image__center-caption into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\",\\"align\\":\\"center\\"} --> <div class=\\"wp-block-cover aligncenter has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -171,7 +171,7 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image__custom-link into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> <div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -199,7 +199,7 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-class into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> <div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -227,7 +227,7 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-rel into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> <div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -255,7 +255,7 @@ exports[`Block transforms correctly transform block Image in fixture core__image exports[`Block transforms correctly transform block Image in fixture core__image__media-link into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> <div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; @@ -363,7 +363,7 @@ exports[`Block transforms correctly transform block Media & Text in fixture core exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Group block 1`] = ` "<!-- wp:group --> <div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"right\\"} --> -<p style=\\"text-align:right\\">... like this one, which is separate from the above and right aligned.</p> +<p class=\\"has-text-align-right\\">... like this one, which is separate from the above and right aligned.</p> <!-- /wp:paragraph --></div></div> <!-- /wp:group -->" `; @@ -489,7 +489,7 @@ exports[`Block transforms correctly transform block Verse in fixture core__verse exports[`Block transforms correctly transform block Video in fixture core__video into the Cover block 1`] = ` "<!-- wp:cover {\\"url\\":\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\",\\"backgroundType\\":\\"video\\"} --> <div class=\\"wp-block-cover has-background-dim\\"><video class=\\"wp-block-cover__video-background\\" autoplay muted loop src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p style=\\"text-align:center\\" class=\\"has-large-font-size\\"></p> +<p class=\\"has-text-align-center has-large-font-size\\"></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover -->" `; diff --git a/post-content.php b/post-content.php index 032c4ad10be6ab..1a5014c061a2e4 100644 --- a/post-content.php +++ b/post-content.php @@ -8,7 +8,7 @@ ?> <!-- wp:cover {"url":"https://cldup.com/Fz-ASbo2s3.jpg","className":"alignwide"} --> <div class="wp-block-cover has-background-dim alignwide" style="background-image:url(https://cldup.com/Fz-ASbo2s3.jpg)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> -<p style="text-align:center" class="has-large-font-size"><?php _e( 'Of Mountains &amp; Printing Presses', 'gutenberg' ); ?></p> +<p class="has-text-align-center has-large-font-size"><?php _e( 'Of Mountains &amp; Printing Presses', 'gutenberg' ); ?></p> <!-- /wp:paragraph --></div></div> <!-- /wp:cover --> @@ -21,7 +21,7 @@ <!-- /wp:paragraph --> <!-- wp:paragraph {"align":"right"} --> -<p style="text-align:right"><?php _e( '... like this one, which is right aligned.', 'gutenberg' ); ?></p> +<p class="has-text-align-right"><?php _e( '... like this one, which is right aligned.', 'gutenberg' ); ?></p> <!-- /wp:paragraph --> <!-- wp:paragraph --> @@ -156,7 +156,7 @@ <!-- /wp:pullquote --> <!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center"> +<p class="has-text-align-center"> <em> <?php echo sprintf( @@ -178,9 +178,9 @@ <!-- /wp:separator --> <!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center"><?php _e( 'Thanks for testing Gutenberg!', 'gutenberg' ); ?></p> +<p class="has-text-align-center"><?php _e( 'Thanks for testing Gutenberg!', 'gutenberg' ); ?></p> <!-- /wp:paragraph --> <!-- wp:paragraph {"align":"center"} --> -<p style="text-align:center">👋</p> +<p class="has-text-align-center">👋</p> <!-- /wp:paragraph --> From 44c9ec0168e24443d19d00172cda9b2a3c64d157 Mon Sep 17 00:00:00 2001 From: Courtney <9257264+cburton4@users.noreply.github.com> Date: Thu, 8 Aug 2019 10:00:09 -0400 Subject: [PATCH 643/664] ClipboardButton Component: Add description and image (#16758) * ClipboardButton Component: Add description and image * Copy tweak Edited copy to remove language like, "allow." --- packages/components/src/clipboard-button/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/components/src/clipboard-button/README.md b/packages/components/src/clipboard-button/README.md index 8a53e8cb2015a2..164af3cef49277 100644 --- a/packages/components/src/clipboard-button/README.md +++ b/packages/components/src/clipboard-button/README.md @@ -1,5 +1,10 @@ # ClipboardButton +With a clipboard button, users copy text (or other elements) with a single click or tap. + +![Clipboard button component](https://wordpress.org/gutenberg/files/2019/07/clipboard-button-2-1.png) + + ## Usage ```jsx From 07d99a7b45115dca4f5619b2f9dc3e1308a97f09 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Thu, 8 Aug 2019 16:04:04 +0100 Subject: [PATCH 644/664] Fix: Don't render drop zone bellow the default block appender. (#16119) --- .../src/components/block-drop-zone/index.js | 6 ++++-- .../src/components/block-drop-zone/style.scss | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js index e0a0d1060ce4fe..adac488e4c3d7f 100644 --- a/packages/block-editor/src/components/block-drop-zone/index.js +++ b/packages/block-editor/src/components/block-drop-zone/index.js @@ -58,7 +58,7 @@ class BlockDropZone extends Component { const { clientId, rootClientId, getBlockIndex } = this.props; if ( clientId !== undefined ) { const index = getBlockIndex( clientId, rootClientId ); - return position.y === 'top' ? index : index + 1; + return ( position && position.y === 'top' ) ? index : index + 1; } } @@ -111,10 +111,12 @@ class BlockDropZone extends Component { } render() { - const { isLockedAll, index } = this.props; + const { isLockedAll } = this.props; if ( isLockedAll ) { return null; } + + const index = this.getInsertIndex(); const isAppender = index === undefined; return ( diff --git a/packages/block-editor/src/components/block-drop-zone/style.scss b/packages/block-editor/src/components/block-drop-zone/style.scss index 5eef5751111527..8135c66f32ffa0 100644 --- a/packages/block-editor/src/components/block-drop-zone/style.scss +++ b/packages/block-editor/src/components/block-drop-zone/style.scss @@ -8,16 +8,20 @@ display: none; } - &.is-close-to-bottom { + &.is-close-to-bottom, + &.is-close-to-top { background: none; - border-bottom: 3px solid theme(primary); } - &.is-close-to-top, - &.is-appender.is-close-to-top, - &.is-appender.is-close-to-bottom { - background: none; + &.is-close-to-top { border-top: 3px solid theme(primary); + } + + &.is-close-to-bottom { + border-bottom: 3px solid theme(primary); + } + + &.is-appender.is-active.is-dragging-over-document { border-bottom: none; } } From 1c7794121a74fb8458e660f5c521c4915a69fffb Mon Sep 17 00:00:00 2001 From: Kjell Reigstad <kjell.reigstad@automattic.com> Date: Thu, 8 Aug 2019 11:28:41 -0400 Subject: [PATCH 645/664] Make all notice-list notices the same min-height (#16891) * Make all notice-list notices the same min-height. * Undo all the absolute positioning changes. This reverts commit ef2af48aa9959e59ce47943209a30dd548ad0abe. * Try using flexbox to properly center things. * Minor: Adjust dismiss button margin. * Add code comments. --- packages/components/src/notice/style.scss | 23 ++++++++++++++----- .../src/components/editor-notices/style.scss | 7 ++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/components/src/notice/style.scss b/packages/components/src/notice/style.scss index 3eff8da877313f..8474146bbec680 100644 --- a/packages/components/src/notice/style.scss +++ b/packages/components/src/notice/style.scss @@ -1,10 +1,12 @@ .components-notice { + display: flex; font-family: $default-font; font-size: $default-font-size; background-color: $blue-medium-100; border-left: 4px solid $blue-medium-500; margin: 5px 15px 2px; padding: 8px 12px; + align-items: center; &.is-dismissible { padding-right: 36px; @@ -28,6 +30,7 @@ } .components-notice__content { + flex-grow: 1; margin: $grid-size-small #{ $icon-button-size-small + $border-width } $grid-size-small 0; } @@ -42,10 +45,11 @@ } .components-notice__dismiss { - position: absolute; - top: 0; - right: 0; - color: $dark-gray-500; + color: $dark-gray-300; + + // Place the dismiss button at the top of the container, even when text wraps onto two lines. + align-self: flex-start; + flex-shrink: 0; &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover, &:not(:disabled):not([aria-disabled="true"]):not(.is-default):active, @@ -65,7 +69,14 @@ z-index: z-index(".components-notice-list"); .components-notice__content { - margin-top: 1em; - margin-bottom: 1em; + margin-top: $grid-size + $grid-size-small; + margin-bottom: $grid-size + $grid-size-small; + line-height: 1.6; + } + + // Reduce margins on inline buttons so that they don't add too much extra line-height. + .components-notice__action.components-button { + margin-top: -2px; + margin-bottom: -2px; } } diff --git a/packages/editor/src/components/editor-notices/style.scss b/packages/editor/src/components/editor-notices/style.scss index 4b6d26f226d1dd..330976df2dfe7b 100644 --- a/packages/editor/src/components/editor-notices/style.scss +++ b/packages/editor/src/components/editor-notices/style.scss @@ -25,10 +25,13 @@ box-sizing: border-box; margin: 0 0 5px; padding: 6px 12px; - min-height: $panel-header-height; + // Min-height matches the height of a single-line notice with an action button. + min-height: $header-height + $grid-size-small; + + // Margins ensure that the dismiss button aligns to the center of the first line of text. .components-notice__dismiss { - margin: 10px 5px; + margin: 6px -5px 6px 5px; } } } From 6402f9527432d9b1085f464d9c11e0fdb566b685 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 9 Aug 2019 09:46:47 +0100 Subject: [PATCH 646/664] Remove edit gallery toolbar button (#16778) --- packages/block-library/src/gallery/edit.js | 36 +--------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 8c4408c3a6746f..129fc2343224a6 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -9,19 +9,15 @@ import { filter, forEach, map, find, every } from 'lodash'; */ import { compose } from '@wordpress/compose'; import { - IconButton, PanelBody, RangeControl, SelectControl, ToggleControl, - Toolbar, withNotices, } from '@wordpress/components'; import { - BlockControls, BlockIcon, MediaPlaceholder, - MediaUpload, InspectorControls, } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; @@ -243,30 +239,6 @@ class GalleryEdit extends Component { const hasImages = !! images.length; - const controls = ( - <BlockControls> - { hasImages && ( - <Toolbar> - <MediaUpload - onSelect={ this.onSelectImages } - allowedTypes={ ALLOWED_MEDIA_TYPES } - multiple - gallery - value={ images.map( ( img ) => img.id ) } - render={ ( { open } ) => ( - <IconButton - className="components-toolbar__control" - label={ __( 'Edit gallery' ) } - icon="edit" - onClick={ open } - /> - ) } - /> - </Toolbar> - ) } - </BlockControls> - ); - const mediaPlaceholder = ( <MediaPlaceholder addToGallery={ hasImages } @@ -289,17 +261,11 @@ class GalleryEdit extends Component { ); if ( ! hasImages ) { - return ( - <> - { controls } - { mediaPlaceholder } - </> - ); + return mediaPlaceholder; } return ( <> - { controls } <InspectorControls> <PanelBody title={ __( 'Gallery Settings' ) }> { images.length > 1 && <RangeControl From a12ac00165d5c20d8cf2d0651e0c4eb6da80a854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 9 Aug 2019 10:48:16 +0200 Subject: [PATCH 647/664] Packages: Fix all missing or obsolete dependencies in packages (#16969) * Packages: Fix all missing or obsolete dependencies in packages * Enable import/no-extraneous-dependencies rule and fix reported violations * Remove 2 dependencies not used in the project on the global level * Remove unused @wordpress/core-data dependnecy from @wordpress/block-editor --- .eslintrc.js | 13 + package-lock.json | 238 ++++++++++++++++-- package.json | 6 +- packages/block-editor/package.json | 7 +- packages/block-editor/src/index.js | 1 - packages/block-library/package.json | 9 +- .../package.json | 3 +- packages/blocks/package.json | 2 +- packages/blocks/src/api/validation.js | 1 + packages/components/package.json | 2 - packages/docgen/package.json | 3 + packages/e2e-tests/package.json | 3 +- packages/edit-post/package.json | 6 +- packages/edit-widgets/package.json | 11 +- packages/editor/package.json | 9 +- packages/format-library/package.json | 5 +- packages/redux-routine/package.json | 1 + packages/rich-text/package.json | 1 - packages/viewport/package.json | 1 - 19 files changed, 280 insertions(+), 42 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2a6ad63b1eafc7..61debd40a2806f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,6 +22,9 @@ module.exports = { 'plugin:@wordpress/eslint-plugin/recommended', 'plugin:jest/recommended', ], + plugins: [ + 'import', + ], rules: { '@wordpress/react-no-unsafe-timeout': 'error', 'no-restricted-syntax': [ @@ -106,6 +109,16 @@ module.exports = { } ], }, overrides: [ + { + files: [ 'packages/**/*.js' ], + rules: { + 'import/no-extraneous-dependencies': 'error', + }, + excludedFiles: [ + '**/*.@(android|ios|native).js', + '**/@(benchmark|test|__tests__)/**/*.js', + ], + }, { files: [ 'packages/e2e-test*/**/*.js' ], env: { diff --git a/package-lock.json b/package-lock.json index 843205061c297e..5095f0d4486c2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4626,19 +4626,26 @@ "@wordpress/viewport": "file:packages/viewport", "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.2.5", + "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", + "inherits": "^2.0.3", "lodash": "^4.17.10", + "memize": "^1.0.5", + "react-autosize-textarea": "^3.0.2", "react-spring": "^8.0.19", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", - "tinycolor2": "^1.4.1" + "tinycolor2": "^1.4.1", + "traverse": "^0.6.6" } }, "@wordpress/block-library": { "version": "file:packages/block-library", "requires": { "@babel/runtime": "^7.4.4", + "@wordpress/a11y": "file:packages/a11y", + "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", "@wordpress/block-editor": "file:packages/block-editor", @@ -4647,18 +4654,23 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/date": "file:packages/date", "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", - "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/server-side-render": "file:packages/server-side-render", + "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", "fast-average-color": "4.3.0", "lodash": "^4.17.14", "memize": "^1.0.5", + "moment": "^2.22.1", + "tinycolor2": "^1.4.1", "url": "^0.11.0" } }, @@ -4671,7 +4683,8 @@ "@wordpress/block-serialization-spec-parser": { "version": "file:packages/block-serialization-spec-parser", "requires": { - "pegjs": "^0.10.0" + "pegjs": "^0.10.0", + "phpegjs": "^1.0.0-beta7" } }, "@wordpress/blocks": { @@ -4681,7 +4694,7 @@ "@wordpress/autop": "file:packages/autop", "@wordpress/blob": "file:packages/blob", "@wordpress/block-serialization-default-parser": "file:packages/block-serialization-default-parser", - "@wordpress/block-serialization-spec-parser": "file:packages/block-serialization-spec-parser", + "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", @@ -4717,10 +4730,8 @@ "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/rich-text": "file:packages/rich-text", - "@wordpress/url": "file:packages/url", "classnames": "^2.2.5", "clipboard": "^2.0.1", - "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", "lodash": "^4.17.14", "memize": "^1.0.5", @@ -4814,6 +4825,9 @@ "version": "file:packages/docgen", "dev": true, "requires": { + "doctrine": "^2.1.0", + "espree": "^4.0.0", + "lodash": "^4.17.14", "mdast-util-inject": "1.1.0", "optionator": "0.8.2", "remark": "10.0.1", @@ -4854,7 +4868,8 @@ "@wordpress/jest-puppeteer-axe": "file:packages/jest-puppeteer-axe", "@wordpress/scripts": "file:packages/scripts", "expect-puppeteer": "^4.3.0", - "lodash": "^4.17.14" + "lodash": "^4.17.14", + "uuid": "^3.3.2" } }, "@wordpress/edit-post": { @@ -4875,13 +4890,17 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/media-utils": "file:packages/media-utils", + "@wordpress/notices": "file:packages/notices", "@wordpress/nux": "file:packages/nux", "@wordpress/plugins": "file:packages/plugins", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "classnames": "^2.2.5", "lodash": "^4.17.14", - "refx": "^3.0.0" + "memize": "^1.0.5", + "refx": "^3.0.0", + "rememo": "^3.0.0" } }, "@wordpress/edit-widgets": { @@ -4889,10 +4908,19 @@ "requires": { "@babel/runtime": "^7.4.4", "@wordpress/block-editor": "file:packages/block-editor", + "@wordpress/block-library": "file:packages/block-library", + "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", + "@wordpress/compose": "file:packages/compose", + "@wordpress/data": "file:packages/data", + "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/element": "file:packages/element", + "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", - "@wordpress/notices": "file:packages/notices" + "@wordpress/media-utils": "file:packages/media-utils", + "@wordpress/notices": "file:packages/notices", + "lodash": "^4.17.14", + "rememo": "^3.0.0" } }, "@wordpress/editor": { @@ -4900,7 +4928,7 @@ "requires": { "@babel/runtime": "^7.4.4", "@wordpress/api-fetch": "file:packages/api-fetch", - "@wordpress/blob": "file:packages/blob", + "@wordpress/autop": "file:packages/autop", "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", @@ -4914,22 +4942,23 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", "@wordpress/nux": "file:packages/nux", + "@wordpress/rich-text": "file:packages/rich-text", + "@wordpress/server-side-render": "file:packages/server-side-render", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.2.5", - "inherits": "^2.0.3", "lodash": "^4.17.14", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", "redux-optimist": "^1.0.0", "refx": "^3.0.0", - "rememo": "^3.0.0", - "traverse": "^0.6.6" + "rememo": "^3.0.0" } }, "@wordpress/element": { @@ -4967,13 +4996,14 @@ "@babel/runtime": "^7.4.4", "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/components": "file:packages/components", - "@wordpress/editor": "file:packages/editor", + "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/rich-text": "file:packages/rich-text", - "@wordpress/url": "file:packages/url" + "@wordpress/url": "file:packages/url", + "lodash": "^4.17.14" } }, "@wordpress/hooks": { @@ -5127,6 +5157,7 @@ "requires": { "@babel/runtime": "^7.4.4", "is-promise": "^2.1.0", + "lodash": "^4.17.14", "rungen": "^0.3.2" } }, @@ -5136,7 +5167,6 @@ "@babel/runtime": "^7.4.4", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", - "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", @@ -5222,7 +5252,6 @@ "@babel/runtime": "^7.4.4", "@wordpress/compose": "file:packages/compose", "@wordpress/data": "file:packages/data", - "@wordpress/element": "file:packages/element", "lodash": "^4.17.14" } }, @@ -8536,6 +8565,12 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -10836,6 +10871,172 @@ } } }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-module-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "eslint-plugin-jest": { "version": "22.14.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.14.1.tgz", @@ -20099,8 +20300,7 @@ "phpegjs": { "version": "1.0.0-beta7", "resolved": "https://registry.npmjs.org/phpegjs/-/phpegjs-1.0.0-beta7.tgz", - "integrity": "sha1-uLbthQGYB//Q7+ID4AKj5e2LTZQ=", - "dev": true + "integrity": "sha1-uLbthQGYB//Q7+ID4AKj5e2LTZQ=" }, "physical-cpu-count": { "version": "2.0.0", diff --git a/package.json b/package.json index 82f89731e6a5f3..35157d07904310 100644 --- a/package.json +++ b/package.json @@ -98,10 +98,9 @@ "cross-env": "3.2.4", "cssnano": "4.1.10", "deep-freeze": "0.0.1", - "doctrine": "2.1.0", "enzyme": "3.9.0", + "eslint-plugin-import": "2.18.2", "eslint-plugin-jest": "22.14.1", - "espree": "4.0.0", "fast-glob": "2.2.7", "fbjs": "0.8.17", "fs-extra": "8.0.1", @@ -122,15 +121,12 @@ "node-sass": "4.12.0", "node-watch": "0.6.0", "parcel-bundler": "1.12.3", - "pegjs": "0.10.0", - "phpegjs": "1.0.0-beta7", "postcss": "7.0.13", "progress": "2.0.3", "react": "16.8.4", "react-dom": "16.8.4", "react-native": "0.60.0", "react-test-renderer": "16.8.4", - "redux": "4.0.0", "rimraf": "2.6.2", "rtlcss": "2.4.0", "sass-loader": "6.0.7", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index beab36f210740c..67e1ec228e77a1 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -43,13 +43,18 @@ "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.2.5", + "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", + "inherits": "^2.0.3", "lodash": "^4.17.10", + "memize": "^1.0.5", + "react-autosize-textarea": "^3.0.2", "react-spring": "^8.0.19", "redux-multi": "^0.1.12", "refx": "^3.0.0", "rememo": "^3.0.0", - "tinycolor2": "^1.4.1" + "tinycolor2": "^1.4.1", + "traverse": "^0.6.6" }, "publishConfig": { "access": "public" diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index d554e6e577a385..6931c7b1d7f658 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import '@wordpress/blocks'; -import '@wordpress/core-data'; import '@wordpress/rich-text'; import '@wordpress/viewport'; diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 3e535e3e8bf580..6f33a69e9d36bb 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -22,6 +22,8 @@ "react-native": "src/index", "dependencies": { "@babel/runtime": "^7.4.4", + "@wordpress/a11y": "file:../a11y", + "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", "@wordpress/block-editor": "file:../block-editor", @@ -30,18 +32,23 @@ "@wordpress/compose": "file:../compose", "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", + "@wordpress/date": "file:../date", "@wordpress/deprecated": "file:../deprecated", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", - "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/rich-text": "file:../rich-text", "@wordpress/server-side-render": "file:../server-side-render", + "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", "fast-average-color": "4.3.0", "lodash": "^4.17.14", "memize": "^1.0.5", + "moment": "^2.22.1", + "tinycolor2": "^1.4.1", "url": "^0.11.0" }, "publishConfig": { diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index 20102b0c217844..33ad5a16ae54fd 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -21,7 +21,8 @@ }, "main": "parser.js", "dependencies": { - "pegjs": "^0.10.0" + "pegjs": "^0.10.0", + "phpegjs": "^1.0.0-beta7" }, "publishConfig": { "access": "public" diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 95fa54024c9b22..4f04e40af09a6e 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -25,7 +25,7 @@ "@wordpress/autop": "file:../autop", "@wordpress/blob": "file:../blob", "@wordpress/block-serialization-default-parser": "file:../block-serialization-default-parser", - "@wordpress/block-serialization-spec-parser": "file:../block-serialization-spec-parser", + "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", diff --git a/packages/blocks/src/api/validation.js b/packages/blocks/src/api/validation.js index ff37b5a73872ca..444830c47c5ac7 100644 --- a/packages/blocks/src/api/validation.js +++ b/packages/blocks/src/api/validation.js @@ -259,6 +259,7 @@ const log = ( () => { // dependency in runtime environments, and it can be dropped by a combo // of Webpack env substitution + UglifyJS dead code elimination. if ( process.env.NODE_ENV === 'test' ) { + // eslint-disable-next-line import/no-extraneous-dependencies return ( ...args ) => logger( require( 'sprintf-js' ).sprintf( ...args ) ); } diff --git a/packages/components/package.json b/packages/components/package.json index c8c1df080d67a5..15c122d787a892 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -32,10 +32,8 @@ "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", "@wordpress/rich-text": "file:../rich-text", - "@wordpress/url": "file:../url", "classnames": "^2.2.5", "clipboard": "^2.0.1", - "diff": "^3.5.0", "dom-scroll-into-view": "^1.2.1", "lodash": "^4.17.14", "memize": "^1.0.5", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 288783fad638d5..f8e9222b1fc7da 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -22,6 +22,9 @@ "docgen": "./bin/cli.js" }, "dependencies": { + "doctrine": "^2.1.0", + "espree": "^4.0.0", + "lodash": "^4.17.14", "mdast-util-inject": "1.1.0", "optionator": "0.8.2", "remark": "10.0.1", diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index b74cc44e18fa1a..b3682d403bbbbe 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -27,7 +27,8 @@ "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", "@wordpress/scripts": "file:../scripts", "expect-puppeteer": "^4.3.0", - "lodash": "^4.17.14" + "lodash": "^4.17.14", + "uuid": "^3.3.2" }, "peerDependencies": { "jest": ">=24", diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 92cba80719a80a..1b00301490760e 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -36,13 +36,17 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", "@wordpress/nux": "file:../nux", "@wordpress/plugins": "file:../plugins", "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "classnames": "^2.2.5", "lodash": "^4.17.14", - "refx": "^3.0.0" + "memize": "^1.0.5", + "refx": "^3.0.0", + "rememo": "^3.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index 56f8c4e9bd57a8..1c467b1244bf22 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -23,10 +23,19 @@ "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/block-editor": "file:../block-editor", + "@wordpress/block-library": "file:../block-library", + "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", + "@wordpress/data": "file:../data", + "@wordpress/data-controls": "file:../data-controls", "@wordpress/element": "file:../element", + "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", - "@wordpress/notices": "file:../notices" + "@wordpress/media-utils": "file:../media-utils", + "@wordpress/notices": "file:../notices", + "lodash": "^4.17.14", + "rememo": "^3.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/editor/package.json b/packages/editor/package.json index bed0ff5b61c23a..fa73db83d59000 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -23,7 +23,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/api-fetch": "file:../api-fetch", - "@wordpress/blob": "file:../blob", + "@wordpress/autop": "file:../autop", "@wordpress/block-editor": "file:../block-editor", "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", @@ -37,22 +37,23 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", "@wordpress/nux": "file:../nux", + "@wordpress/rich-text": "file:../rich-text", + "@wordpress/server-side-render": "file:../server-side-render", "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.2.5", - "inherits": "^2.0.3", "lodash": "^4.17.14", "memize": "^1.0.5", "react-autosize-textarea": "^3.0.2", "redux-optimist": "^1.0.0", "refx": "^3.0.0", - "rememo": "^3.0.0", - "traverse": "^0.6.6" + "rememo": "^3.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 84f47c5fc58828..2d2dbcf8e2fc64 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -24,13 +24,14 @@ "@babel/runtime": "^7.4.4", "@wordpress/block-editor": "file:../block-editor", "@wordpress/components": "file:../components", - "@wordpress/editor": "file:../editor", + "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", "@wordpress/rich-text": "file:../rich-text", - "@wordpress/url": "file:../url" + "@wordpress/url": "file:../url", + "lodash": "^4.17.14" }, "publishConfig": { "access": "public" diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index d8e0a7a99dbb8c..924465d944d9b3 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -25,6 +25,7 @@ "dependencies": { "@babel/runtime": "^7.4.4", "is-promise": "^2.1.0", + "lodash": "^4.17.14", "rungen": "^0.3.2" }, "publishConfig": { diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 1719ada3c18941..e0d763ba266d8c 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -24,7 +24,6 @@ "@babel/runtime": "^7.4.4", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", - "@wordpress/deprecated": "file:../deprecated", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", diff --git a/packages/viewport/package.json b/packages/viewport/package.json index f0e9a81a1005b8..df614c82f59d6a 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -24,7 +24,6 @@ "@babel/runtime": "^7.4.4", "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", - "@wordpress/element": "file:../element", "lodash": "^4.17.14" }, "publishConfig": { From c1d4497bf0a9c30bed9a020d2ddc777e4494b5e4 Mon Sep 17 00:00:00 2001 From: Jeff Ong <jeff.ong@automattic.com> Date: Fri, 9 Aug 2019 05:18:40 -0400 Subject: [PATCH 648/664] Fix checkbox alignment issues in options and block manager modals (#16860) (#16863) --- assets/stylesheets/_z-index.scss | 5 +- .../components/src/checkbox-control/index.js | 24 ++--- .../src/checkbox-control/style.scss | 52 +++++----- .../components/manage-blocks-modal/style.scss | 3 +- .../enable-custom-fields.js.snap | 94 +++++++++++-------- 5 files changed, 101 insertions(+), 77 deletions(-) diff --git a/assets/stylesheets/_z-index.scss b/assets/stylesheets/_z-index.scss index 949a0101b1f527..b1c3341695204b 100644 --- a/assets/stylesheets/_z-index.scss +++ b/assets/stylesheets/_z-index.scss @@ -112,7 +112,10 @@ $z-layers: ( // Make sure corner handles are above side handles for ResizableBox component ".components-resizable-box__side-handle": 1, - ".components-resizable-box__corner-handle": 2 + ".components-resizable-box__corner-handle": 2, + + // Make sure block manager sticky category titles appear above the options + ".edit-post-manage-blocks-modal__category-title": 1 ); @function z-index( $key ) { diff --git a/packages/components/src/checkbox-control/index.js b/packages/components/src/checkbox-control/index.js index 0cf12f0b9dcd90..77f75a33064985 100644 --- a/packages/components/src/checkbox-control/index.js +++ b/packages/components/src/checkbox-control/index.js @@ -15,18 +15,20 @@ function CheckboxControl( { label, className, heading, checked, help, instanceId return ( <BaseControl label={ heading } id={ id } help={ help } className={ className }> - <input - id={ id } - className="components-checkbox-control__input" - type="checkbox" - value="1" - onChange={ onChangeValue } - checked={ checked } - aria-describedby={ !! help ? id + '__help' : undefined } - { ...props } - /> - <label className="components-checkbox-control__label" htmlFor={ id }> + <span className="components-checkbox-control__input-container"> + <input + id={ id } + className="components-checkbox-control__input" + type="checkbox" + value="1" + onChange={ onChangeValue } + checked={ checked } + aria-describedby={ !! help ? id + '__help' : undefined } + { ...props } + /> { checked ? <Dashicon icon="yes" className="components-checkbox-control__checked" role="presentation" /> : null } + </span> + <label className="components-checkbox-control__label" htmlFor={ id }> { label } </label> </BaseControl> diff --git a/packages/components/src/checkbox-control/style.scss b/packages/components/src/checkbox-control/style.scss index 240b1ad7623b83..49c768c73c9aa5 100644 --- a/packages/components/src/checkbox-control/style.scss +++ b/packages/components/src/checkbox-control/style.scss @@ -1,3 +1,6 @@ +$checkbox-input-size: 16px; +$checkbox-input-size-sm: 25px; // width + height for small viewports + .components-checkbox-control__input[type="checkbox"] { border: 1px solid #b4b9be; background: #fff; @@ -6,14 +9,17 @@ cursor: pointer; display: inline-block; line-height: 0; - height: 16px; margin: 0 4px 0 0; outline: 0; padding: 0 !important; text-align: center; - vertical-align: middle; - width: 16px; - min-width: 16px; + vertical-align: top; + width: $checkbox-input-size-sm; + height: $checkbox-input-size-sm; + @include break-small() { + height: $checkbox-input-size; + width: $checkbox-input-size; + } -webkit-appearance: none; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); transition: 0.05s border-color ease-in-out; @@ -39,34 +45,32 @@ } } -.components-checkbox-control__label { +.components-checkbox-control__input-container { position: relative; + display: inline-block; + margin-right: 12px; vertical-align: middle; - line-height: 1; + width: $checkbox-input-size-sm; + height: $checkbox-input-size-sm; + @include break-small() { + width: $checkbox-input-size; + height: $checkbox-input-size; + } } svg.dashicon.components-checkbox-control__checked { - width: 21px; - height: 21px; fill: #fff; cursor: pointer; position: absolute; - left: -31px; - bottom: -3px; + left: -4px; + top: -2px; + width: 31px; + height: 31px; + @include break-small() { + width: 21px; + height: 21px; + left: -3px; + } user-select: none; pointer-events: none; } - -@media screen and ( max-width: 728px ) { - .components-checkbox-control__input[type="checkbox"] { - width: 25px; - height: 25px; - } - - svg.dashicon.components-checkbox-control__checked { - width: 31px; - height: 31px; - left: -41px; - bottom: -9px; - } -} diff --git a/packages/edit-post/src/components/manage-blocks-modal/style.scss b/packages/edit-post/src/components/manage-blocks-modal/style.scss index 8fa6693672f755..46b877d631573b 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/style.scss +++ b/packages/edit-post/src/components/manage-blocks-modal/style.scss @@ -54,6 +54,7 @@ top: 0; padding: $panel-padding 0; background-color: $white; + z-index: z-index(".edit-post-manage-blocks-modal__category-title"); .components-base-control__field { margin-bottom: 0; @@ -88,7 +89,7 @@ margin: 0; } - .components-modal__content & input[type="checkbox"] { + .components-modal__content &.components-checkbox-control__input-container { margin: 0 $grid-size; } diff --git a/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap b/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap index 71c2036684552d..68859d38900241 100644 --- a/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap +++ b/packages/edit-post/src/components/options-modal/options/test/__snapshots__/enable-custom-fields.js.snap @@ -10,18 +10,17 @@ exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation <div className="components-base-control__field" > - <input - checked={true} - className="components-checkbox-control__input" - id="inspector-checkbox-control-3" - onChange={[Function]} - type="checkbox" - value="1" - /> - <label - className="components-checkbox-control__label" - htmlFor="inspector-checkbox-control-3" + <span + className="components-checkbox-control__input-container" > + <input + checked={true} + className="components-checkbox-control__input" + id="inspector-checkbox-control-3" + onChange={[Function]} + type="checkbox" + value="1" + /> <svg aria-hidden="true" className="dashicon dashicons-yes components-checkbox-control__checked" @@ -36,7 +35,11 @@ exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation d="M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z" /> </svg> - </label> + </span> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-3" + /> </div> </div> <p @@ -65,18 +68,17 @@ exports[`EnableCustomFieldsOption renders a checked checkbox when custom fields <div className="components-base-control__field" > - <input - checked={true} - className="components-checkbox-control__input" - id="inspector-checkbox-control-0" - onChange={[Function]} - type="checkbox" - value="1" - /> - <label - className="components-checkbox-control__label" - htmlFor="inspector-checkbox-control-0" + <span + className="components-checkbox-control__input-container" > + <input + checked={true} + className="components-checkbox-control__input" + id="inspector-checkbox-control-0" + onChange={[Function]} + type="checkbox" + value="1" + /> <svg aria-hidden="true" className="dashicon dashicons-yes components-checkbox-control__checked" @@ -91,7 +93,11 @@ exports[`EnableCustomFieldsOption renders a checked checkbox when custom fields d="M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z" /> </svg> - </label> + </span> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-0" + /> </div> </div> </div> @@ -107,14 +113,18 @@ exports[`EnableCustomFieldsOption renders an unchecked checkbox and a confirmati <div className="components-base-control__field" > - <input - checked={false} - className="components-checkbox-control__input" - id="inspector-checkbox-control-2" - onChange={[Function]} - type="checkbox" - value="1" - /> + <span + className="components-checkbox-control__input-container" + > + <input + checked={false} + className="components-checkbox-control__input" + id="inspector-checkbox-control-2" + onChange={[Function]} + type="checkbox" + value="1" + /> + </span> <label className="components-checkbox-control__label" htmlFor="inspector-checkbox-control-2" @@ -147,14 +157,18 @@ exports[`EnableCustomFieldsOption renders an unchecked checkbox when custom fiel <div className="components-base-control__field" > - <input - checked={false} - className="components-checkbox-control__input" - id="inspector-checkbox-control-1" - onChange={[Function]} - type="checkbox" - value="1" - /> + <span + className="components-checkbox-control__input-container" + > + <input + checked={false} + className="components-checkbox-control__input" + id="inspector-checkbox-control-1" + onChange={[Function]} + type="checkbox" + value="1" + /> + </span> <label className="components-checkbox-control__label" htmlFor="inspector-checkbox-control-1" From 853b3cb100469f9d71bad93bb38ece5210dae0b2 Mon Sep 17 00:00:00 2001 From: Pascal Birchler <pascalb@google.com> Date: Fri, 9 Aug 2019 13:25:33 +0200 Subject: [PATCH 649/664] Improve usage of eslint-disable directives (#16941) * Remove unused `eslint-disable` directives * Leverage `eslint-plugin-eslint-comments` and fix reported errors --- .eslintrc.js | 1 + bin/commander.js | 2 ++ bin/packages/build.js | 2 ++ package-lock.json | 18 ++++++++++++++++++ package.json | 1 + .../src/components/block-list/block.js | 11 ----------- .../src/components/inserter/menu.js | 3 +-- .../src/components/inserter/menu.native.js | 1 - .../src/utils/transform-styles/ast/parse.js | 2 ++ .../transform-styles/ast/stringify/identity.js | 2 ++ packages/block-library/src/audio/edit.js | 2 -- packages/block-library/src/classic/edit.js | 8 ++------ .../block-library/src/embed/embed-preview.js | 4 ++-- packages/block-library/src/image/edit.js | 4 ++-- packages/block-library/src/index.js | 2 +- packages/block-library/src/video/edit.js | 2 -- packages/blocks/src/api/test/registration.js | 2 ++ .../components/src/color-picker/saturation.js | 4 ++-- .../src/form-token-field/suggestions-list.js | 4 ++-- .../with-focus-outside/index.native.js | 2 -- packages/components/src/index.js | 1 - packages/components/src/modal/index.js | 2 -- .../src/navigable-container/container.js | 3 --- packages/components/src/popover/index.js | 2 -- packages/components/src/sandbox/index.js | 2 +- .../keyboard-shortcut-help-modal/index.js | 1 + .../editor/src/components/post-format/index.js | 2 -- .../hierarchical-term-selector.js | 1 - packages/hooks/src/test/index.test.js | 2 -- packages/jest-console/src/test/index.test.js | 2 ++ packages/keycodes/src/platform.native.js | 1 - .../rich-text/src/component/index.native.js | 2 -- packages/scripts/scripts/test-e2e.js | 2 +- packages/scripts/scripts/test-unit-jest.js | 2 +- webpack.config.js | 1 - 35 files changed, 48 insertions(+), 55 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 61debd40a2806f..a33f618328f346 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,6 +21,7 @@ module.exports = { extends: [ 'plugin:@wordpress/eslint-plugin/recommended', 'plugin:jest/recommended', + 'plugin:eslint-comments/recommended', ], plugins: [ 'import', diff --git a/bin/commander.js b/bin/commander.js index 091e8156611bdb..9edd90821d3bdb 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -637,3 +637,5 @@ program } ); program.parse( process.argv ); + +/* eslint-enable no-console */ diff --git a/bin/packages/build.js b/bin/packages/build.js index e1a6bd7142f80d..5447ba82d2c80f 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -175,3 +175,5 @@ stream } ) ) .on( 'end', () => ended = true ) .resume(); + +/* eslint-enable no-console */ diff --git a/package-lock.json b/package-lock.json index 5095f0d4486c2e..4c8a714c7221bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10913,6 +10913,24 @@ } } }, + "eslint-plugin-eslint-comments": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.2.tgz", + "integrity": "sha512-QexaqrNeteFfRTad96W+Vi4Zj1KFbkHHNMMaHZEYcovKav6gdomyGzaxSDSL3GoIyUOo078wRAdYlu1caiauIQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "dependencies": { + "ignore": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.2.tgz", + "integrity": "sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ==", + "dev": true + } + } + }, "eslint-plugin-import": { "version": "2.18.2", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", diff --git a/package.json b/package.json index 35157d07904310..1b4628aac3b41d 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "cssnano": "4.1.10", "deep-freeze": "0.0.1", "enzyme": "3.9.0", + "eslint-plugin-eslint-comments": "3.1.2", "eslint-plugin-import": "2.18.2", "eslint-plugin-jest": "22.14.1", "fast-glob": "2.2.7", diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index d95ce1f3a93813..126e8c294fa1b7 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -478,16 +478,6 @@ function BlockListBlock( { blockEdit = <div style={ { display: 'none' } }>{ blockEdit }</div>; } - // Disable reasons: - // - // jsx-a11y/mouse-events-have-key-events: - // - onMouseOver is explicitly handling hover effects - // - // jsx-a11y/no-static-element-interactions: - // - Each block can be selected by clicking on it - - /* eslint-disable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ - return ( <IgnoreNestedEvents id={ blockElementId } @@ -618,7 +608,6 @@ function BlockListBlock( { ) } </IgnoreNestedEvents> ); - /* eslint-enable jsx-a11y/mouse-events-have-key-events, jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } const applyWithSelect = withSelect( diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 8fa9a61ea30fda..0b5f47c68af256 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -161,7 +161,6 @@ export class InserterMenu extends Component { this.props.setTimeout( () => { // We need a generic way to access the panel's container - // eslint-disable-next-line react/no-find-dom-node scrollIntoView( this.panels[ panel ], this.inserterResults.current, { alignWithTop: true, } ); @@ -361,7 +360,7 @@ export class InserterMenu extends Component { } </div> ); - /* eslint-enable jsx-a11y/no-autofocus, jsx-a11y/no-noninteractive-element-interactions */ + /* eslint-enable jsx-a11y/no-autofocus, jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index bf2dd780841f9c..7870f41954afe3 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -101,7 +101,6 @@ export class InserterMenu extends Component { /> </BottomSheet> ); - /* eslint-enable jsx-a11y/no-autofocus, jsx-a11y/no-noninteractive-element-interactions */ } } diff --git a/packages/block-editor/src/utils/transform-styles/ast/parse.js b/packages/block-editor/src/utils/transform-styles/ast/parse.js index b7c033fc3e72fe..11e8d22a378475 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/parse.js +++ b/packages/block-editor/src/utils/transform-styles/ast/parse.js @@ -684,3 +684,5 @@ function addParent( obj, parent ) { return obj; } + +/* eslint-enable @wordpress/no-unused-vars-before-return */ diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js index 87de0945b9f2d3..cc15526e14946e 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js +++ b/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js @@ -286,3 +286,5 @@ Compiler.prototype.indent = function( level ) { return Array( this.level ).join( this.indentation || ' ' ); }; + +/* eslint-enable @wordpress/no-unused-vars-before-return */ diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 17d24e6c9a7a79..15387acbd90c7a 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -151,7 +151,6 @@ class AudioEdit extends Component { ); } - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <> <BlockControls> @@ -210,7 +209,6 @@ class AudioEdit extends Component { </figure> </> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } export default compose( [ diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index 5b30ecb7f90692..2ce77409e8e0f3 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -183,12 +183,8 @@ export default class ClassicEdit extends Component { // jsx-a11y/no-static-element-interactions // - the toolbar itself is non-interactive, but must capture events // from the KeyboardShortcuts component to stop their propagation. - // - // jsx-a11y/no-static-element-interactions - // - Clicking on this visual placeholder should create the - // toolbar, it can also be created by focussing the field below. - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions */ return [ <div key="toolbar" @@ -205,6 +201,6 @@ export default class ClassicEdit extends Component { className="wp-block-freeform block-library-rich-text__tinymce" />, ]; - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index e519ca6aaa2da8..878c3e7504ccae 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -69,7 +69,7 @@ class EmbedPreview extends Component { // Disabled because the overlay div doesn't actually have a role or functionality // as far as the user is concerned. We're just catching the first click so that // the block can be selected without interacting with the embed preview that the overlay covers. - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */ + /* eslint-disable jsx-a11y/no-static-element-interactions */ const embedWrapper = 'wp-embed' === type ? ( <WpEmbedPreview html={ html } @@ -88,7 +88,7 @@ class EmbedPreview extends Component { onMouseUp={ this.hideOverlay } /> } </div> ); - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-static-element-interactions */ + /* eslint-enable jsx-a11y/no-static-element-interactions */ return ( <figure className={ classnames( className, 'wp-block-embed', { 'is-type-video': 'video' === type } ) }> diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index 48615eae98011d..e0cc5e751fdaaf 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -778,7 +778,7 @@ export class ImageEdit extends Component { ); // Disable reason: Each block can be selected by clicking on it - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/click-events-have-key-events */ return ( <> { controls } @@ -924,7 +924,7 @@ export class ImageEdit extends Component { { mediaPlaceholder } </> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 0d313bc171b20e..67d460b4b4b404 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -74,7 +74,7 @@ const registerBlock = ( block ) => { } const { metadata, settings, name } = block; if ( metadata ) { - unstable__bootstrapServerSideBlockDefinitions( { [ name ]: metadata } ); // eslint-disable-line camelcase + unstable__bootstrapServerSideBlockDefinitions( { [ name ]: metadata } ); } registerBlockType( name, settings ); }; diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index d1cbdc7ae47066..6236e51a95c935 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -198,7 +198,6 @@ class VideoEdit extends Component { } const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`; - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ return ( <> <BlockControls> @@ -314,7 +313,6 @@ class VideoEdit extends Component { </figure> </> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } } diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 9be18c0b7ffe5d..b3e558edb0536d 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -712,3 +712,5 @@ describe( 'blocks', () => { } ); } ); } ); + +/* eslint-enable react/forbid-elements */ diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index ac52818a6571c7..3bdd10bfcdfe28 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -150,7 +150,7 @@ export class Saturation extends Component { home: () => this.saturate( -1 ), }; - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-element-interactions */ + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ return ( <KeyboardShortcuts shortcuts={ shortcuts }> <div @@ -181,7 +181,7 @@ export class Saturation extends Component { </div> </KeyboardShortcuts> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/no-noninteractive-element-interactions */ + /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } } diff --git a/packages/components/src/form-token-field/suggestions-list.js b/packages/components/src/form-token-field/suggestions-list.js index d6b2debd695011..2d7e64f84be7d0 100644 --- a/packages/components/src/form-token-field/suggestions-list.js +++ b/packages/components/src/form-token-field/suggestions-list.js @@ -91,7 +91,7 @@ class SuggestionsList extends Component { 'is-selected': index === this.props.selectedIndex, } ); - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/click-events-have-key-events */ return ( <li id={ `components-form-token-suggestions-${ this.props.instanceId }-${ index }` } @@ -117,7 +117,7 @@ class SuggestionsList extends Component { } </li> ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/click-events-have-key-events */ } ) } </ul> diff --git a/packages/components/src/higher-order/with-focus-outside/index.native.js b/packages/components/src/higher-order/with-focus-outside/index.native.js index 7d3ae52eaeb787..3efc036cdd46eb 100644 --- a/packages/components/src/higher-order/with-focus-outside/index.native.js +++ b/packages/components/src/higher-order/with-focus-outside/index.native.js @@ -117,7 +117,6 @@ export default createHigherOrderComponent( // Disable reason: See `normalizeButtonFocus` for browser-specific // focus event normalization. - /* eslint-disable jsx-a11y/no-static-element-interactions */ return ( <View onFocus={ this.cancelBlurCheck } @@ -132,7 +131,6 @@ export default createHigherOrderComponent( { ...this.props } /> </View> ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ } }; }, 'withFocusOutside' diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 6395ea3d843a42..3ab485312f848b 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -1,6 +1,5 @@ // Components export * from './primitives'; -// eslint-disable-next-line camelcase export { default as Animate } from './animate'; export { default as Autocomplete } from './autocomplete'; export { default as BaseControl } from './base-control'; diff --git a/packages/components/src/modal/index.js b/packages/components/src/modal/index.js index ff35faf9222f99..eb5b07ecdb1fde 100644 --- a/packages/components/src/modal/index.js +++ b/packages/components/src/modal/index.js @@ -125,7 +125,6 @@ class Modal extends Component { // Disable reason: this stops mouse events from triggering tooltips and // other elements underneath the modal overlay. - /* eslint-disable jsx-a11y/no-static-element-interactions */ return createPortal( <IsolatedEventContainer className={ classnames( 'components-modal__screen-overlay', overlayClassName ) } @@ -157,7 +156,6 @@ class Modal extends Component { </IsolatedEventContainer>, this.node ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/components/src/navigable-container/container.js b/packages/components/src/navigable-container/container.js index be3a9d73be402c..a4fb6eeabd80a1 100644 --- a/packages/components/src/navigable-container/container.js +++ b/packages/components/src/navigable-container/container.js @@ -116,8 +116,6 @@ class NavigableContainer extends Component { render() { const { children, ...props } = this.props; - // Disable reason: Assumed role is applied by parent via props spread. - /* eslint-disable jsx-a11y/no-static-element-interactions */ return ( <div ref={ this.bindContainer } { ...omit( props, [ @@ -132,7 +130,6 @@ class NavigableContainer extends Component { { children } </div> ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ } } diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index a6aff279f0b151..1fc3b14ca2fed7 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -378,7 +378,6 @@ const Popover = ( { // Disable reason: We care to capture the _bubbled_ events from inputs // within popover as inferring close intent. - /* eslint-disable jsx-a11y/no-static-element-interactions */ let content = ( <PopoverDetectOutside onFocusOutside={ handleOnFocusOutside }> <Animate @@ -420,7 +419,6 @@ const Popover = ( { </Animate> </PopoverDetectOutside> ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ // Apply focus to element as long as focusOnMount is truthy; false is // the only "disabled" value. diff --git a/packages/components/src/sandbox/index.js b/packages/components/src/sandbox/index.js index 01f2e8f8e3b7fd..a607d31487cc20 100644 --- a/packages/components/src/sandbox/index.js +++ b/packages/components/src/sandbox/index.js @@ -48,7 +48,7 @@ class Sandbox extends Component { if ( 'string' === typeof data ) { try { data = JSON.parse( data ); - } catch ( e ) {} // eslint-disable-line no-empty + } catch ( e ) {} } // Verify that the mounted element is the source of the message diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index 33321ea53aed83..19a29aa84afa45 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -63,6 +63,7 @@ const ShortcutList = ( { shortcuts } ) => ( </li> ) ) } </ul> + /* eslint-enable jsx-a11y/no-redundant-roles */ ); const ShortcutSection = ( { title, shortcuts, className } ) => ( diff --git a/packages/editor/src/components/post-format/index.js b/packages/editor/src/components/post-format/index.js index da2dc0b7cf5911..d3402f953e0247 100644 --- a/packages/editor/src/components/post-format/index.js +++ b/packages/editor/src/components/post-format/index.js @@ -36,7 +36,6 @@ function PostFormat( { onUpdatePostFormat, postFormat = 'standard', supportedFor // Disable reason: We need to change the value immiediately to show/hide the suggestion if needed - /* eslint-disable jsx-a11y/no-onchange */ return ( <PostFormatCheck> <div className="editor-post-format"> @@ -64,7 +63,6 @@ function PostFormat( { onUpdatePostFormat, postFormat = 'standard', supportedFor </div> </PostFormatCheck> ); - /* eslint-enable jsx-a11y/no-onchange */ } export default compose( [ diff --git a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js index 4403884ed83598..9c2405be98cbd3 100644 --- a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js @@ -451,7 +451,6 @@ class HierarchicalTermSelector extends Component { </form> ), ]; - /* eslint-enable jsx-a11y/no-onchange */ } } diff --git a/packages/hooks/src/test/index.test.js b/packages/hooks/src/test/index.test.js index 20f27a30b9ae34..f8b1c736f55b43 100644 --- a/packages/hooks/src/test/index.test.js +++ b/packages/hooks/src/test/index.test.js @@ -1,5 +1,3 @@ -/* eslint-disable no-console */ - /** * Internal dependencies */ diff --git a/packages/jest-console/src/test/index.test.js b/packages/jest-console/src/test/index.test.js index 5e02832f7e2051..c626a1c2bc94ca 100644 --- a/packages/jest-console/src/test/index.test.js +++ b/packages/jest-console/src/test/index.test.js @@ -76,3 +76,5 @@ describe( 'jest-console', () => { } ); } ); + +/* eslint-enable no-console */ diff --git a/packages/keycodes/src/platform.native.js b/packages/keycodes/src/platform.native.js index 842cd4fd22e550..bd609d5c16a361 100644 --- a/packages/keycodes/src/platform.native.js +++ b/packages/keycodes/src/platform.native.js @@ -8,7 +8,6 @@ import { Platform } from 'react-native'; * * @return {boolean} True if iOS; false otherwise. */ -// eslint-disable-next-line no-unused-vars export function isAppleOS() { return Platform.OS === 'ios'; } diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 1cf45d08548bf0..fe705d8ad5b386 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -346,7 +346,6 @@ export class RichText extends Component { this.lastAztecEventType = 'content size change'; } - // eslint-disable-next-line no-unused-vars onEnter( event ) { if ( this.props.onEnter ) { this.props.onEnter(); @@ -385,7 +384,6 @@ export class RichText extends Component { this.lastAztecEventType = 'input'; } - // eslint-disable-next-line no-unused-vars onBackspace( event ) { const { __unstableOnMerge: onMerge, diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js index df3fc9733ab644..c82a92f442f883 100644 --- a/packages/scripts/scripts/test-e2e.js +++ b/packages/scripts/scripts/test-e2e.js @@ -12,7 +12,7 @@ process.on( 'unhandledRejection', ( err ) => { /** * External dependencies */ -/* eslint-disable jest/no-jest-import */ +/* eslint-disable-next-line jest/no-jest-import */ const jest = require( 'jest' ); /** diff --git a/packages/scripts/scripts/test-unit-jest.js b/packages/scripts/scripts/test-unit-jest.js index 68c0108824c941..706addf5d6f7c4 100644 --- a/packages/scripts/scripts/test-unit-jest.js +++ b/packages/scripts/scripts/test-unit-jest.js @@ -12,7 +12,7 @@ process.on( 'unhandledRejection', ( err ) => { /** * External dependencies */ -/* eslint-disable jest/no-jest-import */ +/* eslint-disable-next-line jest/no-jest-import */ const jest = require( 'jest' ); /** diff --git a/webpack.config.js b/webpack.config.js index 8a8348bb494d52..b5890fe535c5ed 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -56,7 +56,6 @@ module.exports = { plugins: [ new DefinePlugin( { // Inject the `GUTENBERG_PHASE` global, used for feature flagging. - // eslint-disable-next-line @wordpress/gutenberg-phase 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), 'process.env.FORCE_REDUCED_MOTION': JSON.stringify( process.env.FORCE_REDUCED_MOTION ), } ), From 3787332fd53f236ba89dc59f4922af1b618e42ec Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Fri, 9 Aug 2019 13:13:06 +0100 Subject: [PATCH 650/664] =?UTF-8?q?Add:=20Block=20editor=20keyboard=20shor?= =?UTF-8?q?tcuts=20to=20the=20widget=20screen=20and=E2=80=A6=20(#16972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../edit-widgets/src/components/widget-area/index.js | 10 +++++++--- playground/src/index.js | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index 9ed9055f196e6b..517ae9d3212ff0 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -17,6 +17,7 @@ import { Inserter as BlockInserter, WritingFlow, ObserveTyping, + BlockEditorKeyboardShortcuts, } from '@wordpress/block-editor'; import { withDispatch, withSelect } from '@wordpress/data'; @@ -71,9 +72,12 @@ function WidgetArea( { settings={ settings } > { isSelectedArea && ( - <Inserter> - <BlockInserter /> - </Inserter> + <> + <Inserter> + <BlockInserter /> + </Inserter> + <BlockEditorKeyboardShortcuts /> + </> ) } <SelectionObserver isSelectedArea={ isSelectedArea } diff --git a/playground/src/index.js b/playground/src/index.js index d230f202243c23..b74e30091c1739 100644 --- a/playground/src/index.js +++ b/playground/src/index.js @@ -5,6 +5,7 @@ import '@wordpress/editor'; // This shouldn't be necessary import { render, useState, Fragment } from '@wordpress/element'; import { + BlockEditorKeyboardShortcuts, BlockEditorProvider, BlockList, WritingFlow, @@ -49,6 +50,7 @@ function App() { onChange={ updateBlocks } > <div className="editor-styles-wrapper"> + <BlockEditorKeyboardShortcuts /> <WritingFlow> <ObserveTyping> <BlockList /> From aff692edeb78499df84f897f77befb240c5f88e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Fri, 9 Aug 2019 15:02:37 +0200 Subject: [PATCH 651/664] Extensibility: Add possibility to disable document settings panels registered by plugins (#16900) --- .../src/components/options-modal/index.js | 2 + .../enable-plugin-document-setting-panel.js | 24 +++++++++++ .../components/options-modal/options/index.js | 1 + .../test/__snapshots__/index.js.snap | 1 + .../plugin-document-setting-panel/index.js | 41 ++++++++++++------- 5 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index a7e2cc2f395cc0..bb3f613539062f 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -23,6 +23,7 @@ import { */ import Section from './section'; import { + EnablePluginDocumentSettingPanelOption, EnablePublishSidebarOption, EnableTipsOption, EnablePanelOption, @@ -48,6 +49,7 @@ export function OptionsModal( { isModalActive, isViewable, closeModal } ) { <EnableTipsOption label={ __( 'Enable Tips' ) } /> </Section> <Section title={ __( 'Document Panels' ) }> + <EnablePluginDocumentSettingPanelOption.Slot /> { isViewable && ( <EnablePanelOption label={ __( 'Permalink' ) } panelName="post-link" /> ) } diff --git a/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js b/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js new file mode 100644 index 00000000000000..ac979c357ad782 --- /dev/null +++ b/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { EnablePanelOption } from './index'; + +const { Fill, Slot } = createSlotFill( 'EnablePluginDocumentSettingPanelOption' ); + +const EnablePluginDocumentSettingPanelOption = ( { label, panelName } ) => ( + <Fill> + <EnablePanelOption + label={ label } + panelName={ panelName } + /> + </Fill> +); + +EnablePluginDocumentSettingPanelOption.Slot = Slot; + +export default EnablePluginDocumentSettingPanelOption; diff --git a/packages/edit-post/src/components/options-modal/options/index.js b/packages/edit-post/src/components/options-modal/options/index.js index 7961206caba4ed..e1263f9f684741 100644 --- a/packages/edit-post/src/components/options-modal/options/index.js +++ b/packages/edit-post/src/components/options-modal/options/index.js @@ -1,4 +1,5 @@ export { default as EnableCustomFieldsOption } from './enable-custom-fields'; export { default as EnablePanelOption } from './enable-panel'; +export { default as EnablePluginDocumentSettingPanelOption } from './enable-plugin-document-setting-panel'; export { default as EnablePublishSidebarOption } from './enable-publish-sidebar'; export { default as EnableTipsOption } from './enable-tips'; diff --git a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap index a81252b32b95fb..c2907a26e3001e 100644 --- a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap @@ -19,6 +19,7 @@ exports[`OptionsModal should match snapshot when the modal is active 1`] = ` <Section title="Document Panels" > + <EnablePluginDocumentSettingPanelOptionSlot /> <WithSelect(PostTaxonomies) taxonomyWrapper={[Function]} /> diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js index 16ca9af32466b4..ef10b4ed77ef30 100644 --- a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js @@ -10,24 +10,34 @@ import { compose } from '@wordpress/compose'; import { withPluginContext } from '@wordpress/plugins'; import { withDispatch, withSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { EnablePluginDocumentSettingPanelOption } from '../../options-modal/options'; + export const { Fill, Slot } = createSlotFill( 'PluginDocumentSettingPanel' ); -const PluginDocumentSettingFill = ( { isEnabled, opened, onToggle, className, title, icon, children } ) => { - if ( ! isEnabled ) { - return null; - } +const PluginDocumentSettingFill = ( { isEnabled, panelName, opened, onToggle, className, title, icon, children } ) => { return ( - <Fill> - <PanelBody - className={ className } - title={ title } - icon={ icon } - opened={ opened } - onToggle={ onToggle } - > - { children } - </PanelBody> - </Fill> + <> + <EnablePluginDocumentSettingPanelOption + label={ title } + panelName={ panelName } + /> + <Fill> + { isEnabled && ( + <PanelBody + className={ className } + title={ title } + icon={ icon } + opened={ opened } + onToggle={ onToggle } + > + { children } + </PanelBody> + ) } + </Fill> + </> ); }; @@ -104,4 +114,5 @@ const PluginDocumentSettingPanel = compose( )( PluginDocumentSettingFill ); PluginDocumentSettingPanel.Slot = Slot; + export default PluginDocumentSettingPanel; From 4af92215514af3a9603eabac1dc37bd2eaf3b7e4 Mon Sep 17 00:00:00 2001 From: Riad Benguella <benguella@gmail.com> Date: Fri, 9 Aug 2019 14:05:36 +0100 Subject: [PATCH 652/664] Migrate the Github Actions to the new YAML syntax (#16981) --- .github/main.workflow | 36 ------------------- ...-prs-opened-by-first-time-contributors.yml | 16 +++++++++ ...-fixed-issues-when-pull-request-opened.yml | 16 +++++++++ ...request-milestone-merged-pull-requests.yml | 12 +++++++ 4 files changed, 44 insertions(+), 36 deletions(-) delete mode 100644 .github/main.workflow create mode 100644 .github/workflows/pull_request-add-the-first-time-contributor-label-to-prs-opened-by-first-time-contributors.yml create mode 100644 .github/workflows/pull_request-assign-fixed-issues-when-pull-request-opened.yml create mode 100644 .github/workflows/pull_request-milestone-merged-pull-requests.yml diff --git a/.github/main.workflow b/.github/main.workflow deleted file mode 100644 index 50f5e8cb927036..00000000000000 --- a/.github/main.workflow +++ /dev/null @@ -1,36 +0,0 @@ -workflow "Milestone merged pull requests" { - on = "pull_request" - resolves = ["Milestone It"] -} - -action "Milestone It" { - uses = "./.github/actions/milestone-it" - secrets = ["GITHUB_TOKEN"] -} - -workflow "Assign fixed issues when pull request opened" { - on = "pull_request" - resolves = ["Assign Fixed Issues"] -} - -action "Filter opened" { - uses = "actions/bin/filter@0dbb077f64d0ec1068a644d25c71b1db66148a24" - args = "action opened" -} - -action "Assign Fixed Issues" { - uses = "./.github/actions/assign-fixed-issues" - needs = ["Filter opened"] - secrets = ["GITHUB_TOKEN"] -} - -workflow "Add the First-time Contributor label to PRs opened by first-time contributors" { - on = "pull_request" - resolves = ["First Time Contributor"] -} - -action "First Time Contributor" { - uses = "./.github/actions/first-time-contributor" - needs = ["Filter opened"] - secrets = ["GITHUB_TOKEN"] -} diff --git a/.github/workflows/pull_request-add-the-first-time-contributor-label-to-prs-opened-by-first-time-contributors.yml b/.github/workflows/pull_request-add-the-first-time-contributor-label-to-prs-opened-by-first-time-contributors.yml new file mode 100644 index 00000000000000..d5b8fbbe2e07bd --- /dev/null +++ b/.github/workflows/pull_request-add-the-first-time-contributor-label-to-prs-opened-by-first-time-contributors.yml @@ -0,0 +1,16 @@ +on: pull_request +name: Add the First-time Contributor label to PRs opened by first-time contributors +jobs: + filterOpened: + name: Filter opened + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Filter opened + uses: actions/bin/filter@0dbb077f64d0ec1068a644d25c71b1db66148a24 + with: + args: action opened + - name: First Time Contributor + uses: ./.github/actions/first-time-contributor + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pull_request-assign-fixed-issues-when-pull-request-opened.yml b/.github/workflows/pull_request-assign-fixed-issues-when-pull-request-opened.yml new file mode 100644 index 00000000000000..2b4f0ac9ec4e71 --- /dev/null +++ b/.github/workflows/pull_request-assign-fixed-issues-when-pull-request-opened.yml @@ -0,0 +1,16 @@ +on: pull_request +name: Assign fixed issues when pull request opened +jobs: + filterOpened: + name: Filter opened + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Filter opened + uses: actions/bin/filter@0dbb077f64d0ec1068a644d25c71b1db66148a24 + with: + args: action opened + - name: Assign Fixed Issues + uses: ./.github/actions/assign-fixed-issues + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pull_request-milestone-merged-pull-requests.yml b/.github/workflows/pull_request-milestone-merged-pull-requests.yml new file mode 100644 index 00000000000000..6487a9ed13b7de --- /dev/null +++ b/.github/workflows/pull_request-milestone-merged-pull-requests.yml @@ -0,0 +1,12 @@ +on: pull_request +name: Milestone merged pull requests +jobs: + milestoneIt: + name: Milestone It + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Milestone It + uses: ./.github/actions/milestone-it + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c7faf14771ebd2374830cb061e703a42a577b641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 9 Aug 2019 15:50:36 +0200 Subject: [PATCH 653/664] Writing Flow: fix empty horizontal arrow nav (#16846) * Writing Flow: fix horizontal arrow nav * Add e2e test * Add inline comment --- packages/dom/src/dom.js | 11 +++++++++-- .../specs/__snapshots__/writing-flow.test.js.snap | 14 ++++++++++++++ packages/e2e-tests/specs/writing-flow.test.js | 14 ++++++++++++++ packages/rich-text/src/to-tree.js | 4 ++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index ba005d6d3c4013..564dfb4a7fcfd7 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -163,7 +163,8 @@ function isEdge( container, isReverse, onlyVertical ) { const side = isReverseDir ? 'left' : 'right'; const testRect = getRectangleFromRange( testRange ); - return Math.round( testRect[ side ] ) === Math.round( rangeRect[ side ] ); + // Allow the position to be 1px off. + return Math.abs( testRect[ side ] - rangeRect[ side ] ) <= 1; } /** @@ -352,11 +353,17 @@ function caretRangeFromPoint( doc, x, y ) { * @return {?Range} The best range for the given point. */ function hiddenCaretRangeFromPoint( doc, x, y, container ) { + const originalZIndex = container.style.zIndex; + const originalPosition = container.style.position; + + // A z-index only works if the element position is not static. container.style.zIndex = '10000'; + container.style.position = 'relative'; const range = caretRangeFromPoint( doc, x, y ); - container.style.zIndex = null; + container.style.zIndex = originalZIndex; + container.style.position = originalPosition; return range; } diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap index 42f1ce81d3b6e9..c6c4bf5e8528e3 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -166,6 +166,20 @@ exports[`Writing Flow should navigate empty paragraph 1`] = ` <!-- /wp:paragraph -->" `; +exports[`Writing Flow should navigate empty paragraphs 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p></p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>3</p> +<!-- /wp:paragraph -->" +`; + exports[`Writing Flow should not create extra line breaks in multiline value 1`] = ` "<!-- wp:quote --> <blockquote class=\\"wp-block-quote\\"><p></p></blockquote> diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index f96b1f1602e661..f0683ff51e0f09 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -408,4 +408,18 @@ describe( 'Writing Flow', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should navigate empty paragraphs', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.type( '3' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index f1047228507603..6e50be6a42cdd5 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -255,6 +255,10 @@ export function toTree( { type: 'span', attributes: { 'data-rich-text-placeholder': placeholder, + // Necessary to prevent the placeholder from catching + // selection. The placeholder is also not editable after + // all. + contenteditable: 'false', }, } ); } From 60ec8fe0ccafea48a1642b77ce0ae06c46ef4ca3 Mon Sep 17 00:00:00 2001 From: Robert Anderson <robert@noisysocks.com> Date: Sat, 10 Aug 2019 03:28:24 +1000 Subject: [PATCH 654/664] DropdownMenu: Fix shifting menu items (#16871) Fix menu items in the More menu from shifting horizontally when selected. --- packages/components/src/dropdown-menu/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 23fce37ff6ebde..fe46fd30b8eccd 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -106,5 +106,9 @@ &.has-icon { padding-left: 0.5rem; } + + .dashicon { + margin-right: 4px; + } } } From be95231c28137bd1878783c4986c52c1af86add5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= <wp@iseulde.com> Date: Fri, 9 Aug 2019 19:34:58 +0200 Subject: [PATCH 655/664] API Fetch: refresh nonces as soon as they expired, then fetch again (#16683) * API Fetch: refresh nonces as soon as they expired, then fetch again * Use exit() * Fix PHP linting errors --- gutenberg.php | 8 ++++ lib/client-assets.php | 24 +++--------- packages/api-fetch/src/index.js | 37 +++++++++++++++---- packages/e2e-tests/plugins/nonce.php | 16 ++++++++ .../e2e-tests/specs/plugins/nonce.test.js | 35 ++++++++++++++++++ 5 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 packages/e2e-tests/plugins/nonce.php create mode 100644 packages/e2e-tests/specs/plugins/nonce.test.js diff --git a/gutenberg.php b/gutenberg.php index 7ab481538e32cc..3706a64b6ef741 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -128,3 +128,11 @@ function gutenberg_pre_init() { require_once dirname( __FILE__ ) . '/lib/load.php'; } + +/** + * Outputs a WP REST API nonce. + */ +function gutenberg_rest_nonce() { + exit( wp_create_nonce( 'wp_rest' ) ); +} +add_action( 'wp_ajax_gutenberg_rest_nonce', 'gutenberg_rest_nonce' ); diff --git a/lib/client-assets.php b/lib/client-assets.php index 4d07d1c51ac97e..8e6b1d688cb471 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -246,25 +246,11 @@ function gutenberg_register_scripts_and_styles() { wp_add_inline_script( 'wp-api-fetch', sprintf( - implode( - "\n", - array( - '( function() {', - ' var nonceMiddleware = wp.apiFetch.createNonceMiddleware( "%s" );', - ' wp.apiFetch.use( nonceMiddleware );', - ' wp.hooks.addAction(', - ' "heartbeat.tick",', - ' "core/api-fetch/create-nonce-middleware",', - ' function( response ) {', - ' if ( response[ "rest_nonce" ] ) {', - ' nonceMiddleware.nonce = response[ "rest_nonce" ];', - ' }', - ' }', - ' )', - '} )();', - ) - ), - ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ) + 'wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware( "%s" );' . + 'wp.apiFetch.use( wp.apiFetch.nonceMiddleware );' . + 'wp.apiFetch.nonceEndpoint = "%s";', + ( wp_installing() && ! is_multisite() ) ? '' : wp_create_nonce( 'wp_rest' ), + admin_url( 'admin-ajax.php?action=gutenberg_rest_nonce' ) ), 'after' ); diff --git a/packages/api-fetch/src/index.js b/packages/api-fetch/src/index.js index 27fb225fe94eb8..7535d2117b2f24 100644 --- a/packages/api-fetch/src/index.js +++ b/packages/api-fetch/src/index.js @@ -49,6 +49,14 @@ function registerMiddleware( middleware ) { middlewares.unshift( middleware ); } +const checkStatus = ( response ) => { + if ( response.status >= 200 && response.status < 300 ) { + return response; + } + + throw response; +}; + const defaultFetchHandler = ( nextOptions ) => { const { url, path, data, parse = true, ...remainingOptions } = nextOptions; let { body, headers } = nextOptions; @@ -71,13 +79,6 @@ const defaultFetchHandler = ( nextOptions ) => { headers, } ); - const checkStatus = ( response ) => { - if ( response.status >= 200 && response.status < 300 ) { - return response; - } - - throw response; - }; const parseResponse = ( response ) => { if ( parse ) { @@ -148,7 +149,27 @@ function apiFetch( options ) { return step( workingOptions, next ); }; - return createRunStep( 0 )( options ); + return new Promise( function( resolve, reject ) { + createRunStep( 0 )( options ) + .then( resolve ) + .catch( ( error ) => { + if ( error.code !== 'rest_cookie_invalid_nonce' ) { + return reject( error ); + } + + // If the nonce is invalid, refresh it and try again. + window.fetch( apiFetch.nonceEndpoint ) + .then( checkStatus ) + .then( ( data ) => data.text() ) + .then( ( text ) => { + apiFetch.nonceMiddleware.nonce = text; + apiFetch( options ) + .then( resolve ) + .catch( reject ); + } ) + .catch( reject ); + } ); + } ); } apiFetch.use = registerMiddleware; diff --git a/packages/e2e-tests/plugins/nonce.php b/packages/e2e-tests/plugins/nonce.php new file mode 100644 index 00000000000000..e28ff07ac48ad1 --- /dev/null +++ b/packages/e2e-tests/plugins/nonce.php @@ -0,0 +1,16 @@ +<?php +/** + * Plugin Name: Gutenberg Test Plugin, Nonce + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-plugin-nonce + */ + +/** + * Returns the nonce life time. + */ +function gutenberg_test_plugin_nonce_life() { + return 5; +} +add_filter( 'nonce_life', 'gutenberg_test_plugin_nonce_life' ); diff --git a/packages/e2e-tests/specs/plugins/nonce.test.js b/packages/e2e-tests/specs/plugins/nonce.test.js new file mode 100644 index 00000000000000..5fd76c4d11f510 --- /dev/null +++ b/packages/e2e-tests/specs/plugins/nonce.test.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + deactivatePlugin, + saveDraft, +} from '@wordpress/e2e-test-utils'; + +describe( 'Nonce', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-plugin-nonce' ); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-plugin-nonce' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'should refresh when expired', async () => { + await page.keyboard.press( 'Enter' ); + // eslint-disable-next-line no-restricted-syntax + await page.waitFor( 5000 ); + await page.keyboard.type( 'test' ); + // `saveDraft` waits for saving to be successful, so this test would + // timeout if it's not. + await saveDraft(); + // We expect a 403 status once. + expect( console ).toHaveErrored(); + } ); +} ); From 399e5e143a62a66101285de623cf47fab159bbdb Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Sat, 10 Aug 2019 10:49:42 +0100 Subject: [PATCH 656/664] Fix: Block paddings on the widget screen. (#16944) --- assets/stylesheets/_variables.scss | 3 +++ .../src/components/widget-area/style.scss | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/assets/stylesheets/_variables.scss b/assets/stylesheets/_variables.scss index a1eb8343385d11..652be4700ff22a 100644 --- a/assets/stylesheets/_variables.scss +++ b/assets/stylesheets/_variables.scss @@ -66,3 +66,6 @@ $block-bg-padding--h: $block-side-ui-width + $block-side-ui-clearance; // paddin // Buttons & UI Widgets $radius-round-rectangle: 4px; $radius-round: 50%; + +// Widgets screen +$widget-area-width: 700px; diff --git a/packages/edit-widgets/src/components/widget-area/style.scss b/packages/edit-widgets/src/components/widget-area/style.scss index cdf6480887c632..60f2ce9f1b7e7b 100644 --- a/packages/edit-widgets/src/components/widget-area/style.scss +++ b/packages/edit-widgets/src/components/widget-area/style.scss @@ -1,6 +1,18 @@ .edit-widgets-widget-area { - max-width: $content-width; + max-width: $widget-area-width; margin: 0 auto 30px; + + // Reduce padding inside widget areas + .block-editor-block-list__layout { + padding-left: 0; + padding-right: 0; + } + // By default the default block appender inserter has a negative position, + // but given that on the widget screen we have 0 padding we need to remove the negative position. + .block-editor-default-block-appender .block-editor-inserter, + .block-editor-block-list__empty-block-inserter { + left: 0; + } } .edit-widgets-main-block-list { padding-top: $block-toolbar-height + 2 * $grid-size; From f9b7275f4b7ade5e9315cb8b8136564f3907aea1 Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 12 Aug 2019 11:51:09 +0100 Subject: [PATCH 657/664] Fix: Some ServerSideRender imports (#16991) --- packages/block-library/src/calendar/edit.js | 2 +- packages/block-library/src/rss/edit.js | 2 +- packages/block-library/src/tag-cloud/edit.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/calendar/edit.js b/packages/block-library/src/calendar/edit.js index 19027eecf099f7..243387c6dec399 100644 --- a/packages/block-library/src/calendar/edit.js +++ b/packages/block-library/src/calendar/edit.js @@ -9,10 +9,10 @@ import memoize from 'memize'; */ import { Disabled, - ServerSideRender, } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; +import ServerSideRender from '@wordpress/server-side-render'; class CalendarEdit extends Component { constructor() { diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 43bbf41fd1ed2d..b360b3e0047f7d 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -8,7 +8,6 @@ import { PanelBody, Placeholder, RangeControl, - ServerSideRender, TextControl, ToggleControl, Toolbar, @@ -18,6 +17,7 @@ import { BlockControls, InspectorControls, } from '@wordpress/block-editor'; +import ServerSideRender from '@wordpress/server-side-render'; const DEFAULT_MIN_ITEMS = 1; const DEFAULT_MAX_ITEMS = 10; diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index 3540fffe53498f..2cda30f52e2c31 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -11,11 +11,11 @@ import { PanelBody, ToggleControl, SelectControl, - ServerSideRender, } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; +import ServerSideRender from '@wordpress/server-side-render'; class TagCloudEdit extends Component { constructor() { From 87e2c15be06d8d84a784a25494b2ea5cc36c7370 Mon Sep 17 00:00:00 2001 From: Derek Sifford <dereksifford@gmail.com> Date: Mon, 12 Aug 2019 06:53:06 -0400 Subject: [PATCH 658/664] Fix(eslint-plugin): misc bugfixes related to recent jsdoc changes (#16975) * fix(eslint-plugin): misc assorted fixes to jsdoc config * fix: misc jsdoc fixes * remove duplicate Readonly --- .../developers/data/data-core.md | 2 +- .../src/components/inserter/index.native.js | 2 + packages/components/src/tooltip/index.js | 2 +- packages/core-data/README.md | 2 +- packages/core-data/src/selectors.js | 2 +- packages/eslint-plugin/configs/es5.js | 3 ++ packages/eslint-plugin/configs/jsdoc.js | 39 +++++++++++++++++-- packages/eslint-plugin/configs/recommended.js | 1 - packages/token-list/src/index.js | 2 +- 9 files changed, 45 insertions(+), 10 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md index bdfbe68438a392..90db77965f5995 100644 --- a/docs/designers-developers/developers/data/data-core.md +++ b/docs/designers-developers/developers/data/data-core.md @@ -373,7 +373,7 @@ _Parameters_ _Returns_ -- `booleans`: Is the preview for the URL an oEmbed link fallback. +- `boolean`: Is the preview for the URL an oEmbed link fallback. <a name="isRequestingEmbedPreview" href="#isRequestingEmbedPreview">#</a> **isRequestingEmbedPreview** diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index 90009022f0bd35..b20eb8387992d6 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -45,6 +45,7 @@ class Inserter extends Component { /** * Render callback to display Dropdown toggle element. * + * @param {Object} options * @param {Function} options.onToggle Callback to invoke when toggle is * pressed. * @param {boolean} options.isOpen Whether dropdown is currently open. @@ -63,6 +64,7 @@ class Inserter extends Component { /** * Render callback to display Dropdown content element. * + * @param {Object} options * @param {Function} options.onClose Callback to invoke when dropdown is * closed. * diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index fd52b17ca487df..911fe0aeb05762 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -117,7 +117,7 @@ class Tooltip extends Component { * Creates an event callback to handle assignment of the `isInMouseDown` * instance property in response to a `mousedown` or `mouseup` event. * - * @param {booelan} isMouseDown Whether handler is to be created for the + * @param {boolean} isMouseDown Whether handler is to be created for the * `mousedown` event, as opposed to `mouseup`. * * @return {Function} Event callback handler. diff --git a/packages/core-data/README.md b/packages/core-data/README.md index cc89ec095f5117..64752e8ce030df 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -584,7 +584,7 @@ _Parameters_ _Returns_ -- `booleans`: Is the preview for the URL an oEmbed link fallback. +- `boolean`: Is the preview for the URL an oEmbed link fallback. <a name="isRequestingEmbedPreview" href="#isRequestingEmbedPreview">#</a> **isRequestingEmbedPreview** diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index c8aec286a1107c..0f5cc1dd507809 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -355,7 +355,7 @@ export function getEmbedPreview( state, url ) { * @param {Object} state Data state. * @param {string} url Embedded URL. * - * @return {booleans} Is the preview for the URL an oEmbed link fallback. + * @return {boolean} Is the preview for the URL an oEmbed link fallback. */ export function isPreviewEmbedFallback( state, url ) { const preview = state.embedPreviews[ url ]; diff --git a/packages/eslint-plugin/configs/es5.js b/packages/eslint-plugin/configs/es5.js index c81f5c2249f9d0..7ed0b7ff9d0722 100644 --- a/packages/eslint-plugin/configs/es5.js +++ b/packages/eslint-plugin/configs/es5.js @@ -1,4 +1,7 @@ module.exports = { + extends: [ + require.resolve( './jsdoc.js' ), + ], rules: { 'array-bracket-spacing': [ 'error', 'always' ], 'array-callback-return': 'error', diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index a8a34afe94f125..614816cb734a21 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -1,5 +1,32 @@ const globals = require( 'globals' ); +/** + * Helpful utilities that are globally defined and known to the TypeScript compiler. + * + * @see http://www.typescriptlang.org/docs/handbook/utility-types.html + */ +const typescriptUtilityTypes = [ + 'ArrayLike', + 'Exclude', + 'Extract', + 'InstanceType', + 'Iterable', + 'IterableIterator', + 'NonNullable', + 'Omit', + 'Partial', + 'Pick', + 'PromiseLike', + 'Readonly', + 'ReadonlyArray', + 'ReadonlyMap', + 'ReadonlySet', + 'Record', + 'Required', + 'ReturnType', + 'ThisType', +]; + module.exports = { extends: [ 'plugin:jsdoc/recommended', @@ -17,10 +44,14 @@ module.exports = { }, rules: { 'jsdoc/no-undefined-types': [ 'warn', { - // Required to reference browser types because we don't have the `browser` environment enabled for the project. - // Here we filter out all browser globals that don't begin with an uppercase letter because those - // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). - definedTypes: Object.keys( globals.browser ).filter( ( k ) => /^[A-Z]/.test( k ) ), + definedTypes: [ + // Required to reference browser types because we don't have the `browser` environment enabled for the project. + // Here we filter out all browser globals that don't begin with an uppercase letter because those + // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). + ...Object.keys( globals.browser ).filter( ( k ) => /^[A-Z]/.test( k ) ), + ...typescriptUtilityTypes, + 'void', + ], } ], 'jsdoc/require-jsdoc': 'off', 'jsdoc/require-param-description': 'off', diff --git a/packages/eslint-plugin/configs/recommended.js b/packages/eslint-plugin/configs/recommended.js index 96a64022379413..f7c264684ecaf9 100644 --- a/packages/eslint-plugin/configs/recommended.js +++ b/packages/eslint-plugin/configs/recommended.js @@ -5,7 +5,6 @@ module.exports = { require.resolve( './custom.js' ), require.resolve( './react.js' ), require.resolve( './esnext.js' ), - require.resolve( './jsdoc.js' ), ], env: { node: true, diff --git a/packages/token-list/src/index.js b/packages/token-list/src/index.js index acf8b79b68dacd..e97bfabaa77cf1 100644 --- a/packages/token-list/src/index.js +++ b/packages/token-list/src/index.js @@ -76,7 +76,7 @@ export default class TokenList { * * @see https://dom.spec.whatwg.org/#domtokenlist * - * @return {Generator} TokenList iterator. + * @return {IterableIterator<string>} TokenList iterator. */ * [ Symbol.iterator ]() { return yield* this._valueAsArray; From e2c01f757801328d72e08714b1ea6e7027cf6b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 12 Aug 2019 14:44:26 +0200 Subject: [PATCH 659/664] Components: Refactor DropdownMenu to stop using unstable props (#15968) * Remove __unstableLabelPosition by reusing position provided for the dropdown menu * Replaces unstable class names with the documented contentClassName prop * Refactor DropdownMenu component to allow passing props to nested components * Add CHANGELOG entries and deprecations messages for update props * Swap params for mergeProps helper --- .../components/block-settings-menu/index.js | 10 +-- .../components/block-settings-menu/style.scss | 8 +-- .../rich-text/format-toolbar/index.js | 6 +- packages/components/CHANGELOG.md | 10 ++- .../components/src/dropdown-menu/README.md | 41 +++++++----- .../components/src/dropdown-menu/index.js | 63 +++++++++++++++---- .../e2e-tests/specs/block-grouping.test.js | 2 +- .../src/components/header/more-menu/index.js | 13 +++- .../test/__snapshots__/index.js.snap | 22 +++++-- 9 files changed, 127 insertions(+), 48 deletions(-) diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 527f84ab0ca984..a0133182548025 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -25,6 +25,11 @@ import BlockUnknownConvertButton from './block-unknown-convert-button'; import __experimentalBlockSettingsMenuFirstItem from './block-settings-menu-first-item'; import __experimentalBlockSettingsMenuPluginsExtension from './block-settings-menu-plugins-extension'; +const POPOVER_PROPS = { + className: 'block-editor-block-settings-menu__popover editor-block-settings-menu__popover', + position: 'bottom right', +}; + export function BlockSettingsMenu( { clientIds } ) { const blockClientIds = castArray( clientIds ); const count = blockClientIds.length; @@ -45,11 +50,8 @@ export function BlockSettingsMenu( { clientIds } ) { <DropdownMenu icon="ellipsis" label={ __( 'More options' ) } - position="bottom right" className="block-editor-block-settings-menu" - __unstableToggleClassName="block-editor-block-settings-menu__toggle editor-block-settings-menu__toggle" - __unstableMenuClassName="block-editor-block-settings-menu__content editor-block-settings-menu__content" - __unstablePopoverClassName="block-editor-block-settings-menu__popover editor-block-settings-menu__popover" + popoverProps={ POPOVER_PROPS } > { ( { onClose } ) => ( <> diff --git a/packages/block-editor/src/components/block-settings-menu/style.scss b/packages/block-editor/src/components/block-settings-menu/style.scss index 0b690745012edd..d7b1ae4f5d7db3 100644 --- a/packages/block-editor/src/components/block-settings-menu/style.scss +++ b/packages/block-editor/src/components/block-settings-menu/style.scss @@ -1,7 +1,7 @@ -.block-editor-block-settings-menu__content { - padding: 0; +.block-editor-block-settings-menu .components-dropdown-menu__toggle .dashicon { + transform: rotate(90deg); } -.block-editor-block-settings-menu__toggle .dashicon { - transform: rotate(90deg); +.block-editor-block-settings-menu__popover .components-dropdown-menu__menu { + padding: 0; } diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js index e596de9d6ea28f..5e6fb5cf9846d6 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.js @@ -11,6 +11,10 @@ import { orderBy } from 'lodash'; import { __ } from '@wordpress/i18n'; import { Toolbar, Slot, DropdownMenu } from '@wordpress/components'; +const POPOVER_PROPS = { + position: 'bottom left', +}; + const FormatToolbar = () => { return ( <div className="editor-format-toolbar block-editor-format-toolbar"> @@ -22,9 +26,9 @@ const FormatToolbar = () => { { ( fills ) => fills.length !== 0 && <DropdownMenu icon={ false } - position="bottom left" label={ __( 'More Rich Text Controls' ) } controls={ orderBy( fills.map( ( [ { props } ] ) => props ), 'title' ) } + popoverProps={ POPOVER_PROPS } /> } </Slot> diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index afe7a14e81421a..77e05d4bbe59dd 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,8 +2,16 @@ ### New Features -- Added a new `popoverProps` prop to the `Dropdown` component which allows users of the `Dropdown` component to pass props directly to the `PopOver` component. +- Added a new `popoverProps` prop to the `Dropdown` component which allows users of the `Dropdown` component to pass props directly to the `Popover` component. - Added and documented `hideLabelFromVision` prop to `BaseControl` used by `SelectControl`, `TextControl`, and `TextareaControl`. +- Added a new `popoverProps` prop to the `DropdownMenu` component which allows to pass props directly to the nested `Popover` component. +- Added a new `toggleProps` prop to the `DropdownMenu` component which allows to pass props directly to the nested `IconButton` component. +- Added a new `menuProps` prop to the `DropdownMenu` component which allows to pass props directly to the nested `NavigableMenu` component. + +### Deprecations + +- `menuLabel` prop in `DropdownComponent` has been deprecated. Consider using `menuProps` object and its `aria-label` property instead. +- `position` prop in `DropdownComponent` has been deprecated. Consider using `popoverProps` object and its `position` property instead. ### Bug Fixes diff --git a/packages/components/src/dropdown-menu/README.md b/packages/components/src/dropdown-menu/README.md index f118a1bc4c241f..3cab663adeb143 100644 --- a/packages/components/src/dropdown-menu/README.md +++ b/packages/components/src/dropdown-menu/README.md @@ -163,21 +163,6 @@ A human-readable label to present as accessibility text on the focused collapsed - Type: `String` - Required: Yes -#### menuLabel - -A human-readable label to present as accessibility text on the expanded menu container. - -- Type: `String` -- Required: No - -#### position - -The direction in which the menu should open. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"middle"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis. - -- Type: `String` -- Required: No -- Default: `"top center"` - #### controls An array of objects describing the options to be shown in the expanded menu. @@ -202,7 +187,31 @@ See also: [https://developer.wordpress.org/resource/dashicons/](https://develope #### className -A class name to apply to the dropdown wrapper element. +A class name to apply to the dropdown menu's toggle element wrapper. - Type: `String` - Required: No + +#### popoverProps + +Properties of `popoverProps` object will be passed as props to the nested `Popover` component. +Use this object to modify props available for the `Popover` component that are not already exposed in the `DropdownMenu` component, e.g.: the direction in which the popover should open relative to its parent node set with `position` prop. + + - Type: `Object` + - Required: No + +#### toggleProps + +Properties of `toggleProps` object will be passed as props to the nested `IconButton` component in the `renderToggle` implementation of the `Dropdown` component used internally. +Use this object to modify props available for the `IconButton` component that are not already exposed in the `DropdownMenu` component, e.g.: the tooltip text displayed on hover set with `tooltip` prop. + + - Type: `Object` + - Required: No + +#### menuProps + +Properties of `menuProps` object will be passed as props to the nested `NavigableMenu` component in the `renderContent` implementation of the `Dropdown` component used internally. +Use this object to modify props available for the `NavigableMenu` component that are not already exposed in the `DropdownMenu` component, e.g.: the orientation of the menu set with `orientation` prop. + + - Type: `Object` + - Required: No diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 8212deaeda5956..e94b246231ad1b 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -8,6 +8,7 @@ import { flatMap, isEmpty, isFunction } from 'lodash'; * WordPress dependencies */ import { DOWN } from '@wordpress/keycodes'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -16,6 +17,19 @@ import IconButton from '../icon-button'; import Dropdown from '../dropdown'; import { NavigableMenu } from '../navigable-container'; +function mergeProps( defaultProps = {}, props = {} ) { + const mergedProps = { + ...defaultProps, + ...props, + }; + + if ( props.className && defaultProps.className ) { + mergedProps.className = classnames( props.className, defaultProps.className ); + } + + return mergedProps; +} + function DropdownMenu( { children, className, @@ -23,13 +37,27 @@ function DropdownMenu( { hasArrowIndicator = false, icon = 'menu', label, + popoverProps, + toggleProps, + menuProps, + // The following props exist for backward compatibility. menuLabel, position, - __unstableLabelPosition, - __unstableMenuClassName, - __unstablePopoverClassName, - __unstableToggleClassName, } ) { + if ( menuLabel ) { + deprecated( '`menuLabel` prop in `DropdownComponent`', { + alternative: '`menuProps` object and its `aria-label` property', + plugin: 'Gutenberg', + } ); + } + + if ( position ) { + deprecated( '`position` prop in `DropdownComponent`', { + alternative: '`popoverProps` object and its `position` property', + plugin: 'Gutenberg', + } ); + } + if ( isEmpty( controls ) && ! isFunction( children ) ) { return null; } @@ -42,12 +70,15 @@ function DropdownMenu( { controlSets = [ controlSets ]; } } + const mergedPopoverProps = mergeProps( { + className: 'components-dropdown-menu__popover', + position, + }, popoverProps ); return ( <Dropdown className={ classnames( 'components-dropdown-menu', className ) } - contentClassName={ classnames( 'components-dropdown-menu__popover', __unstablePopoverClassName ) } - position={ position } + popoverProps={ mergedPopoverProps } renderToggle={ ( { isOpen, onToggle } ) => { const openOnArrowDown = ( event ) => { if ( ! isOpen && event.keyCode === DOWN ) { @@ -56,31 +87,37 @@ function DropdownMenu( { onToggle(); } }; + const mergedToggleProps = mergeProps( { + className: classnames( 'components-dropdown-menu__toggle', { + 'is-opened': isOpen, + } ), + tooltip: label, + }, toggleProps ); return ( <IconButton - className={ classnames( 'components-dropdown-menu__toggle', __unstableToggleClassName, { - 'is-opened': isOpen, - } ) } + { ...mergedToggleProps } icon={ icon } onClick={ onToggle } onKeyDown={ openOnArrowDown } aria-haspopup="true" aria-expanded={ isOpen } label={ label } - labelPosition={ __unstableLabelPosition } - tooltip={ label } > { ( ! icon || hasArrowIndicator ) && <span className="components-dropdown-menu__indicator" /> } </IconButton> ); } } renderContent={ ( props ) => { + const mergedMenuProps = mergeProps( { + 'aria-label': menuLabel || label, + className: 'components-dropdown-menu__menu', + }, menuProps ); + return ( <NavigableMenu - className={ classnames( 'components-dropdown-menu__menu', __unstableMenuClassName ) } + { ...mergedMenuProps } role="menu" - aria-label={ menuLabel || label } > { isFunction( children ) ? diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/block-grouping.test.js index 64bba812f4f278..8d12894a6b3111 100644 --- a/packages/e2e-tests/specs/block-grouping.test.js +++ b/packages/e2e-tests/specs/block-grouping.test.js @@ -146,7 +146,7 @@ describe( 'Block Grouping', () => { it( 'does not show group option in the options toolbar if Grouping block is disabled ', async () => { await clickBlockToolbarButton( 'More options' ); - const blockOptionsDropdownHTML = await page.evaluate( () => document.querySelector( '.block-editor-block-settings-menu__content' ).innerHTML ); + const blockOptionsDropdownHTML = await page.evaluate( () => document.querySelector( '.block-editor-block-settings-menu__popover' ).innerHTML ); expect( blockOptionsDropdownHTML ).not.toContain( 'Group' ); } ); diff --git a/packages/edit-post/src/components/header/more-menu/index.js b/packages/edit-post/src/components/header/more-menu/index.js index 16d9b16afb77a1..c04be0527baafc 100644 --- a/packages/edit-post/src/components/header/more-menu/index.js +++ b/packages/edit-post/src/components/header/more-menu/index.js @@ -13,14 +13,21 @@ import ToolsMoreMenuGroup from '../tools-more-menu-group'; import OptionsMenuItem from '../options-menu-item'; import WritingMenu from '../writing-menu'; +const POPOVER_PROPS = { + className: 'edit-post-more-menu__content', + position: 'bottom left', +}; +const TOGGLE_PROPS = { + labelPosition: 'bottom', +}; + const MoreMenu = () => ( <DropdownMenu className="edit-post-more-menu" - position="bottom left" icon="ellipsis" label={ __( 'More tools & options' ) } - __unstableLabelPosition="bottom" - __unstablePopoverClassName="edit-post-more-menu__content" + popoverProps={ POPOVER_PROPS } + toggleProps={ TOGGLE_PROPS } > { ( { onClose } ) => ( <> diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index 889803c75d9107..3f01ba28572d48 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -3,17 +3,29 @@ exports[`MoreMenu should match snapshot 1`] = ` <MoreMenu> <DropdownMenu - __unstableLabelPosition="bottom" - __unstablePopoverClassName="edit-post-more-menu__content" className="edit-post-more-menu" icon="ellipsis" label="More tools & options" - position="bottom left" + popoverProps={ + Object { + "className": "edit-post-more-menu__content", + "position": "bottom left", + } + } + toggleProps={ + Object { + "labelPosition": "bottom", + } + } > <Dropdown className="components-dropdown-menu edit-post-more-menu" - contentClassName="components-dropdown-menu__popover edit-post-more-menu__content" - position="bottom left" + popoverProps={ + Object { + "className": "edit-post-more-menu__content components-dropdown-menu__popover", + "position": "bottom left", + } + } renderContent={[Function]} renderToggle={[Function]} > From f63e4a0707d1a557f897ca89dfe9c23e1257f27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= <grzegorz@gziolo.pl> Date: Mon, 12 Aug 2019 15:04:40 +0200 Subject: [PATCH 660/664] ESLint plugin: Enable `wp` global by default in the `recommended` config (#16904) --- .eslintrc.js | 9 ++++++--- packages/eslint-plugin/CHANGELOG.md | 1 + packages/eslint-plugin/configs/recommended.js | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index a33f618328f346..363cd259c838bf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,9 @@ module.exports = { plugins: [ 'import', ], + globals: { + wp: 'off', + }, rules: { '@wordpress/react-no-unsafe-timeout': 'error', 'no-restricted-syntax': [ @@ -126,9 +129,9 @@ module.exports = { browser: true, }, globals: { - browser: true, - page: true, - wp: true, + browser: 'readonly', + page: 'readonly', + wp: 'readonly', }, }, ], diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 836394fc27ae0f..b40d7e8bb8ff4e 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -8,6 +8,7 @@ ### New Features - New Rule: [`@wordpress/no-unguarded-get-range-at`](https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) +- Enable `wp` global by default in the `recommended` config. ## 2.4.0 (2019-08-05) diff --git a/packages/eslint-plugin/configs/recommended.js b/packages/eslint-plugin/configs/recommended.js index f7c264684ecaf9..5aaead8880681a 100644 --- a/packages/eslint-plugin/configs/recommended.js +++ b/packages/eslint-plugin/configs/recommended.js @@ -12,5 +12,6 @@ module.exports = { globals: { window: true, document: true, + wp: 'readonly', }, }; From d38a123cfc6494eabf66805094946e39b04a5abe Mon Sep 17 00:00:00 2001 From: Jorge Costa <jorge.costa@developer.pt> Date: Mon, 12 Aug 2019 16:04:17 +0100 Subject: [PATCH 661/664] Add deprecated call on wp.components.ServerSideRender; (#16133) --- lib/client-assets.php | 15 --------------- package-lock.json | 1 + packages/server-side-render/package.json | 1 + packages/server-side-render/src/index.js | 18 ++++++++++++++++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 8e6b1d688cb471..669f9597c76a16 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -275,21 +275,6 @@ function gutenberg_register_scripts_and_styles() { ) ); - // Add back compatibility for calls to wp.components.ServerSideRender. - wp_add_inline_script( - 'wp-server-side-render', - implode( - "\n", - array( - '( function() {', - ' if ( wp && wp.components && wp.serverSideRender && ! wp.components.ServerSideRender ) {', - ' wp.components.ServerSideRender = wp.serverSideRender;', - ' };', - '} )();', - ) - ) - ); - // Editor Styles. // This empty stylesheet is defined to ensure backward compatibility. gutenberg_override_style( 'wp-blocks', false ); diff --git a/package-lock.json b/package-lock.json index 4c8a714c7221bb..01441aa566aa81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5218,6 +5218,7 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/components": "file:packages/components", "@wordpress/data": "file:packages/data", + "@wordpress/deprecated": "file:packages/deprecated", "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", "@wordpress/url": "file:packages/url", diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index e43b579d0e4df9..6e6b353ddc3a48 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -26,6 +26,7 @@ "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/components": "file:../components", "@wordpress/data": "file:../data", + "@wordpress/deprecated": "file:../deprecated", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/url": "file:../url", diff --git a/packages/server-side-render/src/index.js b/packages/server-side-render/src/index.js index 3958ee46941366..d4881bcecdd219 100644 --- a/packages/server-side-render/src/index.js +++ b/packages/server-side-render/src/index.js @@ -1,8 +1,9 @@ /** * WordPress dependencies */ -import { useMemo } from '@wordpress/element'; +import { useMemo, forwardRef } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -14,7 +15,7 @@ import ServerSideRender from './server-side-render'; */ const EMPTY_OBJECT = {}; -export default withSelect( +const ExportedServerSideRender = withSelect( ( select ) => { const coreEditorSelect = select( 'core/editor' ); if ( coreEditorSelect ) { @@ -44,3 +45,16 @@ export default withSelect( ); } ); + +if ( window && window.wp && window.wp.components ) { + window.wp.components.ServerSideRender = forwardRef( ( props, ref ) => { + deprecated( 'wp.components.ServerSideRender', { + alternative: 'wp.serverSideRender', + } ); + return ( + <ExportedServerSideRender { ...props } ref={ ref } /> + ); + } ); +} + +export default ExportedServerSideRender; From bff8b7d0ca2348e342a4f663d3c0b8d6b1852317 Mon Sep 17 00:00:00 2001 From: Jorge <jorge.costa@developer.pt> Date: Mon, 12 Aug 2019 16:34:52 +0100 Subject: [PATCH 662/664] Bump plugin version to 6.3.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 3706a64b6ef741..136d5230c59687 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 6.2.0 + * Version: 6.3.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 01441aa566aa81..8a3aa3554f754c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.2.0", + "version": "6.3.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1b4628aac3b41d..46ebf924d604a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "6.2.0", + "version": "6.3.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 89bd6b124bd8930307e92dbb6b3157eb8bc0c833 Mon Sep 17 00:00:00 2001 From: Adam Silverstein <adam@10up.com> Date: Mon, 12 Aug 2019 10:28:26 -0600 Subject: [PATCH 663/664] =?UTF-8?q?Add=20examples=20for=20the=20lockPostSa?= =?UTF-8?q?ving=20and=20unlockPostSaving=20actio=E2=80=A6=20(#16713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add examples for the lockPostSaving and unlockPostSaving actions * build docs --- .../developers/data/data-core-editor.md | 40 ++++++++++++++++++ packages/editor/src/store/actions.js | 42 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/docs/designers-developers/developers/data/data-core-editor.md b/docs/designers-developers/developers/data/data-core-editor.md index b38e4ede0bf678..884d2cc3910eb8 100644 --- a/docs/designers-developers/developers/data/data-core-editor.md +++ b/docs/designers-developers/developers/data/data-core-editor.md @@ -1094,6 +1094,41 @@ _Related_ Returns an action object used to signal that post saving is locked. +_Usage_ + + const { subscribe } = wp.data; + + const initialPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + + // Only allow publishing posts that are set to a future date. + if ( 'publish' !== initialPostStatus ) { + + // Track locking. + let locked = false; + + // Watch for the publish event. + let unssubscribe = subscribe( () => { + const currentPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + if ( 'publish' !== currentPostStatus ) { + + // Compare the post date to the current date, lock the post if the date isn't in the future. + const postDate = new Date( wp.data.select( 'core/editor' ).getEditedPostAttribute( 'date' ) ); + const currentDate = new Date(); + if ( postDate.getTime() <= currentDate.getTime() ) { + if ( ! locked ) { + locked = true; + wp.data.dispatch( 'core/editor' ).lockPostSaving( 'futurelock' ); + } + } else { + if ( locked ) { + locked = false; + wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'futurelock' ); + } + } + } + } ); + } + _Parameters_ - _lockName_ `string`: The lock name. @@ -1330,6 +1365,11 @@ _Returns_ Returns an action object used to signal that post saving is unlocked. +_Usage_ + + // Unlock post saving with the lock key `mylock`: + wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'mylock' ); + _Parameters_ - _lockName_ `string`: The lock name. diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 36190a77b47ade..be98bc2ef8bc67 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -875,6 +875,42 @@ export function disablePublishSidebar() { * * @param {string} lockName The lock name. * + * @example + * ``` + * const { subscribe } = wp.data; + + * const initialPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + * + * // Only allow publishing posts that are set to a future date. + * if ( 'publish' !== initialPostStatus ) { + * + * // Track locking. + * let locked = false; + * + * // Watch for the publish event. + * let unssubscribe = subscribe( () => { + * const currentPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + * if ( 'publish' !== currentPostStatus ) { + * + * // Compare the post date to the current date, lock the post if the date isn't in the future. + * const postDate = new Date( wp.data.select( 'core/editor' ).getEditedPostAttribute( 'date' ) ); + * const currentDate = new Date(); + * if ( postDate.getTime() <= currentDate.getTime() ) { + * if ( ! locked ) { + * locked = true; + * wp.data.dispatch( 'core/editor' ).lockPostSaving( 'futurelock' ); + * } + * } else { + * if ( locked ) { + * locked = false; + * wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'futurelock' ); + * } + * } + * } + * } ); + * } + * ``` + * * @return {Object} Action object */ export function lockPostSaving( lockName ) { @@ -889,6 +925,12 @@ export function lockPostSaving( lockName ) { * * @param {string} lockName The lock name. * + * @example + * ``` + * // Unlock post saving with the lock key `mylock`: + * wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'mylock' ); + * ``` + * * @return {Object} Action object */ export function unlockPostSaving( lockName ) { From 8bbe76c7e3c779238bc88cc58796083d1f3ac876 Mon Sep 17 00:00:00 2001 From: Dave Whitley <drw158@gmail.com> Date: Mon, 12 Aug 2019 13:56:53 -0500 Subject: [PATCH 664/664] Documentation: adding new components (#16845) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit Add component contribution guidelines * Apply suggestions from code review Small tweaks to improve copy. Co-Authored-By: sarah ✈ semark <sarah@triggersandsparks.com> * Copy tweaks * Simplify language --- packages/components/src/CONTRIBUTING.md | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 packages/components/src/CONTRIBUTING.md diff --git a/packages/components/src/CONTRIBUTING.md b/packages/components/src/CONTRIBUTING.md new file mode 100644 index 00000000000000..8acfc7811cf81b --- /dev/null +++ b/packages/components/src/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing components + +You can contribute by adding, modifying, or deprecating components, as well as helping with design, development, and documentation. + +## Does it belong in the component library? + +A component library should include components that are generic and flexible enough to work across a variety of products. It should include what’s shared across many products and omit what’s not. + +To determine if a component should be added, ask yourself: + +- Could this component be used by other products/plugins? +- Does the new component overlap (in functionality or visual design) with any existing components? +- How much effort will be required to make and maintain? +- Is there a clear purpose for the component? + +Here’s a flowchart that can help determine if a new component is necessary: + +[![New component flowchart](https://wordpress.org/gutenberg/files/2019/07/New_component_flowchart.png)](https://coggle.it/diagram/WtUSrld3uAYZHsn-/t/new-ui-component/992b38cbe685d897b4aec6d0dd93cc4b47c06e0d4484eeb0d7d9a47fb2c48d94) + +## First steps + +If you have a component you'd like added or changed, start by opening a GitHub issue. Include a detailed description in which you: + +- Explain the rationale +- Detail the intended behavior +- Clarify whether it’s a variation of an existing component, or a new asset +- Include mockups of any fidelity (optional) +- Include any inspirations from other products (optional) + +This issue will be used to discuss the proposed changes and track progress. Reviewers start by discussing the proposal to determine if it's appropriate for WordPress Components, or if there's overlap with an existing component. + +It’s encouraged to surface works-in-progress. If you’re not able to complete all of the parts yourself, someone in the community may be able to pick up where you left off. + +## Next steps + +Once the team has discussed and approved the change, it's time to start implementing it. + +1. **Provide a rationale**: Explain how your component will add value to the system and the greater product ecosystem. Be sure to include any user experience and interaction descriptions. +2. **Draft documentation**: New components need development, design, and accessibility guidelines. Additionally, if your change adds additional behavior or expands a component’s features, those changes will need to be fully documented as well. Read through existing component documentation for examples. Start with a rough draft, and reviewers will help polish documentation. +3. **Provide working code**: The component or enhancement must be built in React. See the [developer contribution guidelines](https://github.com/WordPress/gutenberg/blob/master/docs/contributors/develop.md). +4. **Create a design spec**: Create sizing and styling annotations for all aspects of the component. This spec should provide a developer with everything they need to create the design in code. [Figma automatically does this for you](https://help.figma.com/article/32-developer-handoff). +5. **Create a Figma component**: Any new components or changes to existing components will be mirrored in the [WordPress Components Figma library](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=735%3A0), so we’ll need to update the Figma library and publish the changes. Please follow the [guidelines](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=746%3A38) for contributing to the Figma libraries. + +Remember, it’s unlikely that all parts will be done by one person. Contribute where you can, and others will help. + +## Component refinement + +Before a component is published it will need to be fine-tuned: + +1. **Expand** the features of the component to a minimum. Agree on what features should be included. +2. **Reduce** scope and leave off features lacking consensus. +3. **Quality assurance**: each contribution must adhere to system standards. + +### Quality assurance + +To ensure quality, each component should be tested. The testing process should be done during the development of the component and before the component is published. + +- **Accessibility**: Has the design and implementation accounted for accessibility? Please use the [WordPress accessibility guidelines](https://make.wordpress.org/accessibility/handbook/best-practices/). You must use the "Needs Accessibility Feedback" label and get a review from the accessibility team. It's best to request a review early (at the documentation stage) in order to ensure the component is designed inclusively from the outset. +- **Visual quality**: Does the component apply visual style — color, typography, icons, space, borders, and more — using appropriate variables, and does it follow [visual guidelines](https://make.wordpress.org/design/handbook/design-guide/)? You must use the "Needs Design Feedback" label and get a review from the design team. +- **Documentation**: Ensure that the component has proper documentation for development, design, and accessibility. +- **Sufficient states & variations**: Does it cover all the necessary variations (primary, secondary, dense, etc.) and states (default, hover, active, disabled, loading, etc.), within the intended scope? +- **Functionality**: Do all behaviors function as expected? +- **Responsiveness**: Does it incorporate responsive behaviors as needed? Is the component designed from a mobile-first perspective? Do all touch interactions work as expected? +- **Content resilience**: Is each dynamic word or image element resilient to too much, too little, and no content at all, respectively? How long can labels be, and what happens when you run out of space? +- **Composability**: Does it fit well when placed next to or layered with other components to form a larger composition? +- **Browser support**: Has the component visual quality and accuracy been checked across Safari, Chrome, Firefox, IE, etc? Please adhere to our [browser support requirements](https://github.com/WordPress/gutenberg/blob/master/packages/browserslist-config/index.js). + +## Deprecation + +A component or prop may need deprecation if it's renamed or removed. + +At no point does the deprecated component/prop become unavailable. Instead, the commitment to support it stops. The following steps are needed to deprecate a component: + +1. Communicate intent via regular channels. + 1. Describe reasoning for deprecation. + 2. Decide on a timeline. +2. Ensure backwards compatibility to keep it available. +3. Clearly mark the component as deprecated in documentation and other channels.