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

(feat+fix): Use BlocksForm instead of SlateEditor to render more slates block #44

Merged
merged 22 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
7 changes: 4 additions & 3 deletions cypress/e2e/01-hero-block-basics.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ describe('Blocks Tests', () => {
'hero',
);
cy.get('.button.hero').click({ force: true });
cy.get('.hero-block-text div[role="textbox"]')
.click()
.type('My Hero Block');

cy.get(
'.inline.field.textarea.field-wrapper-buttonLabel textarea#field-buttonLabel',
Expand All @@ -37,6 +34,10 @@ describe('Blocks Tests', () => {
.eq(0)
.click();

cy.get('.hero-block-text div[role="textbox"]')
.click()
.type('My Hero Block');

// Save
cy.get('#toolbar-save').click();
cy.url().should('eq', Cypress.config().baseUrl + '/cypress/my-page');
Expand Down
20 changes: 12 additions & 8 deletions cypress/e2e/02-dexterity-controlpanel-layout.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('ControlPanel: Dexterity Content-Types Layout', () => {
cy.get('.blocks-chooser .title').contains('Common').click();
cy.get('.content.active.common .button.hero')
.contains('Hero')

.click({ force: true });

cy.get('#toolbar-save').click();
Expand All @@ -48,17 +49,16 @@ describe('ControlPanel: Dexterity Content-Types Layout', () => {
cy.get('button[class="add"]').click();
cy.get('#toolbar-add-book').click();
cy.get('.block.title').contains('Book title');
cy.get('.block.hero div[role="presentation"]').click();

// Add text and button
cy.get('.hero-block-text div[role="textbox"]')
.click()
.type('My hero block');
cy.get('label[for="field-fullWidth"]').click();
cy.get('label[for="field-fullHeight"]').click();
cy.get('.block.hero div[role="presentation"]').first().click();
cy.get('.formtabs.menu').children().first().next().click();

cy.get('label[for="field-quoted"]').click();
cy.get('label[for="field-spaced"]').click();
cy.get('.field-wrapper-buttonLabel #field-buttonLabel').click().type('Label')
cy.get('.field-wrapper-buttonLabel #field-buttonLabel')
.click()
.type('Label');
cy.get('#field-buttonLabel').click().type('my button');
cy.get('.inline.field.field-attached-image .ui.input')
.click()
Expand All @@ -67,6 +67,10 @@ describe('ControlPanel: Dexterity Content-Types Layout', () => {
'.inline.field.field-attached-image .ui.buttons .primary.button',
).click();

cy.get('.hero-block-text div[role="textbox"]')
.click()
.type('My hero block');

// Change book title
cy.clearSlateTitle();
cy.getSlateTitle().type('My First Book');
Expand All @@ -78,6 +82,6 @@ describe('ControlPanel: Dexterity Content-Types Layout', () => {
cy.get('.hero-block-meta .button').contains('my button');
cy.get('.hero-block-image-wrapper');
cy.get('.eea.hero-block.spaced.inverted.full-height');
cy.get('.hero-block-meta.text-left .button').contains('Label')
cy.get('.hero-block-meta.text-left .button').contains('Label');
});
});
138 changes: 76 additions & 62 deletions src/components/Blocks/Hero/Edit.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import React from 'react';
import React, { useState } from 'react';
import cx from 'classnames';
import isFunction from 'lodash/isFunction';
import { Icon } from 'semantic-ui-react';
import config from '@plone/volto/registry';
import { BlocksForm } from '@plone/volto/components';
import EditBlockWrapper from './EditBlockWrapper';
import { v4 as uuid } from 'uuid';

import { emptyBlocksForm } from '@plone/volto/helpers';
import { isEmpty } from 'lodash';
import {
BlockDataForm,
SidebarPortal,
UniversalLink,
} from '@plone/volto/components';
import { BodyClass } from '@plone/volto/helpers';
import SlateEditor from '@plone/volto-slate/editor/SlateEditor';
import {
handleKey,
handleKeyDetached,
} from '@plone/volto-slate/blocks/Text/keyboard';
import {
createSlateHeader,
getFieldURL,
} from '@eeacms/volto-hero-block/helpers';

import { getFieldURL } from '@eeacms/volto-hero-block/helpers';
import { HeroBlockSchema } from './schema';
import Copyright from './Copyright';
import Hero from './Hero';
Expand All @@ -39,23 +38,19 @@ const Metadata = ({ buttonLabel, inverted, styles, ...props }) => {
};

export default function Edit(props) {
const { slate } = config.settings;
const id = uuid();
const [selectedBlock, setSelectedBlock] = useState(id);
const {
data = {},
block = null,
selected = false,
index,
selected,
properties,
onChangeBlock,
onSelectBlock,
onChangeField,
pathname,
metadata = null,
} = props;
const {
text,
copyright,
copyrightIcon,
copyrightPosition,
isMultiline,
} = data;
const { copyright, copyrightIcon, copyrightPosition } = data;
const copyrightPrefix = config.blocks.blocksConfig.hero.copyrightPrefix || '';
const schema = React.useMemo(() => {
if (isFunction(HeroBlockSchema)) {
Expand All @@ -64,58 +59,77 @@ export default function Edit(props) {
return HeroBlockSchema;
}, [props]);

const withBlockProperties = React.useCallback(
(editor) => {
editor.getBlockProps = () => props;
return editor;
},
[props],
);
const blockState = {};
const data_blocks = data?.data?.blocks;
if (data?.text || isEmpty(data_blocks)) {
let dataWithoutText = { ...data };
if (dataWithoutText) delete dataWithoutText.text;

const handleFocus = React.useCallback(() => {
if (!selected) {
onSelectBlock(block);
}
}, [onSelectBlock, selected, block]);

const extensions = React.useMemo(() => {
if (isMultiline) {
return slate.textblockExtensions.filter(
(f) => f.name !== 'withSplitBlocksOnBreak',
);
} else {
return slate.textblockExtensions;
}
}, [slate.textblockExtensions, isMultiline]);

const value = createSlateHeader(text);
onChangeBlock(block, {
...dataWithoutText,
data: data?.text
? {
blocks: {
[id]: {
'@type': 'slate',
value: data.text,
plaintext: data.text?.[0].children?.[0].text,
},
},
blocks_layout: { items: [id] },
}
: emptyBlocksForm(),
});
}

return (
<>
<BodyClass className="with-hero-block" />

<Hero {...data}>
<Hero.Text {...data}>
<SlateEditor
key={isMultiline}
detached={!isMultiline}
index={index}
properties={properties}
extensions={extensions}
renderExtensions={[withBlockProperties]}
value={value}
onChange={(text) => {
<BlocksForm
metadata={properties || metadata}
properties={data.data || {}}
manage={false}
allowedBlocks={'slate'}
selectedBlock={selected ? selectedBlock : null}
title={data.placeholder}
onSelectBlock={(s, e) => {
setSelectedBlock(s);
}}
onChangeFormData={(newFormData) => {
onChangeBlock(block, {
...data,
text,
data: newFormData,
});
}}
block={block}
onFocus={handleFocus}
onKeyDown={isMultiline ? handleKeyDetached : handleKey}
selected={selected}
placeholder="Add text..."
slateSettings={slate}
/>
onChangeField={(id, value) => {
if (['blocks', 'blocks_layout'].indexOf(id) > -1) {
blockState[id] = value;
onChangeBlock(block, {
...data,
data: {
...data.data,
...blockState,
},
});
} else {
onChangeField(id, value);
}
}}
pathname={pathname}
>
{({ draginfo }, editBlock, blockProps) => (
<EditBlockWrapper
draginfo={draginfo}
blockProps={blockProps}
disabled={data.disableInnerButtons}
>
{editBlock}
</EditBlockWrapper>
)}
</BlocksForm>
</Hero.Text>
<Hero.Meta {...data}>
<Metadata {...data} />
Expand Down
58 changes: 22 additions & 36 deletions src/components/Blocks/Hero/Edit.test.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import Edit from './Edit';
import config from '@plone/volto/registry';
import '@testing-library/jest-dom/extend-expect';

const mockStore = configureStore([]);

jest.mock('@plone/volto-slate/editor/SlateEditor', () => {
const observe = jest.fn();
const unobserve = jest.fn();
jest.mock('@plone/volto/components', () => {
return {
__esModule: true,
default: ({ placeholder, children, onChange, onFocus }) => (
<div
onChange={(target) => onChange(target)}
onFocus={() => onFocus()}
id="test"
>
BlocksForm: ({ placeholder, children, onChange, onFocus }) => (
<div id="test">
<div>{placeholder}</div>
{children}
</div>
),
SidebarPortal: ({ children }) => <div>{children}</div>,
BlockDataForm: () => <div></div>,
UniversalLink: () => <div></div>,
RenderBlocks: () => <div></div>,
};
});
jest.mock('react-router-dom', () => ({
useLocation: jest.fn().mockReturnValue({
pathname: '/test-jest',
search: '',
hash: '',
state: null,
key: 'test-jest',
}),
}));

const observe = jest.fn();
const unobserve = jest.fn();
window.IntersectionObserver = jest.fn((callback) => ({
observe,
unobserve,
Expand Down Expand Up @@ -60,7 +68,7 @@ describe('Edit component', () => {
it('renders without crashing', () => {
const { container } = render(
<Provider store={store}>
<Edit />
<Edit onChangeBlock={() => {}} onSelectBlock={() => {}} />
</Provider>,
);
expect(container).toBeTruthy();
Expand All @@ -82,35 +90,13 @@ describe('Edit component', () => {

const { container } = render(
<Provider store={store}>
<Edit data={data} />
<Edit data={data} onChangeBlock={() => {}} />
</Provider>,
);

expect(container.querySelector('#test')).toBeInTheDocument();
});

it('calls onFocus when SlateEditor is focused', () => {
config.blocks = {
blocksConfig: {
hero: {
copyrightPrefix: 'Test Prefix',
schema: () => ({
title: 'Hero',
}),
},
},
};
const onSelectBlock = jest.fn();
const { getByText } = render(
<Provider store={store}>
<Edit onSelectBlock={onSelectBlock} />
</Provider>,
);

fireEvent.focus(getByText('Add text...'));
expect(onSelectBlock).toHaveBeenCalled();
});

it('renders without copyrightPrefix', () => {
config.blocks = {
blocksConfig: {
Expand All @@ -124,7 +110,7 @@ describe('Edit component', () => {
const onSelectBlock = jest.fn();
render(
<Provider store={store}>
<Edit onSelectBlock={onSelectBlock} />
<Edit onSelectBlock={onSelectBlock} onChangeBlock={() => {}} />
</Provider>,
);
});
Expand Down
Loading