' . $attributes['moreText'] . '' : '';
-
- $filter_excerpt_length = function() use ( $attributes ) {
- return isset( $attributes['wordCount'] ) ? $attributes['wordCount'] : 55;
+ $more_text = ! empty( $attributes['moreText'] ) ? '' . $attributes['moreText'] . ' ' : '';
+ $filter_excerpt_more = function( $more ) use ( $more_text ) {
+ return empty( $more_text ) ? $more : '';
};
- add_filter(
- 'excerpt_length',
- $filter_excerpt_length
- );
-
+ /**
+ * Some themes might use `excerpt_more` filter to handle the
+ * `more` link displayed after a trimmed excerpt. Since the
+ * block has a `more text` attribute we have to check and
+ * override if needed the return value from this filter.
+ * So if the block's attribute is not empty override the
+ * `excerpt_more` filter and return nothing. This will
+ * result in showing only one `read more` link at a time.
+ */
+ add_filter( 'excerpt_more', $filter_excerpt_more );
$classes = '';
if ( isset( $attributes['textAlign'] ) ) {
- $classes .= 'has-text-align-' . $attributes['textAlign'];
+ $classes .= "has-text-align-{$attributes['textAlign']}";
}
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) );
- $content = '' . get_the_excerpt( $block->context['postId'] );
- if ( ! isset( $attributes['showMoreOnNewLine'] ) || $attributes['showMoreOnNewLine'] ) {
+ $content = '
' . get_the_excerpt( $block->context['postId'] );
+ $show_more_on_new_line = ! isset( $attributes['showMoreOnNewLine'] ) || $attributes['showMoreOnNewLine'];
+ if ( $show_more_on_new_line && ! empty( $more_text ) ) {
$content .= '
' . $more_text . '
';
} else {
$content .= " $more_text";
}
-
- remove_filter(
- 'excerpt_length',
- $filter_excerpt_length
- );
-
+ remove_filter( 'excerpt_more', $filter_excerpt_more );
return sprintf( '%2$s
', $wrapper_attributes, $content );
}
diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js
index 95094f261cff93..ee07dea9ef7717 100644
--- a/packages/components/src/autocomplete/index.js
+++ b/packages/components/src/autocomplete/index.js
@@ -443,6 +443,7 @@ function useAutocomplete( {
useEffect( () => {
if ( ! textContent ) {
+ reset();
return;
}
diff --git a/packages/components/src/form-token-field/suggestions-list.js b/packages/components/src/form-token-field/suggestions-list.js
index 0f8b44c4068a0e..1ad0ace0a3b89e 100644
--- a/packages/components/src/form-token-field/suggestions-list.js
+++ b/packages/components/src/form-token-field/suggestions-list.js
@@ -21,7 +21,11 @@ class SuggestionsList extends Component {
componentDidUpdate() {
// only have to worry about scrolling selected suggestion into view
// when already expanded
- if ( this.props.selectedIndex > -1 && this.props.scrollIntoView ) {
+ if (
+ this.props.selectedIndex > -1 &&
+ this.props.scrollIntoView &&
+ this.list.children[ this.props.selectedIndex ]
+ ) {
this.scrollingIntoView = true;
scrollIntoView(
this.list.children[ this.props.selectedIndex ],
diff --git a/packages/e2e-tests/fixtures/blocks/core__post-excerpt.json b/packages/e2e-tests/fixtures/blocks/core__post-excerpt.json
index 57d658a4423d05..4378d6e27b6071 100644
--- a/packages/e2e-tests/fixtures/blocks/core__post-excerpt.json
+++ b/packages/e2e-tests/fixtures/blocks/core__post-excerpt.json
@@ -4,7 +4,6 @@
"name": "core/post-excerpt",
"isValid": true,
"attributes": {
- "wordCount": 55,
"showMoreOnNewLine": true
},
"innerBlocks": [],
diff --git a/packages/e2e-tests/plugins/iframed-inline-styles.php b/packages/e2e-tests/plugins/iframed-inline-styles.php
new file mode 100644
index 00000000000000..f54c8eb83e6233
--- /dev/null
+++ b/packages/e2e-tests/plugins/iframed-inline-styles.php
@@ -0,0 +1,52 @@
+ {
+ const { createElement: el } = element;
+ const { registerBlockType } = blocks;
+ const { useBlockProps } = blockEditor;
+
+ registerBlockType( 'test/iframed-inline-styles', {
+ apiVersion: 2,
+ edit: function Edit() {
+ return el( 'div', useBlockProps(), 'Edit' );
+ },
+ save: function Save() {
+ return el( 'div', useBlockProps.save(), 'Save' );
+ },
+ } );
+} )( window );
diff --git a/packages/e2e-tests/plugins/iframed-inline-styles/style.css b/packages/e2e-tests/plugins/iframed-inline-styles/style.css
new file mode 100644
index 00000000000000..23ddfa85290811
--- /dev/null
+++ b/packages/e2e-tests/plugins/iframed-inline-styles/style.css
@@ -0,0 +1,9 @@
+/**
+ * The following styles get applied both on the front of your site and in the
+ * editor.
+ */
+.wp-block-test-iframed-inline-styles {
+ background-color: #21759b;
+ color: #fff;
+ padding: 2px;
+}
diff --git a/packages/e2e-tests/plugins/iframed-masonry-block/editor.js b/packages/e2e-tests/plugins/iframed-masonry-block/editor.js
index a6d2697fe3930c..2833815c0a1aef 100644
--- a/packages/e2e-tests/plugins/iframed-masonry-block/editor.js
+++ b/packages/e2e-tests/plugins/iframed-masonry-block/editor.js
@@ -28,7 +28,7 @@
el( 'div', { className: 'grid-item' } ),
el( 'div', { className: 'grid-item' } ),
el( 'div', { className: 'grid-item grid-item--height2' } ),
- ]
+ ];
registerBlockType( 'test/iframed-masonry-block', {
edit: function Edit() {
diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-inline-styles.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-inline-styles.test.js.snap
new file mode 100644
index 00000000000000..46d3e807bff832
--- /dev/null
+++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-inline-styles.test.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`iframed inline styles should load inline styles in iframe 1`] = `
+"
+Save
+"
+`;
diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-masonry-block.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-masonry-block.test.js.snap
index 1c0d56386164f5..0033b3aff44d12 100644
--- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-masonry-block.test.js.snap
+++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/iframed-masonry-block.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`changing image size should load script and dependencies in iframe 1`] = `
+exports[`iframed masonry block should load script and dependencies in iframe 1`] = `
"
"
diff --git a/packages/e2e-tests/specs/editor/plugins/iframed-inline-styles.test.js b/packages/e2e-tests/specs/editor/plugins/iframed-inline-styles.test.js
new file mode 100644
index 00000000000000..298674e96b7701
--- /dev/null
+++ b/packages/e2e-tests/specs/editor/plugins/iframed-inline-styles.test.js
@@ -0,0 +1,67 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ activatePlugin,
+ createNewPost,
+ deactivatePlugin,
+ insertBlock,
+ getEditedPostContent,
+ openDocumentSettingsSidebar,
+ clickButton,
+ canvas,
+} from '@wordpress/e2e-test-utils';
+
+async function getComputedStyle( context, property ) {
+ await context.waitForSelector( '.wp-block-test-iframed-inline-styles' );
+ return await context.evaluate( ( prop ) => {
+ const container = document.querySelector(
+ '.wp-block-test-iframed-inline-styles'
+ );
+ return window.getComputedStyle( container )[ prop ];
+ }, property );
+}
+
+describe( 'iframed inline styles', () => {
+ beforeEach( async () => {
+ await activatePlugin( 'gutenberg-test-iframed-inline-styles' );
+ await createNewPost( { postType: 'page' } );
+ } );
+
+ afterEach( async () => {
+ await deactivatePlugin( 'gutenberg-test-iframed-inline-styles' );
+ } );
+
+ it( 'should load inline styles in iframe', async () => {
+ await insertBlock( 'Iframed Inline Styles' );
+
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ expect( await getComputedStyle( page, 'padding' ) ).toBe( '20px' );
+ expect( await getComputedStyle( page, 'border-width' ) ).toBe( '2px' );
+
+ await openDocumentSettingsSidebar();
+ await clickButton( 'Page' );
+ await clickButton( 'Template' );
+ await clickButton( 'New' );
+ await page.keyboard.press( 'Tab' );
+ await page.keyboard.press( 'Tab' );
+ await page.keyboard.type( 'Iframed Test' );
+ await clickButton( 'Create' );
+ await page.waitForSelector( 'iframe[name="editor-canvas"]' );
+
+ // Inline styles of properly enqueued stylesheet should load.
+ expect( await getComputedStyle( canvas(), 'padding' ) ).toBe( '20px' );
+
+ // Inline styles of stylesheet loaded with the compatibility layer
+ // should load.
+ expect( await getComputedStyle( canvas(), 'border-width' ) ).toBe(
+ '2px'
+ );
+
+ expect( console ).toHaveErrored(
+ `Stylesheet iframed-inline-styles-compat-style-css was not properly added.
+For blocks, use the block API's style (https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#style) or editorStyle (https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#editor-style).
+For themes, use add_editor_style (https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-support/#editor-styles). `
+ );
+ } );
+} );
diff --git a/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js b/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js
index 5c12f2abb1b6aa..7877442e5695ef 100644
--- a/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js
+++ b/packages/e2e-tests/specs/editor/plugins/iframed-masonry-block.test.js
@@ -26,7 +26,7 @@ async function didMasonryLoadCorrectly( context ) {
} );
}
-describe( 'changing image size', () => {
+describe( 'iframed masonry block', () => {
beforeEach( async () => {
await activatePlugin( 'gutenberg-test-iframed-masonry-block' );
await createNewPost( { postType: 'page' } );
diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap
index fd0466cb4b3bb8..ab04f0b8689245 100644
--- a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap
+++ b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap
@@ -162,6 +162,18 @@ exports[`Multi-block selection should return original focus after failed multi s
"
`;
+exports[`Multi-block selection should select all from empty selection 1`] = `
+"
+1
+
+
+
+2
+"
+`;
+
+exports[`Multi-block selection should select all from empty selection 2`] = `""`;
+
exports[`Multi-block selection should set attributes for multiple paragraphs 1`] = `
"
1
diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap
index 873b1e305b39b3..bb3517f9c28324 100644
--- a/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap
+++ b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap
@@ -110,6 +110,12 @@ exports[`RichText should return focus when pressing formatting button 1`] = `
"
`;
+exports[`RichText should run input rules after composition end 1`] = `
+"
+a
+"
+`;
+
exports[`RichText should split rich text on paste 1`] = `
"
a
diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js
index 8eef794c3f5a8a..fded2c6d7e9146 100644
--- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js
+++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js
@@ -636,4 +636,27 @@ describe( 'Multi-block selection', () => {
'[data-type="core/paragraph"].is-multi-selected'
);
} );
+
+ it( 'should select all from empty selection', async () => {
+ await clickBlockAppender();
+
+ await page.keyboard.type( '1' );
+ await page.keyboard.press( 'Enter' );
+ await page.keyboard.type( '2' );
+
+ // Confirm setup.
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+
+ // Clear the selected block.
+ const paragraph = await page.$( '[data-type="core/paragraph"]' );
+ const box = await paragraph.boundingBox();
+ await page.mouse.click( box.x - 1, box.y );
+
+ await pressKeyWithModifier( 'primary', 'a' );
+
+ await page.keyboard.press( 'Backspace' );
+
+ // Expect both paragraphs to be deleted.
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
} );
diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js
index 39e38297d5bdee..33e0bc64317efd 100644
--- a/packages/e2e-tests/specs/editor/various/rich-text.test.js
+++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js
@@ -451,4 +451,23 @@ describe( 'RichText', () => {
await page.keyboard.press( 'ArrowLeft' );
expect( await page.$( blockToolbarSelector ) ).toBe( null );
} );
+
+ it( 'should run input rules after composition end', async () => {
+ await clickBlockAppender();
+ // Puppeteer doesn't support composition, so emulate it by inserting
+ // text in the DOM directly, setting selection in the right place, and
+ // firing `compositionend`.
+ // See https://github.com/puppeteer/puppeteer/issues/4981.
+ await page.evaluate( () => {
+ document.activeElement.textContent = '`a`';
+ const selection = window.getSelection();
+ selection.selectAllChildren( document.activeElement );
+ selection.collapseToEnd();
+ document.activeElement.dispatchEvent(
+ new CompositionEvent( 'compositionend' )
+ );
+ } );
+
+ expect( await getEditedPostContent() ).toMatchSnapshot();
+ } );
} );
diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js
index 01e2847f1b7f9c..85104e58dca95d 100644
--- a/packages/edit-post/src/components/layout/index.js
+++ b/packages/edit-post/src/components/layout/index.js
@@ -12,6 +12,7 @@ import {
UnsavedChangesWarning,
EditorNotices,
EditorKeyboardShortcutsRegister,
+ EditorSnackbars,
store as editorStore,
} from '@wordpress/editor';
import { AsyncModeProvider, useSelect, useDispatch } from '@wordpress/data';
@@ -220,6 +221,7 @@ function Layout( { styles } ) {
>
)
}
+ notices={ }
content={
<>
diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js
index c8f6898ffaed7e..9718bbaad697a4 100644
--- a/packages/edit-post/src/components/visual-editor/index.js
+++ b/packages/edit-post/src/components/visual-editor/index.js
@@ -57,13 +57,14 @@ function MaybeIframe( {
return (
<>
-
{ children }
-
+
>
);
}
@@ -75,7 +76,7 @@ function MaybeIframe( {
contentRef={ contentRef }
style={ { width: '100%', height: '100%', display: 'block' } }
>
- { children }
+ { children }
);
}
@@ -232,18 +233,14 @@ export default function VisualEditor( { styles } ) {
layout={ defaultLayout }
/>
) }
-
- { ! isTemplateMode && (
-
- ) }
-
-
-
-
+ { ! isTemplateMode && (
+
+ ) }
+
+
+
diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js
index f02348d10f8d7d..6c729c518211e1 100644
--- a/packages/edit-site/src/components/editor/index.js
+++ b/packages/edit-site/src/components/editor/index.js
@@ -19,6 +19,7 @@ import {
} from '@wordpress/interface';
import {
EditorNotices,
+ EditorSnackbars,
EntitiesSavedStates,
UnsavedChangesWarning,
store as editorStore,
@@ -213,6 +214,7 @@ function Editor( { initialSettings } ) {
}
/>
}
+ notices={ }
content={
<>
diff --git a/packages/edit-widgets/src/blocks/widget-area/edit/index.js b/packages/edit-widgets/src/blocks/widget-area/edit/index.js
index 1957361586e5a6..d9c62a7153e9a6 100644
--- a/packages/edit-widgets/src/blocks/widget-area/edit/index.js
+++ b/packages/edit-widgets/src/blocks/widget-area/edit/index.js
@@ -78,7 +78,7 @@ export default function WidgetAreaEdit( {
type="postType"
id={ `widget-area-${ id }` }
>
-
+
) }
diff --git a/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js b/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js
index 38d17428fac5f2..83800d875e62cd 100644
--- a/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js
+++ b/packages/edit-widgets/src/blocks/widget-area/edit/inner-blocks.js
@@ -18,7 +18,7 @@ import { useRef } from '@wordpress/element';
*/
import useIsDraggingWithin from './use-is-dragging-within';
-export default function WidgetAreaInnerBlocks() {
+export default function WidgetAreaInnerBlocks( { id } ) {
const [ blocks, onInput, onChange ] = useEntityBlockEditor(
'root',
'postType'
@@ -40,6 +40,7 @@ export default function WidgetAreaInnerBlocks() {
return (
@@ -42,11 +39,6 @@ export function EditorNotices( { notices, onRemove } ) {
>
-
>
);
}
diff --git a/packages/editor/src/components/editor-snackbars/index.js b/packages/editor/src/components/editor-snackbars/index.js
new file mode 100644
index 00000000000000..6dc01435eb4b45
--- /dev/null
+++ b/packages/editor/src/components/editor-snackbars/index.js
@@ -0,0 +1,30 @@
+/**
+ * External dependencies
+ */
+import { filter } from 'lodash';
+
+/**
+ * WordPress dependencies
+ */
+import { SnackbarList } from '@wordpress/components';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { store as noticesStore } from '@wordpress/notices';
+
+export default function EditorSnackbars() {
+ const notices = useSelect(
+ ( select ) => select( noticesStore ).getNotices(),
+ []
+ );
+ const { removeNotice } = useDispatch( noticesStore );
+ const snackbarNotices = filter( notices, {
+ type: 'snackbar',
+ } );
+
+ return (
+
+ );
+}
diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js
index c2a281781a2ca6..50dfd6885166d2 100644
--- a/packages/editor/src/components/index.js
+++ b/packages/editor/src/components/index.js
@@ -11,6 +11,7 @@ export { default as EditorKeyboardShortcutsRegister } from './global-keyboard-sh
export { default as EditorHistoryRedo } from './editor-history/redo';
export { default as EditorHistoryUndo } from './editor-history/undo';
export { default as EditorNotices } from './editor-notices';
+export { default as EditorSnackbars } from './editor-snackbars';
export { default as EntitiesSavedStates } from './entities-saved-states';
export { default as ErrorBoundary } from './error-boundary';
export { default as LocalAutosaveMonitor } from './local-autosave-monitor';
diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js
index 184f0ddde30056..26bf20681f659a 100644
--- a/packages/interface/src/components/interface-skeleton/index.js
+++ b/packages/interface/src/components/interface-skeleton/index.js
@@ -3,6 +3,9 @@
*/
import classnames from 'classnames';
+/**
+ * WordPress dependencies
+ */
/**
* WordPress dependencies
*/
@@ -31,6 +34,7 @@ function InterfaceSkeleton(
header,
sidebar,
secondarySidebar,
+ notices,
content,
drawer,
actions,
@@ -105,6 +109,11 @@ function InterfaceSkeleton(
{ secondarySidebar }
) }
+ { !! notices && (
+
+ { notices }
+
+ ) }
{
+ return isKeyboardEvent[ modifier ]( event, character );
+ }
+ );
+ }
+
+ return isMatch;
+}
diff --git a/packages/keyboard-shortcuts/src/index.js b/packages/keyboard-shortcuts/src/index.js
index a57718af9c566e..26f2b004b6e04b 100644
--- a/packages/keyboard-shortcuts/src/index.js
+++ b/packages/keyboard-shortcuts/src/index.js
@@ -1,2 +1,3 @@
export { store } from './store';
export { default as useShortcut } from './hooks/use-shortcut';
+export { default as __unstableUseShortcutEventMatch } from './hooks/use-shortcut-event-match';
diff --git a/packages/keyboard-shortcuts/src/store/selectors.js b/packages/keyboard-shortcuts/src/store/selectors.js
index 4bf4c4c9b9c5aa..c4a286e954e11c 100644
--- a/packages/keyboard-shortcuts/src/store/selectors.js
+++ b/packages/keyboard-shortcuts/src/store/selectors.js
@@ -116,6 +116,16 @@ export function getShortcutAliases( state, name ) {
: EMPTY_ARRAY;
}
+export const getAllShortcutKeyCombinations = createSelector(
+ ( state, name ) => {
+ return compact( [
+ getShortcutKeyCombination( state, name ),
+ ...getShortcutAliases( state, name ),
+ ] );
+ },
+ ( state, name ) => [ state[ name ] ]
+);
+
/**
* Returns the raw representation of all the keyboard combinations of a given shortcut name.
*
@@ -126,15 +136,12 @@ export function getShortcutAliases( state, name ) {
*/
export const getAllShortcutRawKeyCombinations = createSelector(
( state, name ) => {
- return compact( [
- getKeyCombinationRepresentation(
- getShortcutKeyCombination( state, name ),
- 'raw'
- ),
- ...getShortcutAliases( state, name ).map( ( combination ) =>
- getKeyCombinationRepresentation( combination, 'raw' )
- ),
- ] );
+ return getAllShortcutKeyCombinations(
+ state,
+ name
+ ).map( ( combination ) =>
+ getKeyCombinationRepresentation( combination, 'raw' )
+ );
},
( state, name ) => [ state[ name ] ]
);
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index 7498e298443493..d5221e455bfe90 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -1387,7 +1387,7 @@ function test_get_from_editor_settings() {
),
),
'spacing' => array(
- 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ),
+ 'units' => array( 'px', 'em', 'rem', 'vh', 'vw', '%' ),
),
'typography' => array(
'customFontSize' => false,
@@ -1489,7 +1489,7 @@ function test_get_editor_settings_custom_units_can_be_enabled() {
$input = gutenberg_get_default_block_editor_settings();
$expected = array(
- 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ),
+ 'units' => array( 'px', 'em', 'rem', 'vh', 'vw', '%' ),
'customPadding' => false,
);