diff --git a/package-lock.json b/package-lock.json
index 4cfbfe2c250978..cc61e74462e934 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6834,6 +6834,14 @@
"warning": "^3.0.0"
}
},
+ "@react-native-community/blur": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-3.6.0.tgz",
+ "integrity": "sha512-GtDBhpX2pQcjl4VopOC8FktrVufrEfYRwVeMQ2WWckqKIv2BdwvlvWvj88L1WmEdBr9UNcm3rtgz+d+YXkmirA==",
+ "requires": {
+ "prop-types": "^15.5.10"
+ }
+ },
"@react-native-community/cli-debugger-ui": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-3.0.0.tgz",
@@ -11956,6 +11964,7 @@
"version": "file:packages/react-native-editor",
"requires": {
"@babel/runtime": "^7.9.2",
+ "@react-native-community/blur": "3.6.0",
"@react-native-community/slider": "git+https://github.com/wordpress-mobile/react-native-slider.git#5ad284d92b8d886e366445bf215be741ed53ddc6",
"@wordpress/api-fetch": "file:packages/api-fetch",
"@wordpress/block-editor": "file:packages/block-editor",
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 7799147217ab97..189b261a67eb98 100644
--- a/packages/block-editor/src/components/block-list/index.native.js
+++ b/packages/block-editor/src/components/block-list/index.native.js
@@ -229,7 +229,6 @@ export class BlockList extends Component {
ListEmptyComponent={ ! isReadOnly && this.renderEmptyList }
ListFooterComponent={ this.renderBlockListFooter }
/>
-
{ this.shouldShowInnerBlockAppender() && (
{
const pickerRef = useRef();
const moversOptions = { keys: [ 'icon', 'actionTitle' ] };
const {
- icon: { backward: backwardButtonIcon, forward: forwardButtonIcon },
actionTitle: {
backward: backwardButtonTitle,
forward: forwardButtonTitle,
@@ -47,7 +61,6 @@ const BlockActionsMenu = ( {
id: 'deleteOption',
label: __( 'Remove block' ),
value: 'deleteOption',
- icon: trash,
separated: true,
disabled: isEmptyDefaultBlock,
};
@@ -56,14 +69,12 @@ const BlockActionsMenu = ( {
id: 'settingsOption',
label: __( 'Block settings' ),
value: 'settingsOption',
- icon: cog,
};
const backwardButtonOption = {
id: 'backwardButtonOption',
label: backwardButtonTitle,
value: 'backwardButtonOption',
- icon: backwardButtonIcon,
disabled: isFirst,
};
@@ -71,14 +82,41 @@ const BlockActionsMenu = ( {
id: 'forwardButtonOption',
label: forwardButtonTitle,
value: 'forwardButtonOption',
- icon: forwardButtonIcon,
disabled: isLast,
};
+ const copyButtonOption = {
+ id: 'copyButtonOption',
+ label: __( 'Copy block' ),
+ value: 'copyButtonOption',
+ };
+
+ const cutButtonOption = {
+ id: 'cutButtonOption',
+ label: __( 'Cut block' ),
+ value: 'cutButtonOption',
+ };
+
+ const pasteButtonOption = {
+ id: 'pasteButtonOption',
+ label: __( 'Paste block' ),
+ value: 'pasteButtonOption',
+ };
+
+ const duplicateButtonOption = {
+ id: 'duplicateButtonOption',
+ label: __( 'Duplicate block' ),
+ value: 'duplicateButtonOption',
+ };
+
const options = compact( [
wrapBlockMover && backwardButtonOption,
wrapBlockMover && forwardButtonOption,
wrapBlockSettings && settingsOption,
+ copyButtonOption,
+ cutButtonOption,
+ isPasteEnabled && pasteButtonOption,
+ duplicateButtonOption,
deleteOption,
] );
@@ -96,6 +134,37 @@ const BlockActionsMenu = ( {
case backwardButtonOption.value:
onMoveUp();
break;
+ case copyButtonOption.value:
+ const copyBlock = getBlocksByClientId( selectedBlockClientId );
+ updateClipboard( serialize( copyBlock ) );
+ createInfoNotice(
+ // translators: displayed right after the block is copied.
+ __( 'Block copied' )
+ );
+ break;
+ case cutButtonOption.value:
+ const cutBlock = getBlocksByClientId( selectedBlockClientId );
+ updateClipboard( serialize( cutBlock ) );
+ removeBlocks( selectedBlockClientId );
+ createInfoNotice(
+ // translators: displayed right after the block is cut.
+ __( 'Block cut' )
+ );
+ break;
+ case pasteButtonOption.value:
+ pasteBlock();
+ createInfoNotice(
+ // translators: displayed right after the block is pasted.
+ __( 'Block pasted' )
+ );
+ break;
+ case duplicateButtonOption.value:
+ duplicateBlock();
+ createInfoNotice(
+ // translators: displayed right after the block is duplicated.
+ __( 'Block duplicated' )
+ );
+ break;
}
}
@@ -131,6 +200,7 @@ const BlockActionsMenu = ( {
destructiveButtonIndex={ options.length }
disabledButtonIndices={ disabledButtonIndices }
hideCancelButton={ Platform.OS !== 'ios' }
+ leftAlign={ true }
anchor={
anchorNodeRef ? findNodeHandle( anchorNodeRef ) : undefined
}
@@ -149,7 +219,11 @@ export default compose(
getBlockOrder,
getBlockName,
getBlock,
+ getBlocksByClientId,
+ getSelectedBlockClientIds,
+ canInsertBlockType,
} = select( 'core/block-editor' );
+ const { getClipboard } = select( 'core/editor' );
const normalizedClientIds = castArray( clientIds );
const block = getBlock( normalizedClientIds );
const blockName = getBlockName( normalizedClientIds );
@@ -171,25 +245,82 @@ export default compose(
const isEmptyDefaultBlock =
isExactlyOneBlock && isDefaultBlock && isEmptyContent;
+ const clipboard = getClipboard();
+ const clipboardBlock =
+ clipboard && rawHandler( { HTML: clipboard } )[ 0 ];
+ const isPasteEnabled =
+ clipboardBlock &&
+ canInsertBlockType( clipboardBlock.name, rootClientId );
+
return {
isFirst: firstIndex === 0,
isLast: lastIndex === blockOrder.length - 1,
rootClientId,
blockTitle,
isEmptyDefaultBlock,
+ getBlocksByClientId,
+ selectedBlockClientId: getSelectedBlockClientIds(),
+ currentIndex: firstIndex,
+ isPasteEnabled,
+ clipboardBlock,
};
} ),
- withDispatch( ( dispatch, { clientIds, rootClientId } ) => {
- const { moveBlocksDown, moveBlocksUp } = dispatch(
- 'core/block-editor'
- );
- const { openGeneralSidebar } = dispatch( 'core/edit-post' );
+ withDispatch(
+ (
+ dispatch,
+ { clientIds, rootClientId, currentIndex, clipboardBlock },
+ { select }
+ ) => {
+ const {
+ moveBlocksDown,
+ moveBlocksUp,
+ duplicateBlocks,
+ removeBlocks,
+ insertBlock,
+ replaceBlocks,
+ } = dispatch( 'core/block-editor' );
+ const { openGeneralSidebar } = dispatch( 'core/edit-post' );
+ const { updateClipboard, createInfoNotice } = dispatch(
+ 'core/editor'
+ );
+ const { getBlockSelectionEnd, getBlock } = select(
+ 'core/block-editor'
+ );
- return {
- onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ),
- onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ),
- openGeneralSidebar: () => openGeneralSidebar( 'edit-post/block' ),
- };
- } ),
+ return {
+ onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ),
+ onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ),
+ openGeneralSidebar: () =>
+ openGeneralSidebar( 'edit-post/block' ),
+ updateClipboard,
+ createInfoNotice,
+ duplicateBlock() {
+ return duplicateBlocks( clientIds );
+ },
+ removeBlocks,
+ pasteBlock: () => {
+ const canReplaceBlock = isUnmodifiedDefaultBlock(
+ getBlock( getBlockSelectionEnd() )
+ );
+
+ if ( ! canReplaceBlock ) {
+ const insertedBlock = createBlock(
+ clipboardBlock.name,
+ clipboardBlock.attributes,
+ clipboardBlock.innerBlocks
+ );
+
+ insertBlock(
+ insertedBlock,
+ currentIndex + 1,
+ rootClientId
+ );
+ } else {
+ replaceBlocks( clientIds, clipboardBlock );
+ }
+ },
+ };
+ }
+ ),
withInstanceId
)( BlockActionsMenu );
diff --git a/packages/block-editor/src/components/inserter/menu-item.native.js b/packages/block-editor/src/components/inserter/menu-item.native.js
new file mode 100644
index 00000000000000..97c3de091b03b5
--- /dev/null
+++ b/packages/block-editor/src/components/inserter/menu-item.native.js
@@ -0,0 +1,94 @@
+/**
+ * External dependencies
+ */
+import { View, TouchableHighlight, Text } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { Icon } from '@wordpress/components';
+import { withPreferredColorScheme } from '@wordpress/compose';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import styles from './style.scss';
+
+class MenuItem extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.onPress = this.onPress.bind( this );
+ }
+
+ onPress() {
+ const { onSelect, item } = this.props;
+ onSelect( item );
+ }
+
+ render() {
+ const {
+ getStylesFromColorScheme,
+ item,
+ itemWidth,
+ maxWidth,
+ } = this.props;
+
+ const modalIconWrapperStyle = getStylesFromColorScheme(
+ styles.modalIconWrapper,
+ styles.modalIconWrapperDark
+ );
+ const modalIconStyle = getStylesFromColorScheme(
+ styles.modalIcon,
+ styles.modalIconDark
+ );
+ const modalItemLabelStyle = getStylesFromColorScheme(
+ styles.modalItemLabel,
+ styles.modalItemLabelDark
+ );
+
+ const clipboardBlockStyles = getStylesFromColorScheme(
+ styles.clipboardBlock,
+ styles.clipboardBlockDark
+ );
+
+ const isClipboardBlock = item.id === 'clipboard';
+
+ return (
+
+
+
+
+
+
+
+
+ { isClipboardBlock ? __( 'Copied block' ) : item.title }
+
+
+
+ );
+ }
+}
+
+export default withPreferredColorScheme( MenuItem );
diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js
index 0ecfba8e84e709..43d96a8dafd04a 100644
--- a/packages/block-editor/src/components/inserter/menu.native.js
+++ b/packages/block-editor/src/components/inserter/menu.native.js
@@ -1,31 +1,23 @@
/**
* External dependencies
*/
-import {
- FlatList,
- View,
- Text,
- TouchableHighlight,
- Dimensions,
-} from 'react-native';
+import { FlatList, View, TouchableHighlight, Dimensions } from 'react-native';
+import { pick } from 'lodash';
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
-import { createBlock } from '@wordpress/blocks';
+import { createBlock, rawHandler } from '@wordpress/blocks';
import { withDispatch, withSelect } from '@wordpress/data';
-import {
- withInstanceId,
- compose,
- withPreferredColorScheme,
-} from '@wordpress/compose';
-import { BottomSheet, Icon } from '@wordpress/components';
+import { withInstanceId, compose } from '@wordpress/compose';
+import { BottomSheet } from '@wordpress/components';
/**
* Internal dependencies
*/
import styles from './style.scss';
+import MenuItem from './menu-item.native';
const MIN_COL_NUM = 3;
@@ -35,6 +27,7 @@ export class InserterMenu extends Component {
this.onClose = this.onClose.bind( this );
this.onLayout = this.onLayout.bind( this );
+ this.renderItem = this.renderItem.bind( this );
this.state = {
numberOfColumns: MIN_COL_NUM,
};
@@ -99,31 +92,34 @@ export class InserterMenu extends Component {
}
onLayout() {
- const columnProperties = this.calculateColumnsProperties();
- const numberOfColumns = columnProperties.numOfColumns;
+ const {
+ numOfColumns,
+ itemWidth,
+ maxWidth,
+ } = this.calculateColumnsProperties();
+ const numberOfColumns = numOfColumns;
+
+ this.setState( { numberOfColumns, itemWidth, maxWidth } );
+ }
- this.setState( { numberOfColumns } );
+ renderItem( { item } ) {
+ const { itemWidth, maxWidth } = this.state;
+ const { onSelect } = this.props;
+ return (
+
+ );
}
render() {
- const { getStylesFromColorScheme, items, onSelect } = this.props;
+ const { items } = this.props;
const { numberOfColumns } = this.state;
const bottomPadding = styles.contentBottomPadding;
- const modalIconWrapperStyle = getStylesFromColorScheme(
- styles.modalIconWrapper,
- styles.modalIconWrapperDark
- );
- const modalIconStyle = getStylesFromColorScheme(
- styles.modalIcon,
- styles.modalIconDark
- );
- const modalItemLabelStyle = getStylesFromColorScheme(
- styles.modalItemLabel,
- styles.modalItemLabelDark
- );
-
- const columnProperties = this.calculateColumnsProperties();
return (
) }
keyExtractor={ ( item ) => item.name }
- renderItem={ ( { item } ) => (
- onSelect( item ) }
- >
-
-
-
-
-
-
-
- { item.title }
-
-
-
- ) }
+ renderItem={ this.renderItem }
/>
@@ -196,8 +156,10 @@ export default compose(
getBlockRootClientId,
getBlockSelectionEnd,
getSettings,
+ canInsertBlockType,
} = select( 'core/block-editor' );
- const { getChildBlockNames } = select( 'core/blocks' );
+ const { getChildBlockNames, getBlockType } = select( 'core/blocks' );
+ const { getClipboard } = select( 'core/editor' );
let destinationRootClientId = rootClientId;
if ( ! destinationRootClientId && ! clientId && ! isAppender ) {
@@ -214,10 +176,29 @@ export default compose(
const {
__experimentalShouldInsertAtTheTop: shouldInsertAtTheTop,
} = getSettings();
+ const clipboard = getClipboard();
+ const clipboardBlock =
+ clipboard && rawHandler( { HTML: clipboard } )[ 0 ];
+ const shouldAddClipboardBlock =
+ clipboardBlock &&
+ canInsertBlockType( clipboardBlock.name, destinationRootClientId );
return {
rootChildBlocks: getChildBlockNames( destinationRootBlockName ),
- items: getInserterItems( destinationRootClientId ),
+ items: shouldAddClipboardBlock
+ ? [
+ {
+ ...pick( getBlockType( clipboardBlock.name ), [
+ 'name',
+ 'icon',
+ ] ),
+ id: 'clipboard',
+ initialAttributes: clipboardBlock.attributes,
+ innerBlocks: clipboardBlock.innerBlocks,
+ },
+ ...getInserterItems( destinationRootClientId ),
+ ]
+ : getInserterItems( destinationRootClientId ),
destinationRootClientId,
shouldInsertAtTheTop,
};
@@ -262,9 +243,13 @@ export default compose(
},
hideInsertionPoint,
onSelect( item ) {
- const { name, initialAttributes } = item;
+ const { name, initialAttributes, innerBlocks } = item;
- const insertedBlock = createBlock( name, initialAttributes );
+ const insertedBlock = createBlock(
+ name,
+ initialAttributes,
+ innerBlocks
+ );
insertBlock(
insertedBlock,
@@ -283,6 +268,5 @@ export default compose(
},
};
} ),
- withInstanceId,
- withPreferredColorScheme
+ 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 bfb45082c8f8f6..4df8eafcc37a44 100644
--- a/packages/block-editor/src/components/inserter/style.native.scss
+++ b/packages/block-editor/src/components/inserter/style.native.scss
@@ -78,3 +78,13 @@
.columnPadding {
padding: $grid-unit-20;
}
+
+.clipboardBlock {
+ background-color: transparent;
+ border-width: 1px;
+ border-color: $light-gray-400;
+}
+
+.clipboardBlockDark {
+ border-color: $gray-70;
+}
diff --git a/packages/blocks/src/api/raw-handling/index.native.js b/packages/blocks/src/api/raw-handling/index.native.js
index f7e468e3fe3639..b5252e9046c636 100644
--- a/packages/blocks/src/api/raw-handling/index.native.js
+++ b/packages/blocks/src/api/raw-handling/index.native.js
@@ -1,2 +1,3 @@
export { getPhrasingContentSchema } from './phrasing-content';
export { pasteHandler } from './paste-handler';
+export { rawHandler } from './index.js';
diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js
index 2938a072788ba5..02913c7aa8541d 100644
--- a/packages/components/src/index.native.js
+++ b/packages/components/src/index.native.js
@@ -42,6 +42,8 @@ export { default as ResizableBox } from './resizable-box';
export { default as UnsupportedFooterControl } from './unsupported-footer-control';
export { default as ColorControl } from './color-control';
export { default as QueryControls } from './query-controls';
+export { default as Notice } from './notice';
+export { default as NoticeList } from './notice/list';
export { default as RadioControl } from './radio-control';
// Higher-Order Components
diff --git a/packages/components/src/notice/index.native.js b/packages/components/src/notice/index.native.js
new file mode 100644
index 00000000000000..6224d9cfd6928d
--- /dev/null
+++ b/packages/components/src/notice/index.native.js
@@ -0,0 +1,121 @@
+/**
+ * External dependencies
+ */
+import {
+ Animated,
+ Easing,
+ Text,
+ TouchableWithoutFeedback,
+ View,
+ Dimensions,
+ Platform,
+} from 'react-native';
+import { BlurView } from '@react-native-community/blur';
+
+/**
+ * WordPress dependencies
+ */
+import { useEffect, useRef, useState } from '@wordpress/element';
+import { usePreferredColorSchemeStyle } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import styles from './style.scss';
+
+const Notice = ( { onNoticeHidden, content, id } ) => {
+ const [ width, setWidth ] = useState( Dimensions.get( 'window' ).width );
+ const [ visible, setVisible ] = useState( true );
+
+ const animationValue = useRef( new Animated.Value( 0 ) ).current;
+ const timer = useRef( null );
+ const isIOS = Platform.OS === 'ios';
+
+ const onDimensionsChange = () => {
+ setWidth( Dimensions.get( 'window' ).width );
+ };
+
+ useEffect( () => {
+ Dimensions.addEventListener( 'change', onDimensionsChange );
+ return () => {
+ Dimensions.removeEventListener( 'change', onDimensionsChange );
+ };
+ }, [] );
+
+ useEffect( () => {
+ startAnimation();
+ return () => {
+ clearTimeout( timer?.current );
+ };
+ }, [ visible, id ] );
+
+ const onHide = () => {
+ setVisible( false );
+ };
+
+ const startAnimation = () => {
+ Animated.timing( animationValue, {
+ toValue: visible ? 1 : 0,
+ duration: visible ? 300 : 150,
+ useNativeDriver: true,
+ easing: Easing.out( Easing.quad ),
+ } ).start( () => {
+ if ( visible && onNoticeHidden ) {
+ timer.current = setTimeout( () => {
+ onHide();
+ }, 3000 );
+ }
+
+ if ( ! visible && onNoticeHidden ) {
+ onNoticeHidden( id );
+ }
+ } );
+ };
+
+ const noticeSolidStyles = usePreferredColorSchemeStyle(
+ styles.noticeSolid,
+ styles.noticeSolidDark
+ );
+
+ const textStyles = usePreferredColorSchemeStyle(
+ styles.text,
+ styles.textDark
+ );
+
+ return (
+ <>
+
+
+
+ { content }
+
+
+ { isIOS && (
+
+ ) }
+
+ >
+ );
+};
+
+export default Notice;
diff --git a/packages/components/src/notice/list.native.js b/packages/components/src/notice/list.native.js
new file mode 100644
index 00000000000000..b91a21407c6c3f
--- /dev/null
+++ b/packages/components/src/notice/list.native.js
@@ -0,0 +1,76 @@
+/**
+ * External dependencies
+ */
+import { View } from 'react-native';
+
+/**
+ * WordPress dependencies
+ */
+import { Component } from '@wordpress/element';
+import { withDispatch, withSelect } from '@wordpress/data';
+import { compose } from '@wordpress/compose';
+
+/**
+ * Internal dependencies
+ */
+import Notice from './';
+import styles from './style.scss';
+
+class NoticeList extends Component {
+ constructor() {
+ super( ...arguments );
+ this.removeNotice = this.removeNotice.bind( this );
+ }
+
+ removeNotice( id ) {
+ const { removeNotice } = this.props;
+ removeNotice( id );
+ }
+
+ render() {
+ const { notices, shouldStack } = this.props;
+
+ if ( ! notices.length ) {
+ return null;
+ }
+
+ return (
+
+ { shouldStack ? (
+ notices
+ .reverse()
+ .map( ( notice ) => (
+
+ ) )
+ ) : (
+
+ ) }
+
+ );
+ }
+}
+
+export default compose( [
+ withSelect( ( select ) => {
+ const { getNotices } = select( 'core/editor' );
+
+ return {
+ notices: getNotices(),
+ };
+ } ),
+ withDispatch( ( dispatch ) => {
+ const { removeNotice, removeAllNotices } = dispatch( 'core/editor' );
+
+ return {
+ removeNotice,
+ removeAllNotices,
+ };
+ } ),
+] )( NoticeList );
diff --git a/packages/components/src/notice/style.native.scss b/packages/components/src/notice/style.native.scss
new file mode 100644
index 00000000000000..88a9b60b396dbf
--- /dev/null
+++ b/packages/components/src/notice/style.native.scss
@@ -0,0 +1,46 @@
+.text {
+ color: #212121;
+ align-self: center;
+ background-color: transparent;
+ font-size: 12;
+}
+
+.textDark {
+ color: $white;
+}
+
+.list {
+ position: absolute;
+ margin-left: auto;
+ margin-right: auto;
+ top: 0;
+ flex-direction: column;
+ z-index: 102;
+ align-items: center;
+ align-self: center;
+}
+
+.notice {
+ height: 24;
+ justify-content: center;
+}
+
+.noticeContent {
+ position: absolute;
+ z-index: 1;
+ width: 100%;
+}
+
+.noticeSolid {
+ background-color: #f4f4f4;
+}
+
+.noticeSolidDark {
+ background-color: #2a2a2a;
+}
+
+.blurBackground {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+}
diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js
index eb989d7d4f32bd..d0548d9696ecee 100644
--- a/packages/edit-post/src/components/layout/index.native.js
+++ b/packages/edit-post/src/components/layout/index.native.js
@@ -16,7 +16,11 @@ import {
FloatingToolbar,
} from '@wordpress/block-editor';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
-import { HTMLTextInput, KeyboardAvoidingView } from '@wordpress/components';
+import {
+ HTMLTextInput,
+ KeyboardAvoidingView,
+ NoticeList,
+} from '@wordpress/components';
import { AutosaveMonitor } from '@wordpress/editor';
import { sendNativeEditorDidLayout } from '@wordpress/react-native-bridge';
@@ -135,6 +139,7 @@ class Layout extends Component {
{ ! isHtmlView && Platform.OS === 'android' && (
) }
+
notice.id !== action.id );
+ }
+ return state;
+}
+
export default optimist(
combineReducers( {
postId,
@@ -62,5 +99,7 @@ export default optimist(
template,
isReady,
editorSettings,
+ clipboard,
+ notices,
} )
);
diff --git a/packages/editor/src/store/selectors.native.js b/packages/editor/src/store/selectors.native.js
index 2edfc373213fa2..474686799bc73d 100644
--- a/packages/editor/src/store/selectors.native.js
+++ b/packages/editor/src/store/selectors.native.js
@@ -55,3 +55,25 @@ export const isEditedPostAutosaveable = createRegistrySelector(
return false;
}
);
+
+/**
+ * Returns the current clipboard data.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {Object} Current clipboard data.
+ */
+export function getClipboard( state ) {
+ return state.clipboard;
+}
+
+/**
+ * Returns the current notice data.
+ *
+ * @param {Object} state Global application state.
+ *
+ * @return {Object} Current notice data.
+ */
+export function getNotices( state ) {
+ return state.notices;
+}
diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock
index 19cb2c000b158d..f097969c346f49 100644
--- a/packages/react-native-editor/ios/Podfile.lock
+++ b/packages/react-native-editor/ios/Podfile.lock
@@ -189,6 +189,8 @@ PODS:
- React-cxxreact (= 0.61.5)
- React-jsi (= 0.61.5)
- React-jsinspector (0.61.5)
+ - react-native-blur (0.8.0):
+ - React
- react-native-get-random-values (1.4.0):
- React
- react-native-keyboard-aware-scroll-view (0.8.8):
@@ -266,6 +268,7 @@ DEPENDENCIES:
- React-jsi (from `../../../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../../../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../../../node_modules/react-native/ReactCommon/jsinspector`)
+ - "react-native-blur (from `../../../node_modules/@react-native-community/blur`)"
- react-native-get-random-values (from `../../../node_modules/react-native-get-random-values`)
- react-native-keyboard-aware-scroll-view (from `../../../node_modules/react-native-keyboard-aware-scroll-view`)
- react-native-safe-area (from `../../../node_modules/react-native-safe-area`)
@@ -325,6 +328,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../../../node_modules/react-native/ReactCommon/jsinspector"
+ react-native-blur:
+ :path: "../../../node_modules/@react-native-community/blur"
react-native-get-random-values:
:path: "../../../node_modules/react-native-get-random-values"
react-native-keyboard-aware-scroll-view:
@@ -382,6 +387,7 @@ SPEC CHECKSUMS:
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
+ react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c
react-native-get-random-values: 8940331a943a46c165d3ed05802c09c392f8dd46
react-native-keyboard-aware-scroll-view: ffa9152671fec9a571197ed2d02e0fcb90206e60
react-native-safe-area: e8230b0017d76c00de6b01e2412dcf86b127c6a3
diff --git a/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj b/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj
index 01bb92522a2d0c..81a72bc9af7c83 100644
--- a/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj
+++ b/packages/react-native-editor/ios/gutenberg.xcodeproj/project.pbxproj
@@ -492,6 +492,7 @@
"${BUILT_PRODUCTS_DIR}/WordPress-Aztec-iOS/Aztec.framework",
"${BUILT_PRODUCTS_DIR}/Yoga/yoga.framework",
"${BUILT_PRODUCTS_DIR}/glog/glog.framework",
+ "${BUILT_PRODUCTS_DIR}/react-native-blur/react_native_blur.framework",
"${BUILT_PRODUCTS_DIR}/react-native-get-random-values/react_native_get_random_values.framework",
"${BUILT_PRODUCTS_DIR}/react-native-keyboard-aware-scroll-view/react_native_keyboard_aware_scroll_view.framework",
"${BUILT_PRODUCTS_DIR}/react-native-safe-area/react_native_safe_area.framework",
@@ -527,6 +528,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Aztec.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/yoga.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/react_native_blur.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/react_native_get_random_values.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/react_native_keyboard_aware_scroll_view.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/react_native_safe_area.framework",
diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json
index 6900187f18132e..342d8352b505df 100644
--- a/packages/react-native-editor/package.json
+++ b/packages/react-native-editor/package.json
@@ -30,6 +30,7 @@
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.9.2",
+ "@react-native-community/blur": "3.6.0",
"@react-native-community/slider": "git+https://github.com/wordpress-mobile/react-native-slider.git#5ad284d92b8d886e366445bf215be741ed53ddc6",
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/block-editor": "file:../block-editor",
diff --git a/test/native/setup.js b/test/native/setup.js
index fe5bfe7a05f33c..3f90fe422455c2 100644
--- a/test/native/setup.js
+++ b/test/native/setup.js
@@ -98,6 +98,10 @@ jest.mock( 'react-native-hsv-color-picker', () => () => 'HsvColorPicker', {
virtual: true,
} );
+jest.mock( '@react-native-community/blur', () => () => 'BlurView', {
+ virtual: true,
+} );
+
// 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"