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

Show working copy links based on backend actions #6393

Merged
merged 11 commits into from
Jan 29, 2025
Merged
7 changes: 6 additions & 1 deletion docs/source/configuration/settings-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,12 @@ contentMetadataTagsImageField
The OpenGraph image that will represent this content item, will be used in the metadata HEAD tag as og:image for SEO purposes. Defaults to image. See the OpenGraph Protocol for more details.

hasWorkingCopySupport
This setting will enable working copy support in your site. You need to install the `plone.app.iterate` add-on in your Plone site in order to make it working.
```{deprecated} Volto 18.8.0
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
This setting is no longer used. Working copy support is now based on whether the `plone.app.iterate` add-on is installed in the backend.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
```

This setting will enable working copy support in your site. You need to install the `plone.app.iterate` add-on in your Plone site in order for it to work.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

controlpanels
Register a component as control panel.
Expand Down
27 changes: 16 additions & 11 deletions docs/source/configuration/workingcopy.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,7 @@ myst:

# Working copy support

Volto provide support for Plone's Working Copy feature. You need to install `plone.app.iterate` add-on in your Plone site that comes available by default. You can do that in Plone's control panel or using the `GenericSetup` facility.

## Volto configuration

You need to enable working copy support in Volto's configuration object:

```js
import config from '@plone/volto/registry'

config.settings.hasWorkingCopySupport = true;
```
Volto provides support for Plone's Working Copy feature. You need to install the `plone.app.iterate` add-on in your Plone site. You can do that in Plone's Add-ons control panel or using the `GenericSetup` facility.
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

## Features

Expand All @@ -29,3 +19,18 @@ Volto working copy support features include:
- Work on the working copy
- "Check in" the working copy by applying the changes into the original (baseline) object
- Cancel the working copy if required

## Volto configuration

```{deprecated} Volto 18.8.0
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

stevepiercy marked this conversation as resolved.
Show resolved Hide resolved
This setting is no longer used.
```

If you have an older version of volto, you also need to enable working copy support in Volto's configuration object:
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

```js
import config from '@plone/volto/registry'

config.settings.hasWorkingCopySupport = true;
```
6 changes: 0 additions & 6 deletions packages/coresandbox/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,6 @@ export const multilingualFixture = (config: ConfigType) => {
return config;
};

export const workingCopyFixture = (config: ConfigType) => {
config.settings.hasWorkingCopySupport = true;

return config;
};

