Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select Block from iframe and its config appear in sidebar #51

Merged
merged 20 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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={<<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).
Expand Down
41 changes: 27 additions & 14 deletions packages/hydra-js/hydra.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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();
}
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions packages/volto-hydra/src/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SET_SELECTED_BLOCK } from './constants';

export function setSelectedBlock(uid) {
return {
type: SET_SELECTED_BLOCK,
uid: uid,
};
}
29 changes: 24 additions & 5 deletions packages/volto-hydra/src/components/Iframe/View.jsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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) {
Expand All @@ -36,6 +42,7 @@ function isValidUrl(string) {
}

const Iframe = () => {
const dispatch = useDispatch();
const [url, setUrl] = useState('');

const [src, setSrc] = useState('');
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions packages/volto-hydra/src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SET_SELECTED_BLOCK = 'SET_SELECTED_BLOCK';
Loading