diff --git a/README.md b/README.md
index 2b87d84..3247950 100644
--- a/README.md
+++ b/README.md
@@ -190,9 +190,46 @@ will automatically handle click events and show a quanta toolbar when selecting
It will allow you to naviate to the parent block (TODO)
+Add the `data-block-uid={<>}` attribute to your outer most container of the rendered block html.
+The `data-block-uid` requires the block's UID, which you need to provide in the outermost container of the block.
+
+For example, if you are using ploneClient to fetch `data`, it will be `data.blocks_layout.items[x]`.
+Now, Click on your blocks in iframe and the sidebar will show its settings.
+
+Usage:
+```js
+// Vanilla JS example to render Blocks
+
+// Function to create the block list
+function createBlockList(data) {
+ const blockList = document.createElement('ul');
+
+ data.blocks_layout.items.forEach(id => {
+ if (data.blocks[id]["@type"] === "slate") {
+ const slateValue = data.blocks[id].value;
+ const listItem = document.createElement('li');
+ listItem.className = 'blog-list-item';
+ listItem.setAttribute('data-block-uid', id); // Set Attribute to enable Clicking on Blocks
+
+ const pre = document.createElement('pre');
+ pre.className = 'pre-block';
+ pre.textContent = JSON.stringify(slateValue, null, 2);
+
+ listItem.appendChild(pre);
+ blockList.appendChild(listItem);
+ }
+ });
+
+ document.body.appendChild(blockList);
+}
+
+// Call the function to render the blocks
+createBlockList(data);
+```
+
### Level 3: Enable Realtime changes while editing
-You will need to subscribe to an ```onEditChange``` event that will call the callback with the updated data.
+You will need to subscribe to an ```onEditChange``` event that will call the callback with the updated data.
The `onEditChange` method listens for changes in the Hydra and triggers a callback with updated data.
The 'data' object follows the same format as you get from the [ploneClient](https://6.docs.plone.org/volto/client/quick-start.html?highlight=data#query-or-mutation-options-factories).
diff --git a/packages/hydra-js/hydra.js b/packages/hydra-js/hydra.js
index 171f0b6..0dc43bb 100644
--- a/packages/hydra-js/hydra.js
+++ b/packages/hydra-js/hydra.js
@@ -3,9 +3,10 @@ class Bridge {
constructor(adminOrigin) {
this.adminOrigin = adminOrigin;
this.token = null;
- this.navigationHandler = null; // Handler for navigation events
- this.realTimeDataHandler = null; // Handler for message events
- this.blockClickHandler = null; // Handler for block click events
+ this.navigationHandler = null;
+ this.realTimeDataHandler = null;
+ this.blockClickHandler = null;
+ this.currentlySelectedBlock = null;
this.init();
}
@@ -29,8 +30,16 @@ class Bridge {
// Get the access token from the URL
const url = new URL(window.location.href);
const access_token = url.searchParams.get('access_token');
- this.token = access_token;
- this._setTokenCookie(access_token);
+ const isEditMode = url.searchParams.get('_edit') === 'true'; // Only when in edit mode
+
+ if (access_token) {
+ this.token = access_token;
+ this._setTokenCookie(access_token);
+ }
+
+ if (isEditMode) {
+ this.enableBlockClickListener();
+ }
}
}
@@ -61,11 +70,24 @@ class Bridge {
document.cookie = `auth_token=${token}; expires=${expiryDate.toUTCString()}; path=/; domain=${domain};`;
}
+ /**
+ * Enable the frontend to listen for clicks on blocks to open the settings
+ */
enableBlockClickListener() {
this.blockClickHandler = (event) => {
const blockElement = event.target.closest('[data-block-uid]');
if (blockElement) {
+ // Remove border and button from the previously selected block
+ if (this.currentlySelectedBlock) {
+ this.currentlySelectedBlock.classList.remove('volto-hydra--outline');
+ }
+
+ // Set the currently selected block
+ this.currentlySelectedBlock = blockElement;
+ // Add border to the currently selected block
+ this.currentlySelectedBlock.classList.add('volto-hydra--outline');
const blockUid = blockElement.getAttribute('data-block-uid');
+
window.parent.postMessage(
{ type: 'OPEN_SETTINGS', uid: blockUid },
this.adminOrigin,
@@ -138,15 +160,6 @@ export function onEditChange(callback) {
}
}
-/**
- * Enable the frontend to listen for clicks on blocks to open the settings
- */
-export function enableBlockClickListener() {
- if (bridgeInstance) {
- bridgeInstance.enableBlockClickListener();
- }
-}
-
// Make initBridge available globally
if (typeof window !== 'undefined') {
window.initBridge = initBridge;
diff --git a/packages/volto-hydra/src/actions.js b/packages/volto-hydra/src/actions.js
new file mode 100644
index 0000000..4360841
--- /dev/null
+++ b/packages/volto-hydra/src/actions.js
@@ -0,0 +1,8 @@
+import { SET_SELECTED_BLOCK } from './constants';
+
+export function setSelectedBlock(uid) {
+ return {
+ type: SET_SELECTED_BLOCK,
+ uid: uid,
+ };
+}
diff --git a/packages/volto-hydra/src/components/Iframe/View.jsx b/packages/volto-hydra/src/components/Iframe/View.jsx
index 585b562..cc4b19f 100644
--- a/packages/volto-hydra/src/components/Iframe/View.jsx
+++ b/packages/volto-hydra/src/components/Iframe/View.jsx
@@ -1,8 +1,9 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
-import { useSelector } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import Cookies from 'js-cookie';
import './styles.css';
+import { setSelectedBlock } from '../../actions';
/**
* Get the default URL from the environment
@@ -20,9 +21,14 @@ const getDefualtUrl = () =>
* @returns {string} URL with the admin params
*/
const getUrlWithAdminParams = (url, token) => {
- return typeof window !== 'undefined'
- ? `${url}${window.location.pathname.replace('/edit', '')}?access_token=${token}&_edit=true`
- : null;
+ if (typeof window !== 'undefined') {
+ if (window.location.pathname.endsWith('/edit')) {
+ return `${url}${window.location.pathname.replace('/edit', '')}?access_token=${token}&_edit=true`;
+ } else {
+ return `${url}${window.location.pathname}?access_token=${token}&_edit=false`;
+ }
+ }
+ return null;
};
function isValidUrl(string) {
@@ -36,6 +42,7 @@ function isValidUrl(string) {
}
const Iframe = () => {
+ const dispatch = useDispatch();
const [url, setUrl] = useState('');
const [src, setSrc] = useState('');
@@ -93,6 +100,12 @@ const Iframe = () => {
handleNavigateToUrl(event.data.url);
break;
+ case 'OPEN_SETTINGS':
+ if (history.location.pathname.endsWith('/edit')) {
+ dispatch(setSelectedBlock(event.data.uid));
+ }
+ break;
+
default:
break;
}
@@ -105,7 +118,13 @@ const Iframe = () => {
return () => {
window.removeEventListener('message', messageHandler);
};
- }, [handleNavigateToUrl, initialUrl, token]);
+ }, [
+ dispatch,
+ handleNavigateToUrl,
+ history.location.pathname,
+ initialUrl,
+ token,
+ ]);
useEffect(() => {
if (Object.keys(form).length > 0 && isValidUrl(initialUrl)) {
diff --git a/packages/volto-hydra/src/constants.js b/packages/volto-hydra/src/constants.js
new file mode 100644
index 0000000..71dfa9e
--- /dev/null
+++ b/packages/volto-hydra/src/constants.js
@@ -0,0 +1 @@
+export const SET_SELECTED_BLOCK = 'SET_SELECTED_BLOCK';
diff --git a/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx b/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
new file mode 100644
index 0000000..f799517
--- /dev/null
+++ b/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
@@ -0,0 +1,226 @@
+/**
+ * Edit block.
+ * @module components/manage/Blocks/Block/Edit
+ */
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { compose } from 'redux';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
+import cx from 'classnames';
+import { setSidebarTab } from '@plone/volto/actions';
+import config from '@plone/volto/registry';
+import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
+import { applyBlockDefaults } from '@plone/volto/helpers';
+import { ViewDefaultBlock, EditDefaultBlock } from '@plone/volto/components';
+
+import {
+ SidebarPortal,
+ BlockSettingsSidebar,
+ BlockSettingsSchema,
+} from '@plone/volto/components';
+
+const messages = defineMessages({
+ unknownBlock: {
+ id: 'Unknown Block',
+ defaultMessage: 'Unknown Block {block}',
+ },
+});
+
+/**
+ * Edit block class.
+ * @class Edit
+ * @extends Component
+ */
+export class Edit extends Component {
+ /**
+ * Property types.
+ * @property {Object} propTypes Property types.
+ * @static
+ */
+ static propTypes = {
+ type: PropTypes.string.isRequired,
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
+ // properties is mapped to formData, so it's not connected to changes of the object
+ properties: PropTypes.objectOf(PropTypes.any).isRequired,
+ selected: PropTypes.bool.isRequired,
+ multiSelected: PropTypes.bool,
+ index: PropTypes.number.isRequired,
+ id: PropTypes.string.isRequired,
+ manage: PropTypes.bool,
+ onMoveBlock: PropTypes.func.isRequired,
+ onDeleteBlock: PropTypes.func.isRequired,
+ editable: PropTypes.bool,
+ pathname: PropTypes.string.isRequired,
+ };
+
+ /**
+ * Default properties.
+ * @property {Object} defaultProps Default properties.
+ * @static
+ */
+ static defaultProps = {
+ manage: false,
+ editable: true,
+ };
+ componentDidMount() {
+ const { type } = this.props;
+ const { blocksConfig = config.blocks.blocksConfig } = this.props;
+
+ const blockHasOwnFocusManagement =
+ blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null;
+ if (
+ !blockHasOwnFocusManagement &&
+ this.props.selected &&
+ this.blockNode.current
+ ) {
+ this.blockNode.current.focus();
+ }
+ const tab = this.props.manage ? 1 : blocksConfig?.[type]?.sidebarTab || 0;
+ if (this.props.selected && this.props.editable) {
+ this.props.setSidebarTab(tab);
+ }
+ }
+
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ const { blocksConfig = config.blocks.blocksConfig } = this.props;
+ const { selected, type } = this.props;
+ const blockHasOwnFocusManagement =
+ blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null;
+ if (
+ !blockHasOwnFocusManagement &&
+ nextProps.selected &&
+ selected !== nextProps.selected &&
+ this.blockNode.current
+ ) {
+ this.blockNode.current.focus();
+ }
+ if (
+ ((!this.props.selected && nextProps.selected) ||
+ type !== nextProps.type) &&
+ this.props.editable
+ ) {
+ const tab = this.props.manage
+ ? 1
+ : blocksConfig?.[nextProps.type]?.sidebarTab || 0;
+ this.props.setSidebarTab(tab);
+ }
+ }
+
+ blockNode = React.createRef();
+
+ /**
+ * Render method.
+ * @method render
+ * @returns {string} Markup for the component.
+ */
+ render() {
+ const { blocksConfig = config.blocks.blocksConfig } = this.props;
+ const { editable, type } = this.props;
+
+ const disableNewBlocks = this.props.data?.disableNewBlocks;
+
+ let Block = blocksConfig?.[type]?.['edit'] || EditDefaultBlock;
+ if (
+ this.props.data?.readOnly ||
+ (!editable && !config.blocks.showEditBlocksInBabelView)
+ ) {
+ Block = blocksConfig?.[type]?.['view'] || ViewDefaultBlock;
+ }
+ const schema = blocksConfig?.[type]?.['schema'] || BlockSettingsSchema;
+ const blockHasOwnFocusManagement =
+ blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null;
+
+ return (
+ <>
+ {Block !== null ? (
+ {
+ const isMultipleSelection = e.shiftKey || e.ctrlKey || e.metaKey;
+ !this.props.selected &&
+ this.props.onSelectBlock(
+ this.props.id,
+ this.props.selected ? false : isMultipleSelection,
+ e,
+ );
+ }}
+ onKeyDown={
+ !(blockHasOwnFocusManagement || disableNewBlocks)
+ ? (e) =>
+ this.props.handleKeyDown(
+ e,
+ this.props.index,
+ this.props.id,
+ this.blockNode.current,
+ )
+ : null
+ }
+ className={cx('block', type, this.props.data.variation, {
+ selected: this.props.selected || this.props.multiSelected,
+ multiSelected: this.props.multiSelected,
+ })}
+ style={{ outline: 'none' }}
+ ref={this.blockNode}
+ // The tabIndex is required for the keyboard navigation
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+ tabIndex={!blockHasOwnFocusManagement ? -1 : null}
+ >
+
+ {this.props.manage && (
+
+
+
+ )}
+
+ ) : (
+
+ !this.props.selected && this.props.onSelectBlock(this.props.id)
+ }
+ onKeyDown={
+ !(blockHasOwnFocusManagement || disableNewBlocks)
+ ? (e) =>
+ this.props.handleKeyDown(
+ e,
+ this.props.index,
+ this.props.id,
+ this.blockNode.current,
+ )
+ : null
+ }
+ className={cx(`block ${type}`, { selected: this.props.selected })}
+ style={{ outline: 'none' }}
+ ref={this.blockNode}
+ // The tabIndex is required for the keyboard navigation
+ tabIndex={-1}
+ >
+ {this.props.intl.formatMessage(messages.unknownBlock, {
+ block: type,
+ })}
+
+ )}
+ >
+ );
+ }
+}
+
+export default compose(
+ injectIntl,
+ withObjectBrowser,
+ connect(
+ (state, props) => ({
+ selected: state.selectedBlock.uid === props.id,
+ }),
+ { setSidebarTab },
+ ),
+)(Edit);
diff --git a/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.original.jsx b/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.original.jsx
new file mode 100644
index 0000000..cbcfdf5
--- /dev/null
+++ b/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.original.jsx
@@ -0,0 +1,222 @@
+/**
+ * Edit block.
+ * @module components/manage/Blocks/Block/Edit
+ */
+
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { compose } from 'redux';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
+import cx from 'classnames';
+import { setSidebarTab } from '@plone/volto/actions';
+import config from '@plone/volto/registry';
+import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
+import { applyBlockDefaults } from '@plone/volto/helpers';
+import { ViewDefaultBlock, EditDefaultBlock } from '@plone/volto/components';
+
+import {
+ SidebarPortal,
+ BlockSettingsSidebar,
+ BlockSettingsSchema,
+} from '@plone/volto/components';
+
+const messages = defineMessages({
+ unknownBlock: {
+ id: 'Unknown Block',
+ defaultMessage: 'Unknown Block {block}',
+ },
+});
+
+/**
+ * Edit block class.
+ * @class Edit
+ * @extends Component
+ */
+export class Edit extends Component {
+ /**
+ * Property types.
+ * @property {Object} propTypes Property types.
+ * @static
+ */
+ static propTypes = {
+ type: PropTypes.string.isRequired,
+ data: PropTypes.objectOf(PropTypes.any).isRequired,
+ // properties is mapped to formData, so it's not connected to changes of the object
+ properties: PropTypes.objectOf(PropTypes.any).isRequired,
+ selected: PropTypes.bool.isRequired,
+ multiSelected: PropTypes.bool,
+ index: PropTypes.number.isRequired,
+ id: PropTypes.string.isRequired,
+ manage: PropTypes.bool,
+ onMoveBlock: PropTypes.func.isRequired,
+ onDeleteBlock: PropTypes.func.isRequired,
+ editable: PropTypes.bool,
+ pathname: PropTypes.string.isRequired,
+ };
+
+ /**
+ * Default properties.
+ * @property {Object} defaultProps Default properties.
+ * @static
+ */
+ static defaultProps = {
+ manage: false,
+ editable: true,
+ };
+
+ componentDidMount() {
+ const { type } = this.props;
+ const { blocksConfig = config.blocks.blocksConfig } = this.props;
+
+ const blockHasOwnFocusManagement =
+ blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null;
+ if (
+ !blockHasOwnFocusManagement &&
+ this.props.selected &&
+ this.blockNode.current
+ ) {
+ this.blockNode.current.focus();
+ }
+ const tab = this.props.manage ? 1 : blocksConfig?.[type]?.sidebarTab || 0;
+ if (this.props.selected && this.props.editable) {
+ this.props.setSidebarTab(tab);
+ }
+ }
+
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ const { blocksConfig = config.blocks.blocksConfig } = this.props;
+ const { selected, type } = this.props;
+ const blockHasOwnFocusManagement =
+ blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null;
+ if (
+ !blockHasOwnFocusManagement &&
+ nextProps.selected &&
+ selected !== nextProps.selected &&
+ this.blockNode.current
+ ) {
+ this.blockNode.current.focus();
+ }
+ if (
+ ((!this.props.selected && nextProps.selected) ||
+ type !== nextProps.type) &&
+ this.props.editable
+ ) {
+ const tab = this.props.manage
+ ? 1
+ : blocksConfig?.[nextProps.type]?.sidebarTab || 0;
+ this.props.setSidebarTab(tab);
+ }
+ }
+
+ blockNode = React.createRef();
+
+ /**
+ * Render method.
+ * @method render
+ * @returns {string} Markup for the component.
+ */
+ render() {
+ const { blocksConfig = config.blocks.blocksConfig } = this.props;
+ const { editable, type } = this.props;
+
+ const disableNewBlocks = this.props.data?.disableNewBlocks;
+
+ let Block = blocksConfig?.[type]?.['edit'] || EditDefaultBlock;
+ if (
+ this.props.data?.readOnly ||
+ (!editable && !config.blocks.showEditBlocksInBabelView)
+ ) {
+ Block = blocksConfig?.[type]?.['view'] || ViewDefaultBlock;
+ }
+ const schema = blocksConfig?.[type]?.['schema'] || BlockSettingsSchema;
+ const blockHasOwnFocusManagement =
+ blocksConfig?.[type]?.['blockHasOwnFocusManagement'] || null;
+
+ return (
+ <>
+ {Block !== null ? (
+ {
+ const isMultipleSelection = e.shiftKey || e.ctrlKey || e.metaKey;
+ !this.props.selected &&
+ this.props.onSelectBlock(
+ this.props.id,
+ this.props.selected ? false : isMultipleSelection,
+ e,
+ );
+ }}
+ onKeyDown={
+ !(blockHasOwnFocusManagement || disableNewBlocks)
+ ? (e) =>
+ this.props.handleKeyDown(
+ e,
+ this.props.index,
+ this.props.id,
+ this.blockNode.current,
+ )
+ : null
+ }
+ className={cx('block', type, this.props.data.variation, {
+ selected: this.props.selected || this.props.multiSelected,
+ multiSelected: this.props.multiSelected,
+ })}
+ style={{ outline: 'none' }}
+ ref={this.blockNode}
+ // The tabIndex is required for the keyboard navigation
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
+ tabIndex={!blockHasOwnFocusManagement ? -1 : null}
+ >
+
+ {this.props.manage && (
+
+
+
+ )}
+
+ ) : (
+
+ !this.props.selected && this.props.onSelectBlock(this.props.id)
+ }
+ onKeyDown={
+ !(blockHasOwnFocusManagement || disableNewBlocks)
+ ? (e) =>
+ this.props.handleKeyDown(
+ e,
+ this.props.index,
+ this.props.id,
+ this.blockNode.current,
+ )
+ : null
+ }
+ className={cx(`block ${type}`, { selected: this.props.selected })}
+ style={{ outline: 'none' }}
+ ref={this.blockNode}
+ // The tabIndex is required for the keyboard navigation
+ tabIndex={-1}
+ >
+ {this.props.intl.formatMessage(messages.unknownBlock, {
+ block: type,
+ })}
+
+ )}
+ >
+ );
+ }
+}
+
+export default compose(
+ injectIntl,
+ withObjectBrowser,
+ connect(null, { setSidebarTab }),
+)(Edit);
diff --git a/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx b/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx
index 3b7668e..f14a401 100644
--- a/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx
+++ b/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx
@@ -57,7 +57,7 @@ import { compose } from 'redux';
import config from '@plone/volto/registry';
import SlotRenderer from '@plone/volto/components/theme/SlotRenderer/SlotRenderer';
import Iframe from '../../../../components/Iframe/View';
-
+import './styles.css';
/**
* Form container class.
* @class Form
@@ -700,7 +700,8 @@ class Form extends Component {
}}
/>
- {/* {
const newFormData = {
...formData,
@@ -730,7 +731,7 @@ class Form extends Component {
history={this.props.history}
location={this.props.location}
token={this.props.token}
- /> */}
+ />
{this.state.isClient &&
this.state.sidebarMetadataIsAvailable &&
this.props.editable &&
diff --git a/packages/volto-hydra/src/customizations/components/manage/Form/styles.css b/packages/volto-hydra/src/customizations/components/manage/Form/styles.css
new file mode 100644
index 0000000..bef06e3
--- /dev/null
+++ b/packages/volto-hydra/src/customizations/components/manage/Form/styles.css
@@ -0,0 +1,3 @@
+.blocks-form {
+ display: none;
+}
\ No newline at end of file
diff --git a/packages/volto-hydra/src/customizations/components/theme/Login/Login.jsx b/packages/volto-hydra/src/customizations/components/theme/Login/Login.jsx
index f57954e..9360501 100644
--- a/packages/volto-hydra/src/customizations/components/theme/Login/Login.jsx
+++ b/packages/volto-hydra/src/customizations/components/theme/Login/Login.jsx
@@ -151,13 +151,17 @@ const Login = (props) => {
progress
- Login below with username "hydraedit" and password "hydraedit" to see the progress
- of the editor experience. You can try any of the following
- frontends:
+ Login below with username "hydraedit" and password "hydraedit"
+ to see the progress of the editor experience. You can try any of
+ the following frontends:
-
+
Next.js Frontend
@@ -174,7 +178,12 @@ const Login = (props) => {
doesn't are in the{' '}
Hydra README
- . Report bugs to Hydra Issues.
+
+ . Report bugs to{' '}
+
+ Hydra Issues
+
+ .
diff --git a/packages/volto-hydra/src/index.js b/packages/volto-hydra/src/index.js
index cb042f0..a318034 100644
--- a/packages/volto-hydra/src/index.js
+++ b/packages/volto-hydra/src/index.js
@@ -1,4 +1,7 @@
+import selectedBlock from './reducers';
+
const applyConfig = (config) => {
+ config.addonReducers.selectedBlock = selectedBlock;
return config;
};
diff --git a/packages/volto-hydra/src/reducers.js b/packages/volto-hydra/src/reducers.js
new file mode 100644
index 0000000..32b59db
--- /dev/null
+++ b/packages/volto-hydra/src/reducers.js
@@ -0,0 +1,18 @@
+import { SET_SELECTED_BLOCK } from './constants';
+
+const initialState = {
+ uid: null,
+};
+
+export default function selectedBlock(state = initialState, action = {}) {
+ switch (action.type) {
+ case `${SET_SELECTED_BLOCK}`:
+ return {
+ ...state,
+ uid: action.uid,
+ };
+
+ default:
+ return state;
+ }
+}