diff --git a/packages/api-fetch/src/middlewares/preloading.js b/packages/api-fetch/src/middlewares/preloading.js index 020cb913fced15..4de356243b7366 100644 --- a/packages/api-fetch/src/middlewares/preloading.js +++ b/packages/api-fetch/src/middlewares/preloading.js @@ -34,7 +34,11 @@ const createPreloadingMiddleware = ( preloadedData ) => ( options, next ) => { if ( parse && 'GET' === method && preloadedData[ path ] ) { return Promise.resolve( preloadedData[ path ].body ); - } else if ( 'OPTIONS' === method && preloadedData[ method ][ path ] ) { + } else if ( + 'OPTIONS' === method && + preloadedData[ method ] && + preloadedData[ method ][ path ] + ) { return Promise.resolve( preloadedData[ method ][ path ] ); } } diff --git a/packages/api-fetch/src/middlewares/test/preloading.js b/packages/api-fetch/src/middlewares/test/preloading.js index d697af3b429494..e2d8a1f4d0c68d 100644 --- a/packages/api-fetch/src/middlewares/test/preloading.js +++ b/packages/api-fetch/src/middlewares/test/preloading.js @@ -25,20 +25,29 @@ describe( 'Preloading Middleware', () => { } ); } ); - it( 'should move to the next middleware if no preloaded data', () => { - const preloadedData = {}; - const prelooadingMiddleware = createPreloadingMiddleware( preloadedData ); - const requestOptions = { - method: 'GET', - path: 'wp/v2/posts', - }; + describe.each( [ + [ 'GET' ], + [ 'OPTIONS' ], + ] )( '%s', ( method ) => { + describe.each( [ + [ 'all empty', {} ], + [ 'method empty', { [ method ]: {} } ], + ] )( '%s', ( label, preloadedData ) => { + it( 'should move to the next middleware if no preloaded data', () => { + const prelooadingMiddleware = createPreloadingMiddleware( preloadedData ); + const requestOptions = { + method, + path: 'wp/v2/posts', + }; - const callback = ( options ) => { - expect( options ).toBe( requestOptions ); - return true; - }; + const callback = ( options ) => { + expect( options ).toBe( requestOptions ); + return true; + }; - const ret = prelooadingMiddleware( requestOptions, callback ); - expect( ret ).toBe( true ); + const ret = prelooadingMiddleware( requestOptions, callback ); + expect( ret ).toBe( true ); + } ); + } ); } ); } ); diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 5c4cae61450ac4..4306b855955f52 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.0 (Unreleased) + +### Breaking Changes + +- `CopyHandler` will now only catch cut/copy events coming from its `props.children`, instead of from anywhere in the `document`. + ## 1.0.0 (2019-03-06) ### New Features diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 4a72cb3ed3423d..5f48fe5a165dba 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -904,6 +904,12 @@ .block-editor-block-contextual-toolbar > * { pointer-events: auto; } + + // Full-aligned blocks have negative margins on the parent of the toolbar, so additional position adjustment is not required. + &[data-align="full"] .block-editor-block-contextual-toolbar { + left: 0; + right: 0; + } } .block-editor-block-list__block.is-focus-mode:not(.is-multi-selected) > .block-editor-block-contextual-toolbar { diff --git a/packages/block-editor/src/components/block-settings-menu/block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js index 2ae65fed66cfd9..f27cac38b1df9a 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-convert-button.js +++ b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js @@ -15,7 +15,6 @@ export default function BlockConvertButton( { shouldRender, onClick, small } ) { className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onClick } icon="screenoptions" - label={ small ? label : undefined } > { ! small && label } 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 8ba029fd1794c1..3f8260d135e441 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 @@ -26,7 +26,6 @@ export function BlockModeToggle( { blockType, mode, onToggleMode, small = false className="editor-block-settings-menu__control block-editor-block-settings-menu__control" onClick={ onToggleMode } icon="html" - label={ small ? label : undefined } > { ! small && label } diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 3942eaae71b034..3f73d5f672c153 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -1,33 +1,17 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; import { serialize } from '@wordpress/blocks'; import { documentHasSelection } from '@wordpress/dom'; import { withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -class CopyHandler extends Component { - constructor() { - super( ...arguments ); - - this.onCopy = ( event ) => this.props.onCopy( event ); - this.onCut = ( event ) => this.props.onCut( event ); - } - - componentDidMount() { - document.addEventListener( 'copy', this.onCopy ); - document.addEventListener( 'cut', this.onCut ); - } - - componentWillUnmount() { - document.removeEventListener( 'copy', this.onCopy ); - document.removeEventListener( 'cut', this.onCut ); - } - - render() { - return null; - } +function CopyHandler( { children, onCopy, onCut } ) { + return ( +
+ { children } +
+ ); } export default compose( [ diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 006ec4a697760b..c6cfad310c89c6 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -42,6 +42,8 @@ import { isCollapsed, LINE_SEPARATOR, indentListItems, + __unstableGetActiveFormats, + __unstableUpdateFormats, } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withFilters, IsolatedEventContainer } from '@wordpress/components'; @@ -82,6 +84,13 @@ const INSERTION_INPUT_TYPES_TO_IGNORE = new Set( [ 'insertLink', ] ); +/** + * Global stylesheet. + */ +const globalStyle = document.createElement( 'style' ); + +document.head.appendChild( globalStyle ); + export class RichText extends Component { constructor( { value, onReplace, multiline } ) { super( ...arguments ); @@ -126,7 +135,10 @@ export class RichText extends Component { this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); this.onPointerDown = this.onPointerDown.bind( this ); - this.formatToValue = memize( this.formatToValue.bind( this ), { size: 1 } ); + this.formatToValue = memize( + this.formatToValue.bind( this ), + { maxSize: 1 } + ); this.savedContent = value; this.patterns = getPatterns( { @@ -176,9 +188,9 @@ export class RichText extends Component { */ getRecord() { const { formats, replacements, text } = this.formatToValue( this.props.value ); - const { start, end, selectedFormat } = this.state; + const { start, end, activeFormats } = this.state; - return { formats, replacements, text, start, end, selectedFormat }; + return { formats, replacements, text, start, end, activeFormats }; } createRecord() { @@ -355,6 +367,8 @@ export class RichText extends Component { unstableOnFocus(); } + this.recalculateBoundaryStyle(); + document.addEventListener( 'selectionchange', this.onSelectionChange ); } @@ -393,40 +407,19 @@ export class RichText extends Component { } } - let { selectedFormat } = this.state; - const { formats, replacements, text, start, end } = this.createRecord(); - - if ( this.formatPlaceholder ) { - selectedFormat = this.formatPlaceholder.length; - - if ( selectedFormat > 0 ) { - formats[ this.state.start ] = this.formatPlaceholder; - } else { - delete formats[ this.state.start ]; - } - } else if ( selectedFormat > 0 ) { - const formatsBefore = formats[ start - 1 ] || []; - const formatsAfter = formats[ start ] || []; - - let source = formatsBefore; - - if ( formatsAfter.length > formatsBefore.length ) { - source = formatsAfter; - } - - source = source.slice( 0, selectedFormat ); - - formats[ this.state.start ] = source; - } else { - delete formats[ this.state.start ]; - } - - const change = { formats, replacements, text, start, end, selectedFormat }; + const value = this.createRecord(); + const { activeFormats = [], start } = this.state; - this.onChange( change, { - withoutHistory: true, + // Update the formats between the last and new caret position. + const change = __unstableUpdateFormats( { + value, + start, + end: value.start, + formats: activeFormats, } ); + this.onChange( change, { withoutHistory: true } ); + const transformed = this.patterns.reduce( ( accumlator, transform ) => transform( accumlator ), change @@ -434,7 +427,7 @@ export class RichText extends Component { if ( transformed !== change ) { this.onCreateUndoLevel(); - this.onChange( { ...transformed, selectedFormat } ); + this.onChange( { ...transformed, activeFormats } ); } // Create an undo level when input stops for over a second. @@ -454,36 +447,40 @@ export class RichText extends Component { * Handles the `selectionchange` event: sync the selection to local state. */ onSelectionChange() { - if ( this.ignoreSelectionChange ) { - delete this.ignoreSelectionChange; - return; - } - const value = this.createRecord(); - const { start, end, formats } = value; + const { start, end } = value; if ( start !== this.state.start || end !== this.state.end ) { - const isCaretWithinFormattedText = this.props.isCaretWithinFormattedText; + const { isCaretWithinFormattedText } = this.props; + const activeFormats = __unstableGetActiveFormats( value ); - if ( ! isCaretWithinFormattedText && formats[ start ] ) { + if ( ! isCaretWithinFormattedText && activeFormats.length ) { this.props.onEnterFormattedText(); - } else if ( isCaretWithinFormattedText && ! formats[ start ] ) { + } else if ( isCaretWithinFormattedText && ! activeFormats.length ) { this.props.onExitFormattedText(); } - let selectedFormat; - - if ( isCollapsed( value ) ) { - const formatsBefore = formats[ start - 1 ] || []; - const formatsAfter = formats[ start ] || []; + this.setState( { start, end, activeFormats } ); + this.applyRecord( { ...value, activeFormats }, { domOnly: true } ); - selectedFormat = Math.min( formatsBefore.length, formatsAfter.length ); + if ( activeFormats.length > 0 ) { + this.recalculateBoundaryStyle(); } + } + } + + recalculateBoundaryStyle() { + const boundarySelector = '*[data-rich-text-format-boundary]'; + const element = this.editableRef.querySelector( boundarySelector ); - this.setState( { start, end, selectedFormat } ); - this.applyRecord( { ...value, selectedFormat }, { domOnly: true } ); + if ( element ) { + const computedStyle = getComputedStyle( element ); + const newColor = computedStyle.color + .replace( ')', ', 0.2)' ) + .replace( 'rgb', 'rgba' ); - delete this.formatPlaceholder; + globalStyle.innerHTML = + `*:focus ${ boundarySelector }{background-color: ${ newColor }}`; } } @@ -511,14 +508,12 @@ export class RichText extends Component { onChange( record, { withoutHistory } = {} ) { this.applyRecord( record ); - const { start, end, formatPlaceholder, selectedFormat } = record; + const { start, end, activeFormats = [] } = record; - this.formatPlaceholder = formatPlaceholder; this.onChangeEditableValue( record ); - this.savedContent = this.valueToFormat( record ); this.props.onChange( this.savedContent ); - this.setState( { start, end, selectedFormat } ); + this.setState( { start, end, activeFormats } ); if ( ! withoutHistory ) { this.onCreateUndoLevel(); @@ -734,17 +729,15 @@ export class RichText extends Component { handleHorizontalNavigation( event ) { const value = this.createRecord(); const { formats, text, start, end } = value; - const { selectedFormat } = this.state; + const { activeFormats = [] } = this.state; const collapsed = isCollapsed( value ); const isReverse = event.keyCode === LEFT; - delete this.formatPlaceholder; - // 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 && selectedFormat === 0 ) { + if ( collapsed && activeFormats.length === 0 ) { if ( start === 0 && isReverse ) { return; } @@ -764,38 +757,43 @@ export class RichText extends Component { // In all other cases, prevent default behaviour. event.preventDefault(); - // Ignore the selection change handler when setting selection, all state - // will be set here. - this.ignoreSelectionChange = true; - const formatsBefore = formats[ start - 1 ] || []; const formatsAfter = formats[ start ] || []; - let newSelectedFormat = selectedFormat; + 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 && selectedFormat < formatsAfter.length ) { - newSelectedFormat++; + if ( ! isReverse && activeFormats.length < formatsAfter.length ) { + newActiveFormatsLength++; } - if ( isReverse && selectedFormat > formatsBefore.length ) { - newSelectedFormat--; + if ( isReverse && activeFormats.length > formatsBefore.length ) { + newActiveFormatsLength--; } } else if ( formatsBefore.length > formatsAfter.length ) { - if ( ! isReverse && selectedFormat > formatsAfter.length ) { - newSelectedFormat--; + if ( ! isReverse && activeFormats.length > formatsAfter.length ) { + newActiveFormatsLength--; } - if ( isReverse && selectedFormat < formatsBefore.length ) { - newSelectedFormat++; + if ( isReverse && activeFormats.length < formatsBefore.length ) { + newActiveFormatsLength++; } } - if ( newSelectedFormat !== selectedFormat ) { - this.applyRecord( { ...value, selectedFormat: newSelectedFormat } ); - this.setState( { selectedFormat: newSelectedFormat } ); + // Wait for boundary class to be added. + setTimeout( () => this.recalculateBoundaryStyle() ); + + if ( newActiveFormatsLength !== activeFormats.length ) { + const newActiveFormats = source.slice( 0, newActiveFormatsLength ); + this.applyRecord( { ...value, activeFormats: newActiveFormats } ); + this.setState( { activeFormats: newActiveFormats } ); return; } @@ -806,7 +804,7 @@ export class RichText extends Component { ...value, start: newPos, end: newPos, - selectedFormat: isReverse ? formatsBefore.length : formatsAfter.length, + activeFormats: isReverse ? formatsBefore : formatsAfter, } ); } diff --git a/packages/block-editor/src/components/rich-text/style.scss b/packages/block-editor/src/components/rich-text/style.scss index 16fdc282020e0c..4a52ddb56fcd37 100644 --- a/packages/block-editor/src/components/rich-text/style.scss +++ b/packages/block-editor/src/components/rich-text/style.scss @@ -45,27 +45,6 @@ *[data-rich-text-format-boundary] { border-radius: 2px; - box-shadow: 0 0 0 1px $light-gray-400; - background: $light-gray-400; - - // Enforce a dark text color so active inline boundaries - // are always readable. - // See https://github.com/WordPress/gutenberg/issues/9508 - color: $dark-gray-900; - } - - // Link inline boundaries get special colors. - a[data-rich-text-format-boundary] { - box-shadow: 0 0 0 1px $blue-medium-100; - background: $blue-medium-100; - color: $blue-medium-900; - } - - // inline boundaries need special treatment because their - // un-selected style is already padded. - code[data-rich-text-format-boundary] { - background: $light-gray-400; - box-shadow: 0 0 0 1px $light-gray-400; } } diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index c74979dc0969d4..b368b3ee24c05d 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -1,10 +1,5 @@ .wp-block-cover-image, .wp-block-cover { - .block-editor-rich-text__editable:focus a[data-rich-text-format-boundary] { - box-shadow: none; - background: rgba(255, 255, 255, 0.3); - } - &.components-placeholder h2 { color: inherit; } diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index 0e251a8f0aafd0..fcc305d9e8a7c3 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -61,13 +61,24 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { if ( switchedPreview || switchedURL ) { if ( this.props.cannotEmbed ) { - // Can't embed this URL, and we've just received or switched the preview. + // We either have a new preview or a new URL, but we can't embed it. + if ( ! this.props.fetching ) { + // If we're not fetching the preview, then we know it can't be embedded, so try + // removing any trailing slash, and resubmit. + this.resubmitWithoutTrailingSlash(); + } return; } this.handleIncomingPreview(); } } + resubmitWithoutTrailingSlash() { + this.setState( ( prevState ) => ( { + url: prevState.url.replace( /\/$/, '' ), + } ), this.setUrl ); + } + setUrl( event ) { if ( event ) { event.preventDefault(); diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 085ea4b22ff8c8..9cba9f50364745 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -46,7 +46,7 @@ export const findBlock = ( url ) => { }; export const isFromWordPress = ( html ) => { - return includes( html, 'class="wp-embedded-content" data-secret' ); + return includes( html, 'class="wp-embedded-content"' ); }; export const getPhotoHtml = ( photo ) => { diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index 0340f66a31e8e5..34b08797e2e558 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -82,10 +82,6 @@ ul.wp-block-gallery li { a { color: $white; } - - &:focus a[data-rich-text-format-boundary] { - color: rgba(0, 0, 0, 0.2); - } } } diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index 7d90224f9639cd..9553ac77885f98 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -68,10 +68,14 @@ export const settings = { return ( ' ) } onChange={ ( nextContent ) => { setAttributes( { - content: nextContent, + // Ensure line breaks are normalised to characters. This + // saves space, is easier to read, and ensures display + // filters work correctly. + content: nextContent.replace( /
/g, '\n' ), } ); } } placeholder={ __( 'Write preformatted text…' ) } diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index b40164f549689b..1d67540c0b9225 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -127,9 +127,14 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam // First of all, strip any meta tags. HTML = HTML.replace( /]+>/, '' ); - // If we detect block delimiters, parse entirely as blocks. - if ( mode !== 'INLINE' && HTML.indexOf( ' -
Pre text

Foo
+
Pre text
+
+Foo
diff --git a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap index 6986ffc94968bc..f68e40f606dc4c 100644 --- a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap @@ -6,6 +6,8 @@ exports[`Multi-block selection should allow selecting outer edge if there is no " `; +exports[`Multi-block selection should always expand single line selection 1`] = `""`; + exports[`Multi-block selection should only trigger multi-selection when at the end 1`] = ` "

1.

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 f5c79ddbd33ac5..bf5fefd31a7e32 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap @@ -18,6 +18,12 @@ exports[`RichText should apply formatting with primary shortcut 1`] = ` " `; +exports[`RichText should apply multiple formats when selection is collapsed 1`] = ` +" +

1.

+" +`; + exports[`RichText should handle change in tag name gracefully 1`] = ` "

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 41f6fe04a2b531..2a80f20633a953 100644 --- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap +++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap @@ -126,6 +126,16 @@ exports[`adding blocks should navigate around nested inline boundaries 2`] = ` " `; +exports[`adding blocks should navigate empty paragraph 1`] = ` +" +

1

+ + + +

2

+" +`; + exports[`adding blocks should not create extra line breaks in multiline value 1`] = ` "

diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap index 4538816e15b411..07a70a19a6b2be 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap +++ b/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap @@ -9,6 +9,8 @@ exports[`Preformatted should preserve character newlines 1`] = ` exports[`Preformatted should preserve character newlines 2`] = ` " -
0
1
2
+
0
+1
+2
" `; diff --git a/packages/e2e-tests/specs/embedding.test.js b/packages/e2e-tests/specs/embedding.test.js index 3ea51358b14b21..6fb26c02281c70 100644 --- a/packages/e2e-tests/specs/embedding.test.js +++ b/packages/e2e-tests/specs/embedding.test.js @@ -9,6 +9,8 @@ import { createJSONResponse, getEditedPostContent, clickButton, + insertBlock, + publishPost, } from '@wordpress/e2e-test-utils'; const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { @@ -60,6 +62,10 @@ const MOCK_BAD_WORDPRESS_RESPONSE = { }; const MOCK_RESPONSES = [ + { + match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook' ), + onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), + }, { match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook/' ), onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), @@ -80,6 +86,10 @@ const MOCK_RESPONSES = [ match: createEmbeddingMatcher( 'https://twitter.com/notnownikki' ), onRequestMatch: createJSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), }, + { + match: createEmbeddingMatcher( 'https://twitter.com/notnownikki/' ), + onRequestMatch: createJSONResponse( MOCK_CANT_EMBED_RESPONSE ), + }, { match: createEmbeddingMatcher( 'https://twitter.com/thatbunty' ), onRequestMatch: createJSONResponse( MOCK_BAD_EMBED_PROVIDER_RESPONSE ), @@ -173,6 +183,17 @@ describe( 'Embedding content', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should retry embeds that could not be embedded with trailing slashes, without the trailing slashes', async () => { + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + // This URL can't be embedded, but without the trailing slash, it can. + await page.keyboard.type( 'https://twitter.com/notnownikki/' ); + await page.keyboard.press( 'Enter' ); + // The twitter block should appear correctly. + await page.waitForSelector( 'figure.wp-block-embed-twitter' ); + } ); + it( 'should allow the user to try embedding a failed URL again', async () => { // URL that can't be embedded. await clickBlockAppender(); @@ -192,4 +213,27 @@ describe( 'Embedding content', () => { await clickButton( 'Try again' ); await page.waitForSelector( 'figure.wp-block-embed-twitter' ); } ); + + it( 'should switch to the WordPress block correctly', async () => { + // This test is to make sure that WordPress embeds are detected correctly, + // because the HTML can vary, and the block is detected by looking for + // classes in the HTML, so we need to flag up if the HTML changes. + + // Publish a post to embed. + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Hello there!' ); + await publishPost(); + const postUrl = await page.$eval( '#inspector-text-control-0', ( el ) => el.value ); + + // Start a new post, embed the previous post. + await createNewPost(); + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( postUrl ); + await page.keyboard.press( 'Enter' ); + + // Check the block has become a WordPress block. + await page.waitForSelector( '.wp-block-embed-wordpress' ); + } ); } ); diff --git a/packages/e2e-tests/specs/multi-block-selection.test.js b/packages/e2e-tests/specs/multi-block-selection.test.js index 76ea211367660d..6bf7a62bf7789e 100644 --- a/packages/e2e-tests/specs/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/multi-block-selection.test.js @@ -181,6 +181,19 @@ describe( 'Multi-block selection', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should always expand single line selection', async () => { + await clickBlockAppender(); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '12' ); + await page.keyboard.press( 'ArrowLeft' ); + await pressKeyWithModifier( 'shift', 'ArrowRight' ); + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + // This delete all blocks. + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should allow selecting outer edge if there is no sibling block', async () => { await clickBlockAppender(); await page.keyboard.type( '1' ); diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js index 5153032953899e..dcb1434abd89be 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/rich-text.test.js @@ -58,6 +58,34 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should apply multiple formats when selection is collapsed', async () => { + await clickBlockAppender(); + await pressKeyWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'i' ); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'primary', 'i' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '.' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should not highlight more than one format', async () => { + await clickBlockAppender(); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( ' 2' ); + await pressKeyWithModifier( 'shift', 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'b' ); + + const count = await page.evaluate( () => document.querySelectorAll( + '*[data-rich-text-format-boundary]' + ).length ); + + expect( count ).toBe( 1 ); + } ); + it( 'should return focus when pressing formatting button', async () => { await clickBlockAppender(); await page.keyboard.type( 'Some ' ); diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js index 62ece158016405..c186507a7cdef3 100644 --- a/packages/e2e-tests/specs/writing-flow.test.js +++ b/packages/e2e-tests/specs/writing-flow.test.js @@ -298,4 +298,16 @@ describe( 'adding blocks', () => { // Check that none of the paragraph blocks have
in them. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + 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' ); + await page.keyboard.type( '2' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index db8c68da542295..4e58838aade5e6 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -5,7 +5,9 @@ * Expose the `className` property to style the `PluginSidebar` component. ### Bug Fixes - - Fix 'save' keyboard shortcut not functioning in the Code Editor. + +- Fix 'save' keyboard shortcut not functioning in the Code Editor. +- Prevent `ClipboardButton` from incorrectly copying a serialized block string instead of the intended text in Safari. ## 3.1.7 (2019-01-03) diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js index 810d558346cc79..7a7451a983ce3b 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -6,7 +6,7 @@ import { difference } from 'lodash'; /** * WordPress dependencies */ -import { IconButton } from '@wordpress/components'; +import { MenuItem } from '@wordpress/components'; import { compose } from '@wordpress/compose'; /** @@ -89,7 +89,7 @@ const PluginBlockSettingsMenuItem = ( { allowedBlocks, icon, label, onClick, sma if ( ! shouldRenderItem( selectedBlocks, allowedBlocks ) ) { return null; } - return ( { ! small && label } - ); + ); } } ); diff --git a/packages/edit-post/src/components/header/feature-toggle/index.js b/packages/edit-post/src/components/header/feature-toggle/index.js index 4e0df7f3749b08..b855ca046c3866 100644 --- a/packages/edit-post/src/components/header/feature-toggle/index.js +++ b/packages/edit-post/src/components/header/feature-toggle/index.js @@ -26,7 +26,6 @@ function FeatureToggle( { onToggle, isActive, label, info, messageActivated, mes isSelected={ isActive } onClick={ flow( onToggle, speakMessage ) } role="menuitemcheckbox" - label={ label } info={ info } > { label } 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..7fa0869a60f223 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 @@ -18,6 +18,7 @@ exports[`PluginMoreMenuItem renders menu item as button properly 1`] = ` >