From 8fe8d5b938392357d84507e1fdfb84c7c15d02d0 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Sat, 15 Jun 2024 22:23:03 +0530
Subject: [PATCH 01/16] send fomData to iframe whenever it is updated and
hydra.js will provide onEditChange to subscribe for changes
---
hydra.js | 24 ++++++++++++-
.../src/components/Iframe/View.jsx | 36 +++++++++++--------
2 files changed, 44 insertions(+), 16 deletions(-)
diff --git a/hydra.js b/hydra.js
index 4e754ae..eb506c6 100644
--- a/hydra.js
+++ b/hydra.js
@@ -29,7 +29,19 @@ class Bridge {
}
});
}
-
+ onEditChange(initialData, callback) {
+ window.addEventListener('message', (event) => {
+ if (event.origin === this.adminOrigin) {
+ if (event.data.type === 'FORM') {
+ if (event.data.data) {
+ callback(event.data.data);
+ } else {
+ callback(initialData);
+ }
+ }
+ }
+ });
+ }
async get_token() {
if (this.token !== null) {
return this.token;
@@ -130,3 +142,13 @@ export async function getToken() {
}
return '';
}
+/**
+ * Enable the frontend to listen for changes in the admin and call the callback with updated data
+ * @param {*} initialData
+ * @param {*} callback
+ */
+export function onEditChange(initialData, callback) {
+ if (bridgeInstance) {
+ bridgeInstance.onEditChange(initialData, callback);
+ }
+}
diff --git a/packages/volto-hydra/src/components/Iframe/View.jsx b/packages/volto-hydra/src/components/Iframe/View.jsx
index 6b54beb..bff4a19 100644
--- a/packages/volto-hydra/src/components/Iframe/View.jsx
+++ b/packages/volto-hydra/src/components/Iframe/View.jsx
@@ -5,26 +5,22 @@ import Cookies from 'js-cookie';
import './styles.css';
const Iframe = () => {
- const [url, setUrl] = useState('');
-
- const [src, setSrc] = useState('');
const history = useHistory();
const token = useSelector((state) => state.userSession.token);
-
+ const form = useSelector((state) => state.form.global);
const getDefualtUrlFromEnv = () =>
process.env['RAZZLE_DEFAULT_IFRAME_URL'] ||
(typeof window !== 'undefined' && window.env['RAZZLE_DEFAULT_IFRAME_URL']);
- useEffect(() => {
- const defaultUrl = getDefualtUrlFromEnv() || 'http://localhost:3002'; // fallback if env is not set
- const savedUrl = Cookies.get('iframe_url');
- const initialUrl = savedUrl
- ? `${savedUrl}${window.location.pathname.replace('/edit', '')}`
- : `${defaultUrl}${window.location.pathname.replace('/edit', '')}`;
-
- setUrl(initialUrl);
- setSrc(initialUrl);
+ const defaultUrl = getDefualtUrlFromEnv() || 'http://localhost:3002'; // fallback if env is not set
+ const savedUrl = Cookies.get('iframe_url');
+ const initialUrl = savedUrl
+ ? `${savedUrl}${history.location.pathname.replace('/edit', '')}`
+ : `${defaultUrl}${history.location.pathname.replace('/edit', '')}`;
+ const [url, setUrl] = useState(initialUrl);
+ const [src, setSrc] = useState(initialUrl);
+ useEffect(() => {
// Listen for messages from the iframe
const initialUrlOrigin = new URL(initialUrl).origin;
window.addEventListener('message', (event) => {
@@ -51,6 +47,16 @@ const Iframe = () => {
});
}, [token]);
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ // Send the form data to the iframe
+ const origin = new URL(initialUrl).origin;
+ document
+ .getElementById('previewIframe')
+ .contentWindow.postMessage({ type: 'FORM', data: form }, origin);
+ }
+ }, [form]);
+
const handleUrlChange = (event) => {
setUrl(event.target.value);
};
@@ -58,8 +64,8 @@ const Iframe = () => {
const handleNavigateToUrl = (givenUrl = '') => {
// Update adminUI URL with the new URL
const formattedUrl = givenUrl ? new URL(givenUrl) : new URL(url);
- const newUrl = formattedUrl.href;
- setSrc(newUrl);
+ // const newUrl = formattedUrl.href;
+ // setSrc(newUrl);
const newOrigin = formattedUrl.origin;
Cookies.set('iframe_url', newOrigin, { expires: 7 });
From fbd9fb206ab32578a2ffdd5ad45ddf79c5e726b5 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Sun, 16 Jun 2024 02:45:52 +0530
Subject: [PATCH 02/16] update readme
---
README.md | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 3035d9c..56c47d7 100644
--- a/README.md
+++ b/README.md
@@ -141,9 +141,27 @@ If you wish to make the editing experience faster you can register for ```onSave
#### Enable Show changes while editing
-You will need to subscribe to an ```onEditChange``` event that will send blocks or metadata changes
+You will need to subscribe to an ```onEditChange``` event that will send blocks or metadata changes.
-TODO: not implemented yet.
+The `onEditChange` method listens for changes in the Hydra and triggers a callback with updated data or the initial data if no update is available. It takes following args:
+| Args | Description |
+| :-----------:| :-------|
+| *initialData*| The initial data to fall back on if no updated data is received. |
+| *callback* | A function to call with the updated data when a change is detected. |
+
+Usage:
+```js
+// the initial data (from ploneClient)
+const initialData = data;
+
+// Define the callback function
+function handleEditChange(updatedData) {
+ console.log('Updated data:', updatedData);
+}
+
+// Set up the onEditChange listener
+onEditChange(initialData, handleEditChange);
+```
#### Enable Managing Blocks directly on your frontend
From 687cacd18a7e0d1221209c373bc6b13876f3188c Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Mon, 17 Jun 2024 14:30:18 +0530
Subject: [PATCH 03/16] add redux state to store selected block uid
---
README.md | 3 +
packages/volto-hydra/src/actions.js | 8 +
.../src/components/Iframe/View.jsx | 8 +-
packages/volto-hydra/src/constants.js | 1 +
.../components/manage/Blocks/Block/Edit.jsx | 226 ++++++++++++++++++
.../manage/Blocks/Block/Edit.original.jsx | 222 +++++++++++++++++
.../components/manage/Form/Form.jsx | 7 +-
.../components/manage/Form/styles.css | 3 +
packages/volto-hydra/src/index.js | 3 +
packages/volto-hydra/src/reducers.js | 18 ++
10 files changed, 495 insertions(+), 4 deletions(-)
create mode 100644 packages/volto-hydra/src/actions.js
create mode 100644 packages/volto-hydra/src/constants.js
create mode 100644 packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
create mode 100644 packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.original.jsx
create mode 100644 packages/volto-hydra/src/customizations/components/manage/Form/styles.css
create mode 100644 packages/volto-hydra/src/reducers.js
diff --git a/README.md b/README.md
index 56c47d7..8155473 100644
--- a/README.md
+++ b/README.md
@@ -168,6 +168,9 @@ onEditChange(initialData, handleEditChange);
You will add data attributes to your rendered block html so hydra knows where they are on the page and it
will automatically handle click events and show a quanta toolbar when selecting a block.
+Add the `data-block-uid={}` attribute to your rendered block html and call the `enableBlockClickListener()` provided by 'hydra.js' whenever your component is mounted.
+Now, Click on your blocks in iframe and the sidebar will show its settings.
+
TODO: not implemented yet
#### Enable Editing blocks inplace
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 bff4a19..8d1b3af 100644
--- a/packages/volto-hydra/src/components/Iframe/View.jsx
+++ b/packages/volto-hydra/src/components/Iframe/View.jsx
@@ -1,10 +1,12 @@
import React, { useState, useEffect } 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';
const Iframe = () => {
+ const dispatch = useDispatch();
const history = useHistory();
const token = useSelector((state) => state.userSession.token);
const form = useSelector((state) => state.form.global);
@@ -41,6 +43,10 @@ const Iframe = () => {
);
break;
+ case 'OPEN_SETTINGS':
+ dispatch(setSelectedBlock(event.data.uid));
+ break;
+
default:
break;
}
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..3608ccd
--- /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) => ({
+ selectedBlock: state.selectedBlock,
+ }),
+ { 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/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;
+ }
+}
From be879d5a39a6804eb9ef494b94018f194b16d642 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Mon, 17 Jun 2024 15:07:57 +0530
Subject: [PATCH 04/16] update hydra.js to listen to click events on blocks
with attribute
---
hydra.js | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/hydra.js b/hydra.js
index eb506c6..20fabbf 100644
--- a/hydra.js
+++ b/hydra.js
@@ -114,6 +114,18 @@ class Bridge {
}
return null;
}
+ enableBlockClickListener() {
+ document.addEventListener('click', (event) => {
+ const blockElement = event.target.closest('[data-block-uid]');
+ if (blockElement) {
+ const blockUid = blockElement.getAttribute('data-block-uid');
+ window.parent.postMessage(
+ { type: 'OPEN_SETTINGS', uid: blockUid },
+ this.adminOrigin,
+ );
+ }
+ });
+ }
}
// Export an instance of the Bridge class
@@ -152,3 +164,9 @@ export function onEditChange(initialData, callback) {
bridgeInstance.onEditChange(initialData, callback);
}
}
+
+export function enableBlockClickListener() {
+ if (bridgeInstance) {
+ bridgeInstance.enableBlockClickListener();
+ }
+}
From d43d4986ea325175bae260b3c8a96045434a5782 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Tue, 18 Jun 2024 02:34:56 +0530
Subject: [PATCH 05/16] map this.props.selected with state
---
.../src/customizations/components/manage/Blocks/Block/Edit.jsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
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
index 3608ccd..67b90e0 100644
--- a/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
+++ b/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
@@ -218,8 +218,9 @@ export default compose(
injectIntl,
withObjectBrowser,
connect(
- (state) => ({
+ (state, props) => ({
selectedBlock: state.selectedBlock,
+ selected: state.selectedBlock.uid === props.id,
}),
{ setSidebarTab },
),
From 05a266875add8b31d1b8f01e8d7e5d23497ae61a Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Tue, 18 Jun 2024 22:23:33 +0530
Subject: [PATCH 06/16] only dispatch action selectBlock when in edit mode
---
packages/volto-hydra/src/components/Iframe/View.jsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/volto-hydra/src/components/Iframe/View.jsx b/packages/volto-hydra/src/components/Iframe/View.jsx
index 8d1b3af..cbeb209 100644
--- a/packages/volto-hydra/src/components/Iframe/View.jsx
+++ b/packages/volto-hydra/src/components/Iframe/View.jsx
@@ -44,7 +44,9 @@ const Iframe = () => {
break;
case 'OPEN_SETTINGS':
- dispatch(setSelectedBlock(event.data.uid));
+ if (history.location.pathname.endsWith('/edit')) {
+ dispatch(setSelectedBlock(event.data.uid));
+ }
break;
default:
From 83656b70d49c2ed04d67e7e4035ad61203ceaedd Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Wed, 19 Jun 2024 12:10:53 +0530
Subject: [PATCH 07/16] update readme to include example
---
README.md | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 8155473..33c267b 100644
--- a/README.md
+++ b/README.md
@@ -169,10 +169,27 @@ You will add data attributes to your rendered block html so hydra knows where th
will automatically handle click events and show a quanta toolbar when selecting a block.
Add the `data-block-uid={}` attribute to your rendered block html and call the `enableBlockClickListener()` provided by 'hydra.js' whenever your component is mounted.
-Now, Click on your blocks in iframe and the sidebar will show its settings.
+The `data-block-uid` requires the block's UID, which you need to provide in the outermost container of the block.
-TODO: not implemented yet
+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
+// This will create a list of all the slate Blocks with 'data-block-uid' attribute
+{data.blocks_layout.items.map((id, index) => {
+ if (data.blocks[id]["@type"] === "slate") {
+ const slateValue = data.blocks[id].value;
+ return (
+
+
+ {JSON.stringify(slateValue, null, 2)}
+
+
+ );
+ }
+ })}
+```
#### Enable Editing blocks inplace
You will add data attributes to where a blocks text is editable and subscribe to ```onBlockFieldChanged``` events to handle fine grained
From 667319b144ac835c3ddb60a0458472afe19f5d0f Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Wed, 19 Jun 2024 12:11:30 +0530
Subject: [PATCH 08/16] correct example in readme
---
README.md | 41 ++++++++++++++++++++++++++++-------------
1 file changed, 28 insertions(+), 13 deletions(-)
diff --git a/README.md b/README.md
index 33c267b..83796cc 100644
--- a/README.md
+++ b/README.md
@@ -176,19 +176,34 @@ Now, Click on your blocks in iframe and the sidebar will show its settings.
Usage:
```js
-// This will create a list of all the slate Blocks with 'data-block-uid' attribute
-{data.blocks_layout.items.map((id, index) => {
- if (data.blocks[id]["@type"] === "slate") {
- const slateValue = data.blocks[id].value;
- return (
-
-
- {JSON.stringify(slateValue, null, 2)}
-
-
- );
- }
- })}
+// 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);
+ enableBlockClickListener(); // Call the hydra.js function
+}
+
+// Call the function to render the blocks
+createBlockList(data);
```
#### Enable Editing blocks inplace
From bf52439b40aa81575da22e0e84a7725e3105ea90 Mon Sep 17 00:00:00 2001
From: "Mohammad K. Hussain" <99530996+MAX-786@users.noreply.github.com>
Date: Thu, 20 Jun 2024 14:07:24 +0530
Subject: [PATCH 09/16] Update README.md
Co-authored-by: Dylan Jay
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 83796cc..970683d 100644
--- a/README.md
+++ b/README.md
@@ -168,7 +168,7 @@ onEditChange(initialData, handleEditChange);
You will add data attributes to your rendered block html so hydra knows where they are on the page and it
will automatically handle click events and show a quanta toolbar when selecting a block.
-Add the `data-block-uid={}` attribute to your rendered block html and call the `enableBlockClickListener()` provided by 'hydra.js' whenever your component is mounted.
+Add the `data-block-uid={<>}` attribute to your outer most container of the rendered block html and call the `enableBlockClickListener()` provided by 'hydra.js' whenever your component is mounted.
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]`.
From 7441d7b9a787c735e30bd5bb34d443919f1b390d Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Thu, 20 Jun 2024 14:33:15 +0530
Subject: [PATCH 10/16] remove the need to call extra api
---
README.md | 3 +--
hydra.js | 1 +
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 970683d..2ec222b 100644
--- a/README.md
+++ b/README.md
@@ -168,7 +168,7 @@ onEditChange(initialData, handleEditChange);
You will add data attributes to your rendered block html so hydra knows where they are on the page and it
will automatically handle click events and show a quanta toolbar when selecting a block.
-Add the `data-block-uid={<>}` attribute to your outer most container of the rendered block html and call the `enableBlockClickListener()` provided by 'hydra.js' whenever your component is mounted.
+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]`.
@@ -199,7 +199,6 @@ function createBlockList(data) {
});
document.body.appendChild(blockList);
- enableBlockClickListener(); // Call the hydra.js function
}
// Call the function to render the blocks
diff --git a/hydra.js b/hydra.js
index 20fabbf..4c12da7 100644
--- a/hydra.js
+++ b/hydra.js
@@ -28,6 +28,7 @@ class Bridge {
}
}
});
+ enableBlockClickListener();
}
onEditChange(initialData, callback) {
window.addEventListener('message', (event) => {
From 5d054b23ad6ab7b4f45961ea09f6b5a860c085e3 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Thu, 20 Jun 2024 15:09:56 +0530
Subject: [PATCH 11/16] correct calling of enableBlockClickListener in init
bridge
---
hydra.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hydra.js b/hydra.js
index 4c12da7..e2596e2 100644
--- a/hydra.js
+++ b/hydra.js
@@ -28,7 +28,7 @@ class Bridge {
}
}
});
- enableBlockClickListener();
+ this.enableBlockClickListener();
}
onEditChange(initialData, callback) {
window.addEventListener('message', (event) => {
From 7e9f53dc6f6d2046c0511c93f5ef56e025ffe89e Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Thu, 20 Jun 2024 15:14:13 +0530
Subject: [PATCH 12/16] remove unused state value from edit.jsx
---
.../src/customizations/components/manage/Blocks/Block/Edit.jsx | 1 -
1 file changed, 1 deletion(-)
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
index 67b90e0..f799517 100644
--- a/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
+++ b/packages/volto-hydra/src/customizations/components/manage/Blocks/Block/Edit.jsx
@@ -219,7 +219,6 @@ export default compose(
withObjectBrowser,
connect(
(state, props) => ({
- selectedBlock: state.selectedBlock,
selected: state.selectedBlock.uid === props.id,
}),
{ setSidebarTab },
From 5c8c69174a8fca7e1ca747c2d39f2d1434ea61c5 Mon Sep 17 00:00:00 2001
From: "me@jeffersonbledsoe.com"
Date: Thu, 20 Jun 2024 13:53:28 +0100
Subject: [PATCH 13/16] Fix bad merge
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index 99e04db..837f28a 100644
--- a/README.md
+++ b/README.md
@@ -149,7 +149,6 @@ The 'data' object follows the same format as you get from the [ploneClient](http
`onEditChange` takes following args:
| Args | Description |
| :-----------:| :-------|
-| *initialData*| The initial data to fall back on if no updated data is received. |
| *callback* | A function to call with the updated data when a change is detected. |
Usage:
From 48d6c32cf2911caa27bb28e263a5d7902cc90c0e Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Fri, 21 Jun 2024 18:38:00 +0530
Subject: [PATCH 14/16] no longer using postMessage for auth_token
---
packages/volto-hydra/src/components/Iframe/View.jsx | 7 -------
1 file changed, 7 deletions(-)
diff --git a/packages/volto-hydra/src/components/Iframe/View.jsx b/packages/volto-hydra/src/components/Iframe/View.jsx
index a90ae7d..eba9f0e 100644
--- a/packages/volto-hydra/src/components/Iframe/View.jsx
+++ b/packages/volto-hydra/src/components/Iframe/View.jsx
@@ -100,13 +100,6 @@ const Iframe = () => {
handleNavigateToUrl(event.data.url);
break;
- case 'GET_TOKEN': // Request for the token from the iframe
- event.source.postMessage(
- { type: 'GET_TOKEN_RESPONSE', token: token },
- event.origin,
- );
- break;
-
case 'OPEN_SETTINGS':
if (history.location.pathname.endsWith('/edit')) {
dispatch(setSelectedBlock(event.data.uid));
From dbee93c7e06353358cd002cffa13620600e70f12 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Fri, 21 Jun 2024 19:38:44 +0530
Subject: [PATCH 15/16] fix dependecy array in useEffect
---
packages/volto-hydra/src/components/Iframe/View.jsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/packages/volto-hydra/src/components/Iframe/View.jsx b/packages/volto-hydra/src/components/Iframe/View.jsx
index eba9f0e..cc4b19f 100644
--- a/packages/volto-hydra/src/components/Iframe/View.jsx
+++ b/packages/volto-hydra/src/components/Iframe/View.jsx
@@ -118,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)) {
From 58b3b726416a23fb3df61aa10af852a7b7233d65 Mon Sep 17 00:00:00 2001
From: MAX-786
Date: Fri, 21 Jun 2024 21:40:08 +0530
Subject: [PATCH 16/16] adding new cleaner domain at LoginPage
---
.../components/theme/Login/Login.jsx | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
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
+
+ .