Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 0 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -806,8 +806,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa

## 16.0.0-alpha.34 (2022-09-17)

### Breaking

### Feature

- Added new components `Aliases` for aliases control in Volto. Alias management in both controlpanel and object view. @andreiggr @avoinea
Expand All @@ -818,8 +816,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa
- Add `matchAllRoutes` to AsyncConnect so that it matches all configured `asyncPropsExtenders` @tiberiuichim
- Fix acceptence test groups controlpanel @ksuess

### Internal

### Documentation

- Bring back "Guidelines for Contributing"
Expand Down
1 change: 1 addition & 0 deletions news/3161.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Quanta toolbar as experimental feature @sneridagh
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,10 @@ export const DefaultTextBlockEditor = (props) => {
}}
</Dropzone>

{!config.experimental.addBlockButton.enabled &&
{!(
config.experimental.addBlockButton.enabled ||
config.experimental.quantaToolbar.enabled
) &&
selected &&
!data.plaintext?.trim() &&
!disableNewBlocks && (
Expand Down
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export Workflow from '@plone/volto/components/manage/Workflow/Workflow';
export Messages from '@plone/volto/components/manage/Messages/Messages';
export BlockChooser from '@plone/volto/components/manage/BlockChooser/BlockChooser';
export BlockChooserButton from '@plone/volto/components/manage/BlockChooser/BlockChooserButton';
export BlockToolbarItem from '@plone/volto/components/manage/Blocks/Block/BlockToolbarItem';
export Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
export Sidebar from '@plone/volto/components/manage/Sidebar/Sidebar';
export SidebarPopup from '@plone/volto/components/manage/Sidebar/SidebarPopup';
Expand Down
25 changes: 25 additions & 0 deletions src/components/manage/Blocks/Block/BlockToolbarItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* A Quanta toolbar entry button that can render itself either as
* a Dropdown.Item (in the more menu) or a simple Button.
*
*/

import React from 'react';
import { Dropdown, Button } from 'semantic-ui-react';
import { Icon } from '@plone/volto/components';

const BlockToolbarItem = (props) => {
const { isMenuShape = false, icon, label, onClick } = props;
return isMenuShape ? (
<Dropdown.Item onClick={onClick}>
<Icon name={icon} size="18px" />
{label}
</Dropdown.Item>
) : (
<Button icon basic onClick={onClick} title={label}>
<Icon name={icon} size="18px" />
</Button>
);
};

export default BlockToolbarItem;
58 changes: 58 additions & 0 deletions src/components/manage/Blocks/Block/BlockToolbarItem.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import Wrapper from '@plone/volto/storybook';
import BlockToolbarItem from './BlockToolbarItem';
import { PluggablesProvider } from '@plone/volto/components/manage/Pluggable';
import trashSVG from '@plone/volto/icons/delete.svg';
import hideSVG from '@plone/volto/icons/hide.svg';
import showSVG from '@plone/volto/icons/show.svg';

const icons = {
showSVG,
hideSVG,
trashSVG,
};

const BlockToolbarItemStory = (args) => {
return (
<Wrapper>
<PluggablesProvider>
<BlockToolbarItem {...args} icon={icons[args.icon]} />
</PluggablesProvider>
</Wrapper>
);
};

export const Default = BlockToolbarItemStory.bind({});

Default.args = {
id: 'field-default',
label: 'Sample entry',
icon: 'trashSVG',
isMenuShape: false,
};

export default {
title: 'Quanta/BlockToolbaritem',
component: BlockToolbarItem,
decorators: [
(Story) => (
<div style={{ width: '400px' }}>
<Story />
</div>
),
],
argTypes: {
// controlled value prop
icon: {
// control: {
// disable: true,
// },
control: {
type: 'select',
options: ['showSVG', 'hideSVG', 'trashSVG'],
},
},
},
// excludeStories: ['searchResults'],
// subcomponents: { ArgsTable },
};
52 changes: 42 additions & 10 deletions src/components/manage/Blocks/Block/BlocksForm.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import WrapperlessBlockEdit from './Edit';
import { useIntl } from 'react-intl';
import EditBlock from './Edit';
import { DragDropList } from '@plone/volto/components';
import {
getBlocks,
Expand All @@ -10,6 +10,7 @@ import {
import {
addBlock,
insertBlock,
insertBlockAfter,
changeBlock,
deleteBlock,
moveBlock,
Expand All @@ -21,8 +22,25 @@ import EditBlockWrapper from './EditBlockWrapper';
import { setSidebarTab } from '@plone/volto/actions';
import { useDispatch } from 'react-redux';
import { useDetectClickOutside, useEvent } from '@plone/volto/helpers';
import QuantaEditBlockWrapper from './QuantaEditBlockWrapper';
import config from '@plone/volto/registry';

const topLevelBlockWrapper = ({ draginfo }, editBlock, blockProps) => {
return (
<QuantaEditBlockWrapper draginfo={draginfo} blockProps={blockProps}>
{editBlock}
</QuantaEditBlockWrapper>
);
};

const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => {
return (
<EditBlockWrapper draginfo={draginfo} blockProps={blockProps}>
{editBlock}
</EditBlockWrapper>
);
};

const BlocksForm = (props) => {
const {
pathname,
Expand All @@ -42,6 +60,7 @@ const BlocksForm = (props) => {
isMainForm = true,
blocksConfig = config.blocks.blocksConfig,
editable = true,
direction,
} = props;

const blockList = getBlocks(properties);
Expand Down Expand Up @@ -137,6 +156,17 @@ const BlocksForm = (props) => {
return newId;
};

const onInsertBlockAfter = (id, value, current) => {
const [newId, newFormData] = insertBlockAfter(
properties,
id,
value,
current,
);
onChangeFormData(newFormData);
return newId;
};

const onAddBlock = (type, index) => {
if (editable) {
const [id, newFormData] = addBlock(properties, type, index);
Expand Down Expand Up @@ -172,14 +202,6 @@ const BlocksForm = (props) => {
onChangeFormData(newFormData);
};

const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => (
<EditBlockWrapper draginfo={draginfo} blockProps={blockProps}>
{editBlock}
</EditBlockWrapper>
);

const editBlockWrapper = children || defaultBlockWrapper;

// Remove invalid blocks on saving
// Note they are alreaady filtered by DragDropList, but we also want them
// to be removed when the user saves the page next. Otherwise the invalid
Expand All @@ -203,6 +225,7 @@ const BlocksForm = (props) => {
<fieldset className="invisible" disabled={!editable}>
<DragDropList
childList={blockList}
direction={direction}
onMoveItem={(result) => {
const { source, destination } = result;
if (!destination) {
Expand Down Expand Up @@ -232,6 +255,7 @@ const BlocksForm = (props) => {
manage,
onAddBlock,
onInsertBlock,
onInsertBlockAfter,
onChangeBlock,
onChangeField,
onChangeFormData,
Expand All @@ -251,9 +275,17 @@ const BlocksForm = (props) => {
editable,
showBlockChooser: selectedBlock === childId,
};
const editBlockWrapper =
children ||
(props.isMainForm &&
(config.experimental.quantaToolbar.enabled ||
blocksConfig[child['@type']].disableQuantaToolbar)
? topLevelBlockWrapper
: defaultBlockWrapper);

return editBlockWrapper(
dragProps,
<EditBlock key={childId} {...blockProps} />,
<WrapperlessBlockEdit key={childId} {...blockProps} />,
blockProps,
);
}}
Expand Down
9 changes: 7 additions & 2 deletions src/components/manage/Blocks/Block/EditBlockWrapper.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
/** This is the classic block wrapper. The BlocksForm uses either this one or
* the QuantaEditBlockWrapper
*/

import React from 'react';
import { Icon } from '@plone/volto/components';
import {
blockHasValue,
buildStyleClassNamesFromData,
} from '@plone/volto/helpers';
import dragSVG from '@plone/volto/icons/drag.svg';
import { injectIntl, defineMessages } from 'react-intl';

import config from '@plone/volto/registry';
import { Button } from 'semantic-ui-react';
import includes from 'lodash/includes';
import isBoolean from 'lodash/isBoolean';
import { defineMessages, injectIntl } from 'react-intl';
import cx from 'classnames';
import config from '@plone/volto/registry';
import { BlockChooserButton } from '@plone/volto/components';

import trashSVG from '@plone/volto/icons/delete.svg';
Expand Down
97 changes: 97 additions & 0 deletions src/components/manage/Blocks/Block/GroupedMenuButtons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import { Button, Dropdown } from 'semantic-ui-react';
import moreSVG from '@plone/volto/icons/more.svg';
import { Icon } from '@plone/volto/components';
import config from '@plone/volto/registry';
import cx from 'classnames';

const GroupedMenuButtons = ({
items,
maxSizeBeforeCollapse = 3,
params = {},
groupIcon = moreSVG,
toolbarGroups = config.blocks.toolbarGroups,
className,
}) => {
const groups = new Map();
const seen = [];
const options = { isMenuShape: items.length > 1, ...params };

toolbarGroups.forEach(({ id, title }) => {
groups[id] = items
.filter((renderer, i) => {
if (renderer.extra?.group === id) {
seen.push(i);
return true;
}
return false;
})
.map((renderer) => [renderer(options), renderer.id])
.filter(([res]) => !!res);
});
const ungrouped = items
.filter((renderer, i) => seen.indexOf(i) === -1)
.map((renderer) => [renderer(options), renderer.id])
.filter(([res]) => !!res);

// NOTE: this will reorder even if ungrouped, based on config group
// defined order
let allItems = []; // TODO: use reduce
Object.keys(groups)
.map((n) => groups[n])
.forEach((entries) => (allItems = [...allItems, ...entries]));
allItems = [...allItems, ...ungrouped];

const isDropdown = allItems.length > maxSizeBeforeCollapse;

return isDropdown ? (
<Dropdown
item
icon={
<div className="button-wrapper">
<Button icon className={cx('group', [className])}>
<Icon name={groupIcon} size="24px" />
</Button>
</div>
}
className={cx('grouped-buttons', [className])}
>
<Dropdown.Menu className="right">
<>
{Object.keys(groups).map((groupName) => {
const results = groups[groupName];
const { title } = config.blocks.toolbarGroups.find(
(g) => g.id === groupName,
);

return results.length > 0 ? (
<>
<Dropdown.Header content={title} />
<Dropdown.Menu scrolling>
{results.map(([res, id]) => (
<React.Fragment key={id}>{res}</React.Fragment>
))}
</Dropdown.Menu>
</>
) : null;
})}
{ungrouped.length > 0 ? (
<>
{ungrouped.map(([res, id]) => (
<Dropdown.Item key={id} className={id}>
{res}
</Dropdown.Item>
))}
</>
) : (
''
)}
</>
</Dropdown.Menu>
</Dropdown>
) : allItems.length > 0 ? (
allItems.map(([res, id]) => <React.Fragment key={id}>{res}</React.Fragment>)
) : null;
};

export default GroupedMenuButtons;
21 changes: 21 additions & 0 deletions src/components/manage/Blocks/Block/PluggableMenuSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { Pluggable } from '@plone/volto/components/manage/Pluggable';
import GroupedMenuButtons from './GroupedMenuButtons';

const PluggableMenuSection = (props) => {
const { name, maxSizeBeforeCollapse = 3, params = {}, ...rest } = props;
return (
<Pluggable name={name} {...rest}>
{(pluggables) => (
<GroupedMenuButtons
items={pluggables}
maxSizeBeforeCollapse={maxSizeBeforeCollapse}
params={params}
className={name.split(':').shift()}
/>
)}
</Pluggable>
);
};

export default PluggableMenuSection;
Loading