@plone/volto 18.0.0-alpha.32 → 18.0.0-alpha.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/locales/ca/LC_MESSAGES/volto.po +17 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +17 -0
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +17 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +17 -0
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +17 -0
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +17 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +17 -0
- package/locales/fr.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +17 -0
- package/locales/hi.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +17 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +17 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +17 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +17 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +17 -0
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +17 -0
- package/locales/ro.json +1 -1
- package/locales/volto.pot +18 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +17 -0
- package/locales/zh_CN.json +1 -1
- package/package.json +10 -5
- package/razzle.config.js +58 -0
- package/src/actions/form/form.js +18 -2
- package/src/actions/index.js +1 -1
- package/src/components/manage/BlockChooser/BlockChooserSearch.jsx +1 -0
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +157 -81
- package/src/components/manage/Blocks/Block/BlocksForm.test.jsx +8 -0
- package/src/components/manage/Blocks/Block/Edit.jsx +37 -4
- package/src/components/manage/Blocks/Block/Order/Item.jsx +122 -0
- package/src/components/manage/Blocks/Block/Order/Order.jsx +367 -0
- package/src/components/manage/Blocks/Block/Order/SortableItem.jsx +58 -0
- package/src/components/manage/Blocks/Block/Order/utilities.js +113 -0
- package/src/components/manage/Blocks/Container/Edit.jsx +1 -0
- package/src/components/manage/Blocks/Grid/Edit.jsx +6 -4
- package/src/components/manage/Blocks/Image/schema.js +2 -0
- package/src/components/manage/Controlpanels/Relations/RelationsListing.jsx +1 -1
- package/src/components/manage/Controlpanels/Relations/RelationsMatrix.jsx +1 -1
- package/src/components/manage/Form/Form.jsx +159 -151
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +158 -65
- package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +125 -71
- package/src/components/manage/Sidebar/Sidebar.jsx +28 -1
- package/src/components/manage/Widgets/IdWidget.jsx +138 -203
- package/src/components/manage/Widgets/InternalUrlWidget.jsx +10 -14
- package/src/components/manage/Widgets/TextWidget.jsx +92 -124
- package/src/components/manage/Widgets/TextWidget.stories.jsx +14 -3
- package/src/components/theme/App/App.jsx +5 -0
- package/src/components/theme/Footer/Footer.jsx +1 -0
- package/src/components/theme/FormattedDate/FormattedDate.jsx +13 -1
- package/src/components/theme/Icon/Icon.jsx +4 -4
- package/src/components/theme/Navigation/Navigation.jsx +1 -1
- package/src/components/theme/SlotRenderer/SlotRenderer.tsx +3 -4
- package/src/components/theme/View/View.jsx +1 -1
- package/src/config/Loadables.jsx +18 -0
- package/src/config/server.js +0 -1
- package/src/constants/ActionTypes.js +1 -0
- package/src/express-middleware/static.js +37 -19
- package/src/helpers/Blocks/Blocks.js +182 -1
- package/src/helpers/Blocks/Blocks.test.js +136 -0
- package/src/helpers/Html/Html.jsx +6 -8
- package/src/helpers/Slots/index.tsx +12 -5
- package/src/helpers/index.js +4 -0
- package/src/reducers/form/form.js +18 -1
- package/src/reducers/form/form.test.js +15 -1
- package/src/server.jsx +34 -36
- package/theme/themes/pastanaga/extras/blocks.less +7 -6
- package/theme/themes/pastanaga/extras/objectbrowser-widget.less +83 -0
- package/theme/themes/pastanaga/extras/sidebar.less +145 -0
- package/theme/themes/pastanaga/extras/widgets.less +19 -0
- package/types/actions/form/form.d.ts +8 -1
- package/types/actions/index.d.ts +1 -1
- package/types/components/manage/Blocks/Block/Order/Item.d.ts +2 -0
- package/types/components/manage/Blocks/Block/Order/Order.d.ts +13 -0
- package/types/components/manage/Blocks/Block/Order/SortableItem.d.ts +9 -0
- package/types/components/manage/Blocks/Block/Order/utilities.d.ts +9 -0
- package/types/components/manage/Blocks/Image/schema.d.ts +2 -0
- package/types/components/manage/Sidebar/ObjectBrowserNav.d.ts +2 -1
- package/types/components/manage/Widgets/IdWidget.d.ts +54 -2
- package/types/components/manage/Widgets/TextWidget.d.ts +54 -5
- package/types/components/manage/Widgets/TextWidget.stories.d.ts +13 -1
- package/types/components/manage/Widgets/index.d.ts +2 -2
- package/types/components/theme/Icon/Icon.d.ts +8 -8
- package/types/config/Loadables.d.ts +15 -162
- package/types/config/Widgets.d.ts +1 -1
- package/types/config/server.d.ts +0 -3
- package/types/constants/ActionTypes.d.ts +1 -0
- package/types/helpers/Blocks/Blocks.d.ts +13 -0
- package/types/helpers/Slots/index.d.ts +5 -3
- package/types/helpers/index.d.ts +2 -2
- package/types/start-client.d.ts +1 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import TextWidget from './TextWidget';
|
|
3
2
|
import WidgetStory from './story';
|
|
4
3
|
|
|
@@ -6,7 +5,10 @@ export const Text = WidgetStory.bind({
|
|
|
6
5
|
props: { id: 'text', title: 'Text' },
|
|
7
6
|
widget: TextWidget,
|
|
8
7
|
});
|
|
9
|
-
|
|
8
|
+
Text.args = {
|
|
9
|
+
description: 'description',
|
|
10
|
+
placeholder: 'placeholder',
|
|
11
|
+
};
|
|
10
12
|
export default {
|
|
11
13
|
title: 'Edit Widgets/Text',
|
|
12
14
|
component: TextWidget,
|
|
@@ -17,5 +19,14 @@ export default {
|
|
|
17
19
|
</div>
|
|
18
20
|
),
|
|
19
21
|
],
|
|
20
|
-
argTypes: {
|
|
22
|
+
argTypes: {
|
|
23
|
+
description: {
|
|
24
|
+
control: 'text',
|
|
25
|
+
description: 'description',
|
|
26
|
+
},
|
|
27
|
+
placeholder: {
|
|
28
|
+
control: 'text',
|
|
29
|
+
description: 'placeholder',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
21
32
|
};
|
|
@@ -151,6 +151,11 @@ export class App extends Component {
|
|
|
151
151
|
[trim(join(split(this.props.pathname, '/'), ' section-'))]:
|
|
152
152
|
this.props.pathname !== '/',
|
|
153
153
|
siteroot: this.props.pathname === '/',
|
|
154
|
+
[`is-adding-contenttype-${decodeURIComponent(
|
|
155
|
+
this.props.location?.search?.replace('?type=', ''),
|
|
156
|
+
)
|
|
157
|
+
.replace(' ', '')
|
|
158
|
+
.toLowerCase()}`]: this.props.location?.search,
|
|
154
159
|
'is-authenticated': !!this.props.token,
|
|
155
160
|
'is-anonymous': !this.props.token,
|
|
156
161
|
'cms-ui': isCmsUI,
|
|
@@ -18,13 +18,25 @@ const FormattedDate = ({
|
|
|
18
18
|
const language = useSelector((state) => locale || state.intl.locale);
|
|
19
19
|
const toDate = (d) => (typeof d === 'string' ? new Date(d) : d);
|
|
20
20
|
const args = { date, long, includeTime, format, locale: language };
|
|
21
|
+
const new_date = new Date(toDate(date));
|
|
22
|
+
// Dat check taken from https://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript#1353711
|
|
23
|
+
if (Object.prototype.toString.call(new_date) === '[object Date]') {
|
|
24
|
+
// it is a date
|
|
25
|
+
if (isNaN(new_date)) {
|
|
26
|
+
// date object is not valid
|
|
27
|
+
return <span>bad date</span>;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
// not a date object
|
|
31
|
+
return <span>not a date</span>;
|
|
32
|
+
}
|
|
21
33
|
|
|
22
34
|
return (
|
|
23
35
|
<time
|
|
24
36
|
className={className}
|
|
25
37
|
dateTime={date}
|
|
26
38
|
title={new Intl.DateTimeFormat(language, long_date_format)
|
|
27
|
-
.format(
|
|
39
|
+
.format(new_date)
|
|
28
40
|
.replace('\u202F', ' ')}
|
|
29
41
|
>
|
|
30
42
|
{children
|
|
@@ -12,10 +12,10 @@ const defaultSize = '36px';
|
|
|
12
12
|
* @function Field
|
|
13
13
|
* @param {Object} props Component properties.
|
|
14
14
|
* @param {string} props.name Name source object.
|
|
15
|
-
* @param {string} props.size Size of the Icon (in px).
|
|
16
|
-
* @param {string} props.color Color of the Icon.
|
|
17
|
-
* @param {string} props.className className to add to the component.
|
|
18
|
-
* @param {string} props.title Title (a11y).
|
|
15
|
+
* @param {string} [props.size] Size of the Icon (in px).
|
|
16
|
+
* @param {string} [props.color] Color of the Icon.
|
|
17
|
+
* @param {string} [props.className] className to add to the component.
|
|
18
|
+
* @param {string} [props.title] Title (a11y).
|
|
19
19
|
* @returns {string} Markup of the component.
|
|
20
20
|
*
|
|
21
21
|
* Use:
|
|
@@ -49,7 +49,7 @@ const Navigation = (props) => {
|
|
|
49
49
|
setisMobileMenuOpen(false);
|
|
50
50
|
};
|
|
51
51
|
return (
|
|
52
|
-
<nav className="navigation" id="navigation" aria-label="Site">
|
|
52
|
+
<nav className="navigation" id="navigation" aria-label="Site" tabIndex="-1">
|
|
53
53
|
{items?.length ? (
|
|
54
54
|
<div className="hamburger-wrapper mobile tablet only">
|
|
55
55
|
<button
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useLocation } from 'react-router-dom';
|
|
2
2
|
import config from '@plone/volto/registry';
|
|
3
|
-
|
|
4
3
|
import type { Content } from '@plone/types';
|
|
5
4
|
|
|
6
5
|
/*
|
|
@@ -17,11 +16,11 @@ const SlotRenderer = ({
|
|
|
17
16
|
content: Content;
|
|
18
17
|
navRoot?: Content;
|
|
19
18
|
}) => {
|
|
20
|
-
const
|
|
19
|
+
const location = useLocation();
|
|
21
20
|
|
|
22
21
|
let slots = config.getSlot(name, {
|
|
23
22
|
content,
|
|
24
|
-
|
|
23
|
+
location: location as any, // Since we are using an older version of history, we need to cast it to any
|
|
25
24
|
// This is to cover the use case while adding a new content and we don't have
|
|
26
25
|
// have the navRoot information in the initial content. This will be
|
|
27
26
|
// useful for SlotRenderers rendered in the `Add` route.
|
|
@@ -48,7 +47,7 @@ const SlotRenderer = ({
|
|
|
48
47
|
<SlotComponent
|
|
49
48
|
key={name}
|
|
50
49
|
content={content}
|
|
51
|
-
|
|
50
|
+
location={location}
|
|
52
51
|
navRoot={navRoot}
|
|
53
52
|
/>
|
|
54
53
|
);
|
|
@@ -237,7 +237,7 @@ class View extends Component {
|
|
|
237
237
|
this.getViewByLayout() || this.getViewByType() || this.getViewDefault();
|
|
238
238
|
|
|
239
239
|
return (
|
|
240
|
-
<div id="view">
|
|
240
|
+
<div id="view" tabIndex="-1">
|
|
241
241
|
<ContentMetadataTags content={this.props.content} />
|
|
242
242
|
{/* Body class if displayName in component is set */}
|
|
243
243
|
<BodyClass
|
package/src/config/Loadables.jsx
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import loadable from '@loadable/component';
|
|
2
2
|
|
|
3
|
+
// This is to make happy the types declaration extractor (tsc) that is not able to
|
|
4
|
+
// extract one private method the types from `@dnd-kit` library:
|
|
5
|
+
// error TS9006: Declaration emit for this file requires using private name
|
|
6
|
+
// 'DefaultDropAnimationSideEffectsOptions' from module '".../node_modules/@dnd-kit/core/dist/components/DragOverlay/hooks/useDropAnimation"'.
|
|
7
|
+
// An explicit type annotation may unblock declaration emit
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} LoadableLib
|
|
11
|
+
* @property {() => Promise<any>} import
|
|
12
|
+
* @property {Object} [options]
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @type {{ [key: string]: LoadableLib }}
|
|
17
|
+
*/
|
|
3
18
|
export const loadables = {
|
|
4
19
|
prettierStandalone: loadable.lib(() => import('prettier/standalone')),
|
|
5
20
|
prettierParserHtml: loadable.lib(() => import('prettier/plugins/html')),
|
|
@@ -35,4 +50,7 @@ export const loadables = {
|
|
|
35
50
|
reactDndHtml5Backend: loadable.lib(() => import('react-dnd-html5-backend')),
|
|
36
51
|
reactBeautifulDnd: loadable.lib(() => import('react-beautiful-dnd')),
|
|
37
52
|
rrule: loadable.lib(() => import('rrule')),
|
|
53
|
+
dndKitCore: loadable.lib(() => import('@dnd-kit/core')),
|
|
54
|
+
dndKitSortable: loadable.lib(() => import('@dnd-kit/sortable')),
|
|
55
|
+
dndKitUtilities: loadable.lib(() => import('@dnd-kit/utilities')),
|
|
38
56
|
};
|
package/src/config/server.js
CHANGED
|
@@ -145,4 +145,5 @@ export const RESET_LOGIN_REQUEST = 'RESET_LOGIN_REQUEST';
|
|
|
145
145
|
export const GET_SITE = 'GET_SITE';
|
|
146
146
|
export const GET_NAVROOT = 'GET_NAVROOT';
|
|
147
147
|
export const SET_FORM_DATA = 'SET_FORM_DATA';
|
|
148
|
+
export const SET_UI_STATE = 'SET_UI_STATE';
|
|
148
149
|
export const UPDATE_UPLOADED_FILES = 'UPDATE_UPLOADED_FILES';
|
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import AddonConfigurationRegistry from '@plone/registry/src/addon-registry';
|
|
3
4
|
import config from '@plone/volto/registry';
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
const projectRootPath = path.resolve('.');
|
|
7
|
+
const registry = new AddonConfigurationRegistry(projectRootPath);
|
|
8
|
+
|
|
9
|
+
const staticDirectory = () => {
|
|
10
|
+
if (process.env.BUILD_DIR) {
|
|
11
|
+
return path.join(process.env.BUILD_DIR, 'public');
|
|
12
|
+
}
|
|
13
|
+
// Only for development, when Volto detects that it's working on itself (not an
|
|
14
|
+
// old fashioned Volto project), there are add-ons (so it's the new setup) then
|
|
15
|
+
// point to the public folder in the root of the setup, instead of the inner Volto
|
|
16
|
+
// public folder.
|
|
17
|
+
if (
|
|
18
|
+
process.env.NODE_ENV !== 'production' &&
|
|
19
|
+
!registry.isVoltoProject &&
|
|
20
|
+
registry.addonNames.length > 0
|
|
21
|
+
) {
|
|
22
|
+
return path.join(projectRootPath, '../../../public');
|
|
23
|
+
}
|
|
24
|
+
// Is always set (Razzle does it)
|
|
25
|
+
return process.env.RAZZLE_PUBLIC_DIR;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const staticMiddlewareFn = express.static(staticDirectory(), {
|
|
29
|
+
setHeaders: function (res, path) {
|
|
30
|
+
const pathLib = require('path');
|
|
31
|
+
const base = pathLib.resolve(process.env.RAZZLE_PUBLIC_DIR);
|
|
32
|
+
const relpath = path.substr(base.length);
|
|
33
|
+
config.settings.serverConfig.staticFiles.some((elem) => {
|
|
34
|
+
if (relpath.match(elem.match)) {
|
|
35
|
+
for (const name in elem.headers) {
|
|
36
|
+
res.setHeader(name, elem.headers[name] || 'undefined');
|
|
20
37
|
}
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
});
|
|
24
42
|
},
|
|
25
|
-
);
|
|
43
|
+
});
|
|
26
44
|
|
|
27
45
|
export default function staticsMiddleware() {
|
|
28
46
|
const middleware = express.Router();
|
|
@@ -7,7 +7,11 @@ import { omit, without, endsWith, find, isObject, keys, merge } from 'lodash';
|
|
|
7
7
|
import move from 'lodash-move';
|
|
8
8
|
import { v4 as uuid } from 'uuid';
|
|
9
9
|
import config from '@plone/volto/registry';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
applySchemaEnhancer,
|
|
12
|
+
insertInArray,
|
|
13
|
+
removeFromArray,
|
|
14
|
+
} from '@plone/volto/helpers';
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Get blocks field.
|
|
@@ -714,6 +718,183 @@ export function findBlocks(blocks, types, result = []) {
|
|
|
714
718
|
return result;
|
|
715
719
|
}
|
|
716
720
|
|
|
721
|
+
export const getBlocksHierarchy = (properties) => {
|
|
722
|
+
const blocksFieldName = getBlocksFieldname(properties);
|
|
723
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(properties);
|
|
724
|
+
return properties[blocksLayoutFieldname]?.items?.map((n) => ({
|
|
725
|
+
id: n,
|
|
726
|
+
title: properties[blocksFieldName][n]?.['@type'],
|
|
727
|
+
data: properties[blocksFieldName][n],
|
|
728
|
+
children:
|
|
729
|
+
properties[blocksFieldName][n]?.['@type'] === 'gridBlock'
|
|
730
|
+
? getBlocksHierarchy(properties[blocksFieldName][n])
|
|
731
|
+
: [],
|
|
732
|
+
}));
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Move block to different location index within blocks_layout
|
|
737
|
+
* @function moveBlock
|
|
738
|
+
* @param {Object} formData Form data
|
|
739
|
+
* @param {number} source index within form blocks_layout items
|
|
740
|
+
* @param {number} destination index within form blocks_layout items
|
|
741
|
+
* @return {Object} New form data
|
|
742
|
+
*/
|
|
743
|
+
export function moveBlockEnhanced(formData, { source, destination }) {
|
|
744
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
|
745
|
+
const blocksFieldName = getBlocksFieldname(formData);
|
|
746
|
+
|
|
747
|
+
// If either one of source and destination are present
|
|
748
|
+
// (Moves intra-container or container <-> main container)
|
|
749
|
+
if (source.parent || destination.parent) {
|
|
750
|
+
// Move from a container to the main container
|
|
751
|
+
if (source.parent && !destination.parent) {
|
|
752
|
+
let clonedFormData = { ...formData };
|
|
753
|
+
|
|
754
|
+
clonedFormData[blocksFieldName][source.id] =
|
|
755
|
+
formData[blocksFieldName][source.parent][blocksFieldName][source.id];
|
|
756
|
+
|
|
757
|
+
clonedFormData[blocksLayoutFieldname].items = insertInArray(
|
|
758
|
+
formData[blocksLayoutFieldname].items,
|
|
759
|
+
source.id,
|
|
760
|
+
destination.position,
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
// Remove the source block from the source parent
|
|
764
|
+
const sourceContainer = findContainer(clonedFormData, {
|
|
765
|
+
containerId: source.parent,
|
|
766
|
+
});
|
|
767
|
+
delete sourceContainer[blocksFieldName][source.id];
|
|
768
|
+
sourceContainer[blocksLayoutFieldname].items = removeFromArray(
|
|
769
|
+
sourceContainer[blocksLayoutFieldname].items,
|
|
770
|
+
source.position,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
return clonedFormData;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Move from the main container to an inner container
|
|
777
|
+
if (!source.parent && destination.parent) {
|
|
778
|
+
let clonedFormData = { ...formData };
|
|
779
|
+
|
|
780
|
+
const destinationContainer = findContainer(clonedFormData, {
|
|
781
|
+
containerId: destination.parent,
|
|
782
|
+
});
|
|
783
|
+
destinationContainer[blocksFieldName][source.id] =
|
|
784
|
+
clonedFormData[blocksFieldName][source.id];
|
|
785
|
+
destinationContainer[blocksLayoutFieldname].items = insertInArray(
|
|
786
|
+
destinationContainer[blocksLayoutFieldname].items,
|
|
787
|
+
source.id,
|
|
788
|
+
destination.position,
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
// Remove the source block from the source parent
|
|
792
|
+
delete clonedFormData[blocksFieldName][source.id];
|
|
793
|
+
clonedFormData[blocksLayoutFieldname].items = removeFromArray(
|
|
794
|
+
clonedFormData[blocksLayoutFieldname].items,
|
|
795
|
+
source.position,
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
return clonedFormData;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Move within the same container (except moves within the main container)
|
|
802
|
+
if (source.parent === destination.parent) {
|
|
803
|
+
let clonedFormData = { ...formData };
|
|
804
|
+
|
|
805
|
+
const destinationContainer = findContainer(clonedFormData, {
|
|
806
|
+
containerId: destination.parent,
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
destinationContainer[blocksLayoutFieldname].items = move(
|
|
810
|
+
destinationContainer[blocksLayoutFieldname].items,
|
|
811
|
+
source.position,
|
|
812
|
+
destination.position,
|
|
813
|
+
);
|
|
814
|
+
return clonedFormData;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Move between containers
|
|
818
|
+
if (source.parent !== destination.parent) {
|
|
819
|
+
let clonedFormData = { ...formData };
|
|
820
|
+
|
|
821
|
+
const destinationContainer = findContainer(clonedFormData, {
|
|
822
|
+
containerId: destination.parent,
|
|
823
|
+
});
|
|
824
|
+
destinationContainer[blocksFieldName][source.id] =
|
|
825
|
+
formData[blocksFieldName][source.parent][blocksFieldName][source.id];
|
|
826
|
+
|
|
827
|
+
destinationContainer[blocksLayoutFieldname].items = insertInArray(
|
|
828
|
+
destinationContainer[blocksLayoutFieldname].items,
|
|
829
|
+
source.id,
|
|
830
|
+
destination.position,
|
|
831
|
+
);
|
|
832
|
+
|
|
833
|
+
// Remove the source block from the source parent
|
|
834
|
+
const sourceContainer = findContainer(clonedFormData, {
|
|
835
|
+
containerId: source.parent,
|
|
836
|
+
});
|
|
837
|
+
delete sourceContainer[blocksFieldName][source.id];
|
|
838
|
+
sourceContainer[blocksLayoutFieldname].items = removeFromArray(
|
|
839
|
+
sourceContainer[blocksLayoutFieldname].items,
|
|
840
|
+
source.position,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
return clonedFormData;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Default catch all, no source/destination parent specified
|
|
848
|
+
// Move within the main container
|
|
849
|
+
return {
|
|
850
|
+
...formData,
|
|
851
|
+
[blocksLayoutFieldname]: {
|
|
852
|
+
items: move(
|
|
853
|
+
formData[blocksLayoutFieldname].items,
|
|
854
|
+
source.position,
|
|
855
|
+
destination.position,
|
|
856
|
+
),
|
|
857
|
+
},
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Finds the container with the specified containerId in the given formData.
|
|
863
|
+
*
|
|
864
|
+
* @param {object} formData - The form data object.
|
|
865
|
+
* @param {object} options - The options object.
|
|
866
|
+
* @param {string} options.containerId - The ID of the container to find.
|
|
867
|
+
* @returns {object|undefined} - The container object if found, otherwise undefined.
|
|
868
|
+
*/
|
|
869
|
+
export const findContainer = (formData, { containerId }) => {
|
|
870
|
+
if (
|
|
871
|
+
formData.blocks[containerId] &&
|
|
872
|
+
Object.keys(formData.blocks[containerId]).includes('blocks') &&
|
|
873
|
+
Object.keys(formData.blocks[containerId]).includes('blocks_layout')
|
|
874
|
+
) {
|
|
875
|
+
return formData.blocks[containerId];
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
let container;
|
|
879
|
+
Object.keys(formData.blocks).every((blockId) => {
|
|
880
|
+
const block = formData.blocks[blockId];
|
|
881
|
+
if (
|
|
882
|
+
formData.blocks[blockId] &&
|
|
883
|
+
Object.keys(formData.blocks[blockId]).includes('blocks') &&
|
|
884
|
+
Object.keys(formData.blocks[blockId]).includes('blocks_layout')
|
|
885
|
+
) {
|
|
886
|
+
container = findContainer(block, { containerId });
|
|
887
|
+
}
|
|
888
|
+
if (container) {
|
|
889
|
+
return false;
|
|
890
|
+
} else {
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
return container;
|
|
896
|
+
};
|
|
897
|
+
|
|
717
898
|
const _dummyIntl = {
|
|
718
899
|
formatMessage() {},
|
|
719
900
|
};
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getPreviousNextBlock,
|
|
23
23
|
blocksFormGenerator,
|
|
24
24
|
findBlocks,
|
|
25
|
+
findContainer,
|
|
25
26
|
} from './Blocks';
|
|
26
27
|
|
|
27
28
|
import config from '@plone/volto/registry';
|
|
@@ -1507,3 +1508,138 @@ describe('findBlocks', () => {
|
|
|
1507
1508
|
expect(findBlocks(blocks, types)).toStrictEqual(['3', '4', '8', '9']);
|
|
1508
1509
|
});
|
|
1509
1510
|
});
|
|
1511
|
+
|
|
1512
|
+
describe('findContainer', () => {
|
|
1513
|
+
const blocksData = { blocks: {}, blocks_layout: { items: [] } };
|
|
1514
|
+
|
|
1515
|
+
it('Get a container in the first level (main block container)', () => {
|
|
1516
|
+
const formData = {
|
|
1517
|
+
title: 'Example',
|
|
1518
|
+
blocks: {
|
|
1519
|
+
1: { title: 'title', '@type': 'title' },
|
|
1520
|
+
2: { title: 'an image', '@type': 'image' },
|
|
1521
|
+
3: { title: 'description', '@type': 'description' },
|
|
1522
|
+
4: { title: 'a container', '@type': 'container', ...blocksData },
|
|
1523
|
+
},
|
|
1524
|
+
blocks_layout: {
|
|
1525
|
+
items: ['1', '2', '3', '4'],
|
|
1526
|
+
},
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
expect(findContainer(formData, { containerId: '4' })).toStrictEqual({
|
|
1530
|
+
title: 'a container',
|
|
1531
|
+
'@type': 'container',
|
|
1532
|
+
...blocksData,
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
it('Get a container in the second level', () => {
|
|
1537
|
+
const formData = {
|
|
1538
|
+
title: 'Example',
|
|
1539
|
+
blocks: {
|
|
1540
|
+
1: { title: 'title', '@type': 'title' },
|
|
1541
|
+
2: { title: 'an image', '@type': 'image' },
|
|
1542
|
+
3: { title: 'description', '@type': 'description' },
|
|
1543
|
+
4: {
|
|
1544
|
+
title: 'a container',
|
|
1545
|
+
'@type': 'container',
|
|
1546
|
+
blocks: {
|
|
1547
|
+
1: { title: 'title', '@type': 'title' },
|
|
1548
|
+
2: { title: 'an image', '@type': 'image' },
|
|
1549
|
+
'second-level': {
|
|
1550
|
+
title: 'a container',
|
|
1551
|
+
'@type': 'container',
|
|
1552
|
+
blocks: {},
|
|
1553
|
+
blocks_layout: { items: [] },
|
|
1554
|
+
},
|
|
1555
|
+
},
|
|
1556
|
+
blocks_layout: { items: [1, 2, 'second-level'] },
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
blocks_layout: {
|
|
1560
|
+
items: ['1', '2', '3', '4'],
|
|
1561
|
+
},
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
expect(
|
|
1565
|
+
findContainer(formData, { containerId: 'second-level' }),
|
|
1566
|
+
).toStrictEqual({
|
|
1567
|
+
title: 'a container',
|
|
1568
|
+
'@type': 'container',
|
|
1569
|
+
blocks: {},
|
|
1570
|
+
blocks_layout: { items: [] },
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
|
|
1574
|
+
it('Get a container in the third level', () => {
|
|
1575
|
+
const formData = {
|
|
1576
|
+
title: 'Example',
|
|
1577
|
+
blocks: {
|
|
1578
|
+
1: { title: 'title', '@type': 'title' },
|
|
1579
|
+
2: { title: 'an image', '@type': 'image' },
|
|
1580
|
+
3: { title: 'description', '@type': 'description' },
|
|
1581
|
+
4: {
|
|
1582
|
+
title: 'a container',
|
|
1583
|
+
'@type': 'container',
|
|
1584
|
+
blocks: {
|
|
1585
|
+
1: { title: 'title', '@type': 'title' },
|
|
1586
|
+
2: { title: 'an image', '@type': 'image' },
|
|
1587
|
+
'second-level': {
|
|
1588
|
+
title: 'a second level container',
|
|
1589
|
+
'@type': 'container',
|
|
1590
|
+
blocks: {
|
|
1591
|
+
'third-level': {
|
|
1592
|
+
title: 'a third level container',
|
|
1593
|
+
'@type': 'container',
|
|
1594
|
+
blocks: {},
|
|
1595
|
+
blocks_layout: { items: [] },
|
|
1596
|
+
},
|
|
1597
|
+
},
|
|
1598
|
+
blocks_layout: { items: ['third-level'] },
|
|
1599
|
+
},
|
|
1600
|
+
},
|
|
1601
|
+
blocks_layout: { items: [1, 2, 'second-level'] },
|
|
1602
|
+
},
|
|
1603
|
+
},
|
|
1604
|
+
blocks_layout: {
|
|
1605
|
+
items: ['1', '2', '3', '4'],
|
|
1606
|
+
},
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
expect(
|
|
1610
|
+
findContainer(formData, { containerId: 'third-level' }),
|
|
1611
|
+
).toStrictEqual({
|
|
1612
|
+
title: 'a third level container',
|
|
1613
|
+
'@type': 'container',
|
|
1614
|
+
blocks: {},
|
|
1615
|
+
blocks_layout: { items: [] },
|
|
1616
|
+
});
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
describe('findContainer then modify it', () => {
|
|
1620
|
+
const blocksData = { blocks: {}, blocks_layout: { items: [] } };
|
|
1621
|
+
|
|
1622
|
+
it('Get and modifies a container in the first level (main block container)', () => {
|
|
1623
|
+
const formData = {
|
|
1624
|
+
title: 'Example',
|
|
1625
|
+
blocks: {
|
|
1626
|
+
1: { title: 'title', '@type': 'title' },
|
|
1627
|
+
2: { title: 'an image', '@type': 'image' },
|
|
1628
|
+
3: { title: 'description', '@type': 'description' },
|
|
1629
|
+
4: { title: 'a container', '@type': 'container', ...blocksData },
|
|
1630
|
+
},
|
|
1631
|
+
blocks_layout: {
|
|
1632
|
+
items: ['1', '2', '3', '4'],
|
|
1633
|
+
},
|
|
1634
|
+
};
|
|
1635
|
+
|
|
1636
|
+
const container = findContainer(formData, { containerId: '4' });
|
|
1637
|
+
container.title = 'Modified the title of the container';
|
|
1638
|
+
expect(findContainer(formData, { containerId: '4' })).toStrictEqual({
|
|
1639
|
+
title: 'Modified the title of the container',
|
|
1640
|
+
'@type': 'container',
|
|
1641
|
+
...blocksData,
|
|
1642
|
+
});
|
|
1643
|
+
});
|
|
1644
|
+
});
|
|
1645
|
+
});
|
|
@@ -187,14 +187,12 @@ class Html extends Component {
|
|
|
187
187
|
charSet="UTF-8"
|
|
188
188
|
/>
|
|
189
189
|
{/* Add the crossorigin while in development */}
|
|
190
|
-
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
)
|
|
197
|
-
: ''}
|
|
190
|
+
{extractor.getScriptElements().map((elem) =>
|
|
191
|
+
React.cloneElement(elem, {
|
|
192
|
+
crossOrigin:
|
|
193
|
+
process.env.NODE_ENV === 'production' ? undefined : 'true',
|
|
194
|
+
}),
|
|
195
|
+
)}
|
|
198
196
|
</body>
|
|
199
197
|
</html>
|
|
200
198
|
);
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import type { Content } from '@plone/types';
|
|
2
1
|
import { matchPath } from 'react-router-dom';
|
|
2
|
+
import type { Content } from '@plone/types';
|
|
3
|
+
import type { Location } from 'history';
|
|
3
4
|
|
|
4
5
|
export function RouteCondition(path: string, exact?: boolean) {
|
|
5
|
-
return ({
|
|
6
|
-
Boolean(matchPath(pathname, { path, exact }));
|
|
6
|
+
return ({ location }: { location: Location }) =>
|
|
7
|
+
Boolean(matchPath(location.pathname, { path, exact }));
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function ContentTypeCondition(contentType: string[]) {
|
|
10
|
-
return ({ content }: { content: Content }) =>
|
|
11
|
-
|
|
11
|
+
return ({ content, location }: { content: Content; location: Location }) => {
|
|
12
|
+
return (
|
|
13
|
+
contentType.includes(content?.['@type']) ||
|
|
14
|
+
contentType.some((type) => {
|
|
15
|
+
return location.search.includes(`type=${encodeURIComponent(type)}`);
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
};
|
|
12
19
|
}
|
package/src/helpers/index.js
CHANGED
|
@@ -61,6 +61,8 @@ export {
|
|
|
61
61
|
buildStyleObjectFromData,
|
|
62
62
|
getPreviousNextBlock,
|
|
63
63
|
findBlocks,
|
|
64
|
+
getBlocksHierarchy,
|
|
65
|
+
moveBlockEnhanced,
|
|
64
66
|
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
65
67
|
export {
|
|
66
68
|
getSimpleDefaultBlocks,
|
|
@@ -98,6 +100,8 @@ export {
|
|
|
98
100
|
hasApiExpander,
|
|
99
101
|
replaceItemOfArray,
|
|
100
102
|
cloneDeepSchema,
|
|
103
|
+
insertInArray,
|
|
104
|
+
removeFromArray,
|
|
101
105
|
arrayRange,
|
|
102
106
|
reorderArray,
|
|
103
107
|
isInteractiveElement,
|