// We extend the block types with the custom ones
declare module '@plone/types' {
export interface BlocksConfigData {
Expand Down
2 changes: 1 addition & 1 deletion packages/seven/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ working-copy-acceptance-backend-start: ## Start backend acceptance server for wo

.PHONY: working-copy-acceptance-frontend-prod-start
working-copy-acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode for working copy tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could unify this into the main tests then, but it does not hurt to have them separated.

ADDONS=@plone/volto-coresandbox:workingCopyFixture PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod
PLONE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod

.PHONY: working-copy-acceptance-test
working-copy-acceptance-test: ## Start Cypress in interactive mode for working copy tests
Expand Down
1 change: 1 addition & 0 deletions packages/seven/news/6393.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update Makefile. @davisagli
1 change: 1 addition & 0 deletions packages/types/news/6393.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove `hasWorkingCopySupport` setting. @davisagli
1 change: 0 additions & 1 deletion packages/types/src/config/Settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export interface SettingsConfig {

showSelfRegistration: boolean;
contentMetadataTagsImageField: string;
hasWorkingCopySupport: boolean;
maxUndoLevels: number;
addonsInfo: unknown;
workflowMapping: unknown;
Expand Down
2 changes: 1 addition & 1 deletion packages/volto/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ working-copy-acceptance-backend-start: ## Start backend acceptance server for wo

.PHONY: working-copy-acceptance-frontend-prod-start
working-copy-acceptance-frontend-prod-start: build-deps ## Start acceptance frontend in production mode for working copy tests
ADDONS=@plone/volto-coresandbox:workingCopyFixture RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod
RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod

.PHONY: working-copy-acceptance-test
working-copy-acceptance-test: ## Start Cypress in interactive mode for working copy tests
Expand Down
2 changes: 1 addition & 1 deletion packages/volto/cypress/tests/workingCopy/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('Working Copy Tests - Create', () => {
cy.findByText('View working copy');
});

it('Portal root does not have create option', function () {
it('Portal root has create option', function () {
cy.visit('/');
cy.get('#toolbar-more').click();
cy.get('.menu-more').contains('Create working copy').should('not.exist');
Expand Down
1 change: 1 addition & 0 deletions packages/volto/news/5284.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show the working copy actions (checkin/checkout) based on whether they are enabled in the backend, instead of the `hasWorkingCopySupport` setting. @wesleybl, @davisagli
230 changes: 113 additions & 117 deletions packages/volto/src/components/manage/Toolbar/More.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ const More = (props) => {
id: 'redirection',
});

const workingCopyCheckoutAction = find(actions.object_buttons, {
id: 'iterate_checkout',
});
const workingCopyCheckinAction = find(actions.object_buttons, {
id: 'iterate_checkin',
});

const dateOptions = {
year: 'numeric',
month: 'long',
Expand Down Expand Up @@ -320,125 +327,114 @@ const More = (props) => {
</>
)}
</Pluggable>
{config.settings.hasWorkingCopySupport &&
content['@type'] !== 'Plone Site' && (
<>
{!content.working_copy && (
<Plug pluggable="toolbar-more-manage-content" id="workingcopy">
<li>
<button
aria-label={intl.formatMessage(messages.CreateWorkingCopy)}
onClick={() => {
dispatch(createWorkingCopy(path)).then((response) => {
history.push(flattenToAppURL(response['@id']));
props.closeMenu();
});
}}
>
{intl.formatMessage(messages.CreateWorkingCopy)}
{workingCopyCheckoutAction && (
<Plug pluggable="toolbar-more-manage-content" id="workingcopy">
<li>
<button
aria-label={intl.formatMessage(messages.CreateWorkingCopy)}
onClick={() => {
dispatch(createWorkingCopy(path)).then((response) => {
history.push(flattenToAppURL(response['@id']));
props.closeMenu();
});
}}
>
{intl.formatMessage(messages.CreateWorkingCopy)}

<Icon name={rightArrowSVG} size="24px" />
</button>
</li>
</Plug>
)}
{content.working_copy && content.working_copy_of && (
<Plug pluggable="toolbar-more-manage-content" id="workingcopy">
<li>
<button
aria-label={intl.formatMessage(messages.applyWorkingCopy)}
onClick={() => {
dispatch(applyWorkingCopy(path)).then((response) => {
history.push(
flattenToAppURL(content.working_copy_of['@id']),
);
props.closeMenu();
toast.info(
<Toast
info
title={intl.formatMessage(
messages.workingAppliedTitle,
)}
content={intl.formatMessage(
messages.workingCopyAppliedBy,
{
creator: content.working_copy?.creator_name,
date: (
<FormattedDate
date={content.working_copy?.created}
format={dateOptions}
/>
),
},
)}
/>,
{
toastId: 'workingcopyapplyinfo',
autoClose: 10000,
},
);
});
}}
>
{intl.formatMessage(messages.applyWorkingCopy)}
<Icon name={rightArrowSVG} size="24px" />
</button>
</li>
</Plug>
)}
{workingCopyCheckinAction && (
<Plug pluggable="toolbar-more-manage-content" id="workingcopy">
<li>
<button
aria-label={intl.formatMessage(messages.applyWorkingCopy)}
onClick={() => {
dispatch(applyWorkingCopy(path)).then((response) => {
history.push(flattenToAppURL(content.working_copy_of['@id']));
props.closeMenu();
toast.info(
<Toast
info
title={intl.formatMessage(messages.workingAppliedTitle)}
content={intl.formatMessage(
messages.workingCopyAppliedBy,
{
creator: content.working_copy?.creator_name,
date: (
<FormattedDate
date={content.working_copy?.created}
format={dateOptions}
/>
),
},
)}
/>,
{
toastId: 'workingcopyapplyinfo',
autoClose: 10000,
},
);
});
}}
>
{intl.formatMessage(messages.applyWorkingCopy)}

<Icon
name={applySVG}
size="24px"
title={intl.formatMessage(messages.applyWorkingCopy)}
/>
</button>
</li>
<li>
<button
aria-label={intl.formatMessage(messages.removeWorkingCopy)}
onClick={() => {
dispatch(removeWorkingCopy(path)).then((response) => {
history.push(
flattenToAppURL(content.working_copy_of['@id']),
);
props.closeMenu();
toast.info(
<Toast
info
title={intl.formatMessage(
messages.workingCopyRemovedTitle,
)}
/>,
{
toastId: 'workingcopyremovednotice',
autoClose: 10000,
},
);
});
}}
>
{intl.formatMessage(messages.removeWorkingCopy)}
<Icon
name={removeSVG}
size="24px"
color="#e40166"
title={intl.formatMessage(messages.removeWorkingCopy)}
/>
</button>
</li>
</Plug>
)}
{content.working_copy && !content.working_copy_of && (
<Plug pluggable="toolbar-more-manage-content" id="workingcopy">
<li>
<Link
to={flattenToAppURL(content.working_copy['@id'])}
onClick={() => props.closeMenu()}
>
{intl.formatMessage(messages.viewWorkingCopy)}
<Icon name={rightArrowSVG} size="24px" />
</Link>
</li>
</Plug>
)}
</>
)}
<Icon
name={applySVG}
size="24px"
title={intl.formatMessage(messages.applyWorkingCopy)}
/>
</button>
</li>
<li>
<button
aria-label={intl.formatMessage(messages.removeWorkingCopy)}
onClick={() => {
dispatch(removeWorkingCopy(path)).then((response) => {
history.push(flattenToAppURL(content.working_copy_of['@id']));
props.closeMenu();
toast.info(
<Toast
info
title={intl.formatMessage(
messages.workingCopyRemovedTitle,
)}
/>,
{
toastId: 'workingcopyremovednotice',
autoClose: 10000,
},
);
});
}}
>
{intl.formatMessage(messages.removeWorkingCopy)}
<Icon
name={removeSVG}
size="24px"
color="#e40166"
title={intl.formatMessage(messages.removeWorkingCopy)}
/>
</button>
</li>
</Plug>
)}
{content.working_copy && !content.working_copy_of && (
<Plug pluggable="toolbar-more-manage-content" id="workingcopy">
<li>
<Link
to={flattenToAppURL(content.working_copy['@id'])}
onClick={() => props.closeMenu()}
>
{intl.formatMessage(messages.viewWorkingCopy)}
<Icon name={rightArrowSVG} size="24px" />
</Link>
</li>
</Plug>
)}
{editAction && config.settings.isMultilingual && (
<Plug pluggable="toolbar-more-manage-content" id="multilingual">
<li>
Expand Down
24 changes: 0 additions & 24 deletions packages/volto/src/components/manage/Toolbar/More.test.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React from 'react';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-intl-redux';
import { MemoryRouter } from 'react-router-dom';
import { PluggablesProvider } from '@plone/volto/components/manage/Pluggable';
import { waitFor, render } from '@testing-library/react';
import config from '@plone/volto/registry';

import More from './More';

Expand Down Expand Up @@ -162,26 +160,4 @@ describe('Toolbar More component', () => {
await waitFor(() => {});
expect(container).toMatchSnapshot();
});
it('renders a Toolbar More component with manage content (working copy)', async () => {
config.settings.hasWorkingCopySupport = true;

const { container } = render(
<PluggablesProvider>
<Provider store={store}>
<MemoryRouter>
<More
pathname="/blah"
loadComponent={() => {}}
theToolbar={{
current: { getBoundingClientRect: () => ({ width: '320' }) },
}}
closeMenu={() => {}}
/>
</MemoryRouter>
</Provider>
</PluggablesProvider>,
);
await waitFor(() => {});
expect(container).toMatchSnapshot();
});
});
Loading
Loading