diff --git a/.travis.yml b/.travis.yml
index 7b87e251a2525..43b1058c0e1fa 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,7 @@ cache:
branches:
only:
- - master
+ - wp/5.2
before_install:
- nvm install
diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md
index 3942b64d18006..1d1089561ed2a 100644
--- a/packages/block-editor/README.md
+++ b/packages/block-editor/README.md
@@ -483,5 +483,4 @@ Undocumented declaration.
-
diff --git a/packages/block-editor/src/components/rich-text/editable.js b/packages/block-editor/src/components/rich-text/editable.js
index 58c7217e1944c..c7c9a854fc0b8 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 c6cfad310c89c..403134acdc808 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -56,7 +56,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';
@@ -202,7 +202,6 @@ export class RichText extends Component {
range,
multilineTag: this.multilineTag,
multilineWrapperTags: this.multilineWrapperTags,
- prepareEditableTree: this.props.prepareEditableTree,
__unstableIsEditableTree: true,
} );
}
@@ -473,15 +472,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 }}`;
}
/**
@@ -731,7 +733,10 @@ export class RichText extends Component {
const { formats, text, start, end } = value;
const { activeFormats = [] } = this.state;
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.
diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js
index d03b11d0a84f7..784b21fa9fe4a 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
@@ -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( target );
+ }
+
// 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,11 +293,10 @@ class WritingFlow extends Component {
return;
}
- if ( ! isVertical ) {
- this.verticalRect = null;
- } else if ( ! this.verticalRect ) {
- this.verticalRect = computeCaretRect( target );
- }
+ // 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 +327,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/block-library/README.md b/packages/block-library/README.md
index 4c7d9cd0ae139..78573bbf753e7 100644
--- a/packages/block-library/README.md
+++ b/packages/block-library/README.md
@@ -33,5 +33,4 @@ registerCoreBlocks();
-
diff --git a/packages/blocks/README.md b/packages/blocks/README.md
index 0f5805c5244d0..960edae9da773 100644
--- a/packages/blocks/README.md
+++ b/packages/blocks/README.md
@@ -845,5 +845,4 @@ wrapped component.
-
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 0000000000000..fbfa319ccb831
--- /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 1d67540c0b922..c99b4ea5c5d0a 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 0000000000000..6721e20bec23b
--- /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(
+ '',
+ [ commentRemover ]
+ ) ).toEqual(
+ ''
+ );
+ } );
+ it( 'should remove multiple comments', () => {
+ expect( deepFilterHTML(
+ 'First paragraph.
Second paragraph.
',
+ [ commentRemover ]
+ ) ).toEqual(
+ 'First paragraph.
Second paragraph.
'
+ );
+ } );
+ it( 'should remove nested comments', () => {
+ expect( deepFilterHTML(
+ 'Paragraph.
',
+ [ commentRemover ]
+ ) ).toEqual(
+ 'Paragraph.
'
+ );
+ } );
+ it( 'should remove multi-line comments', () => {
+ expect( deepFilterHTML(
+ `First paragraph.
Second paragraph.
`,
+ [ commentRemover ]
+ ) ).toEqual(
+ 'First paragraph.
Second paragraph.
'
+ );
+ } );
+} );
diff --git a/packages/dom/README.md b/packages/dom/README.md
index e003966ab6775..c6b9945545602 100644
--- a/packages/dom/README.md
+++ b/packages/dom/README.md
@@ -268,5 +268,4 @@ Wraps the given node with a new node with the given tag name.
-
diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js
index aba561f992d59..c2491a0c07be1 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/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js
index 4800da23a860f..dac807458e291 100644
--- a/packages/e2e-tests/config/setup-test-framework.js
+++ b/packages/e2e-tests/config/setup-test-framework.js
@@ -123,6 +123,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/__snapshots__/links.test.js.snap b/packages/e2e-tests/specs/__snapshots__/links.test.js.snap
index d63515e5d2ee3..292ac9e5781a9 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`] = `
This is WordPress
"
`;
+
+exports[`Links should contain a label when it should open in a new tab 2`] = `
+"
+This is WordPress
+"
+`;
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 bf5fefd31a7e3..8aab0dcd5b734 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`]
"
`;
+exports[`RichText should handle Home and End keys 1`] = `
+"
+-12 +
+"
+`;
+
exports[`RichText should handle change in tag name gracefully 1`] = `
"
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 0000000000000..19b4e8c305acc
--- /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`] = `
+"
+٠١٢
+"
+`;
+
+exports[`RTL should arrow navigate between blocks 1`] = `
+"
+٠ ١
+
+
+
+٠ ١ ٢
+"
+`;
+
+exports[`RTL should merge backward 1`] = `
+"
+٠١
+"
+`;
+
+exports[`RTL should merge forward 1`] = `
+"
+٠١
+"
+`;
+
+exports[`RTL should navigate inline boundaries 1`] = `
+"
+١ ٠٢
+"
+`;
+
+exports[`RTL should navigate inline boundaries 2`] = `
+"
+١٠ ٢
+"
+`;
+
+exports[`RTL should navigate inline boundaries 3`] = `
+"
+٠١ ٢
+"
+`;
+
+exports[`RTL should navigate inline boundaries 4`] = `
+"
+٠١ ٢
+"
+`;
+
+exports[`RTL should split 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 0e41868f3cffa..e4c846f0631d0 100644
--- a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap
+++ b/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap
@@ -197,3 +197,23 @@ exports[`adding blocks should not prematurely multi-select 1`] = `
>
"
`;
+
+exports[`adding blocks should preserve horizontal position when navigating vertically between blocks 1`] = `
+"
+abc
+
+
+
+123
+"
+`;
+
+exports[`adding blocks should remember initial vertical position 1`] = `
+"
+1x
+
+
+
+ 2
+"
+`;
diff --git a/packages/e2e-tests/specs/blocks/classic.test.js b/packages/e2e-tests/specs/blocks/classic.test.js
index 2df2d1f2c8982..29d1605f0874b 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]' );
diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/links.test.js
index a41aa953bd8e7..b97a9cde0d08b 100644
--- a/packages/e2e-tests/specs/links.test.js
+++ b/packages/e2e-tests/specs/links.test.js
@@ -308,7 +308,9 @@ describe( 'Links', () => {
} );
// Test for regressions of https://github.com/WordPress/gutenberg/issues/10496.
- it( 'allows autocomplete suggestions to be navigated with the keyboard', async () => {
+ // This test isn't reliable on Travis and fails from time to time.
+ // See: https://github.com/WordPress/gutenberg/pull/15211.
+ it.skip( 'allows autocomplete suggestions to be navigated with the keyboard', async () => {
const titleText = 'Test post keyboard';
const postURL = await createPostWithTitle( titleText );
@@ -501,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/e2e-tests/specs/plugins/wp-editor-meta-box.test.js b/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js
index c99da39b7cfe0..74dd45374e946 100644
--- a/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js
+++ b/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js
@@ -8,7 +8,9 @@ import {
publishPost,
} from '@wordpress/e2e-test-utils';
-describe( 'WP Editor Meta Boxes', () => {
+// This test isn't reliable on Travis and fails from time to time.
+// See: https://github.com/WordPress/gutenberg/pull/15211.
+describe.skip( 'WP Editor Meta Boxes', () => {
beforeAll( async () => {
await activatePlugin( 'gutenberg-test-plugin-wp-editor-meta-box' );
await createNewPost();
diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/rich-text.test.js
index dcb1434abd89b..1bd6d79c1b10f 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 0000000000000..76e6d15ff9aa3
--- /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 (٠١٢
).
+ // 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/e2e-tests/specs/taxonomies.test.js b/packages/e2e-tests/specs/taxonomies.test.js
index 2cd8e7665eda4..1c88fa690be96 100644
--- a/packages/e2e-tests/specs/taxonomies.test.js
+++ b/packages/e2e-tests/specs/taxonomies.test.js
@@ -110,7 +110,10 @@ describe( 'Taxonomies', () => {
expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' );
} );
- it( 'should be able to open the tags panel and create a new tag if the user has the right capabilities', async () => {
+ // This test isn't reliable locally because repeated execution of the test triggers 400 network
+ // because of the tag's duplication. Also, it randomly doesn't add a new tag after pressing enter.
+ // See: https://github.com/WordPress/gutenberg/pull/15211.
+ it.skip( 'should be able to open the tags panel and create a new tag if the user has the right capabilities', async () => {
await createNewPost();
await openDocumentSettingsSidebar();
diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/writing-flow.test.js
index c9ca7bf2021df..5e870534bf010 100644
--- a/packages/e2e-tests/specs/writing-flow.test.js
+++ b/packages/e2e-tests/specs/writing-flow.test.js
@@ -342,4 +342,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();
+ } );
} );
diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md
index 7831aeb6b4115..0c8eb1326e884 100644
--- a/packages/edit-post/README.md
+++ b/packages/edit-post/README.md
@@ -466,5 +466,4 @@ an initial state from prior to the crash.
-
diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js
index c0c88d1072fe9..fd1458f032043 100644
--- a/packages/editor/src/store/actions.js
+++ b/packages/editor/src/store/actions.js
@@ -732,6 +732,7 @@ export const multiSelect = getBlockEditorAction( 'multiSelect' );
export const clearSelectedBlock = getBlockEditorAction( 'clearSelectedBlock' );
export const toggleSelection = getBlockEditorAction( 'toggleSelection' );
export const replaceBlocks = getBlockEditorAction( 'replaceBlocks' );
+export const replaceBlock = getBlockEditorAction( 'replaceBlock' );
export const moveBlocksDown = getBlockEditorAction( 'moveBlocksDown' );
export const moveBlocksUp = getBlockEditorAction( 'moveBlocksUp' );
export const moveBlockToPosition = getBlockEditorAction( 'moveBlockToPosition' );
diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js
index e94ce68514fb3..c4b64e9e3c45c 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;
}
diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md
index 288b836083ea7..90fe67367ddc8 100644
--- a/packages/rich-text/README.md
+++ b/packages/rich-text/README.md
@@ -388,5 +388,4 @@ Unregisters a format.
-
diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js
index 14402735b1e60..3f9fea6a49990 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
@@ -28,7 +28,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.
@@ -49,13 +49,6 @@ export function applyFormat(
applyFormats( newFormats, endIndex, format );
endIndex++;
}
- // Otherwise, insert a placeholder with the format so new input appears
- // with the format applied.
- } else {
- return {
- ...value,
- activeFormats: [ ...activeFormats, format ],
- };
}
} else {
for ( let index = startIndex; index < endIndex; index++ ) {
@@ -63,7 +56,17 @@ 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,
+ ],
+ } );
}
function applyFormats( formats, index, format ) {
diff --git a/packages/rich-text/src/remove-format.js b/packages/rich-text/src/remove-format.js
index 4a4c9c820f900..5ce2fcd7ce833 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 b75dc7ee5de00..a46a8378eac3c 100644
--- a/packages/rich-text/src/test/apply-format.js
+++ b/packages/rich-text/src/test/apply-format.js
@@ -22,6 +22,7 @@ describe( 'applyFormat', () => {
text: 'one two three',
};
const expected = {
+ activeFormats: [ strong ],
formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ],
text: 'one two three',
};
@@ -40,6 +41,7 @@ describe( 'applyFormat', () => {
end: 6,
};
const expected = {
+ activeFormats: [ strong ],
formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ],
text: 'one two three',
start: 3,
@@ -72,12 +74,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 4d6b10f0d27fb..343eb8c47e011 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 4a890a43bfae5..b86d18a436f3a 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 ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ],
+ activeFormats: [ strong ],
text: 'one two three',
start: 3,
end: 6,
diff --git a/test/integration/blocks-raw-handling.spec.js b/test/integration/blocks-raw-handling.spec.js
index 83315ba0d38a6..fda576a042cbb 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 c667fddceab44..e70f79825bf30 100644
--- a/test/integration/fixtures/google-docs-in.html
+++ b/test/integration/fixtures/google-docs-in.html
@@ -1 +1 @@
-This is a title
This is a heading Formatting test: bold , italic , link , strikethrough , superscript , subscript , nested .
One
Two
Three
An image:
\ No newline at end of file
+This is a title
This is a heading Formatting test: bold , italic , link , strikethrough , superscript , subscript , nested .
One
Two
Three
An image:
\ 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 8a6b117fa6ed5..ad03a2dff0517 100644
--- a/test/integration/fixtures/google-docs-table-in.html
+++ b/test/integration/fixtures/google-docs-table-in.html
@@ -1 +1 @@
-
+
\ 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 0000000000000..a23b54c17a5e3
--- /dev/null
+++ b/test/integration/fixtures/google-docs-table-with-comments-in.html
@@ -0,0 +1,4 @@
+
+
+
+
\ 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 0000000000000..697c2d41ea5cd
--- /dev/null
+++ b/test/integration/fixtures/google-docs-table-with-comments-out.html
@@ -0,0 +1,3 @@
+
+
+
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 0000000000000..e838e0198fe8f
--- /dev/null
+++ b/test/integration/fixtures/google-docs-with-comments-in.html
@@ -0,0 +1,4 @@
+
+This is a title
This is a heading Formatting test: bold , italic , link , strikethrough , superscript , subscript , nested .
One
Two
Three
An image:
+
+
\ 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 0000000000000..7733ca660bdd0
--- /dev/null
+++ b/test/integration/fixtures/google-docs-with-comments-out.html
@@ -0,0 +1,35 @@
+
+This is a title
+
+
+
+This is a heading
+
+
+
+Formatting test: bold , italic , link , strikethrough, superscript , subscript , nested .
+
+
+
+
+
+
+
+One Two Three
+
+
+
+
+
+
+
+
+
+
+
+An image:
+
+
+
+
+