@plone/volto 17.0.0-alpha.12 → 17.0.0-alpha.14
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/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +62 -0
- package/README.md +2 -2
- package/docker-compose.yml +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +10 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +11 -1
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +10 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +53 -43
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +10 -0
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +10 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +10 -0
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +10 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +10 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +10 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +10 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +10 -0
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +10 -0
- package/locales/ro.json +1 -1
- package/locales/volto.pot +11 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +10 -0
- package/locales/zh_CN.json +1 -1
- package/package.json +2 -1
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +8 -3
- package/packages/volto-slate/src/blocks/Text/TextBlockView.jsx +20 -16
- package/packages/volto-slate/src/blocks/Text/extensions/withDeserializers.js +3 -1
- package/packages/volto-slate/src/editor/config.jsx +5 -4
- package/packages/volto-slate/src/editor/less/slate.less +28 -0
- package/packages/volto-slate/src/editor/render.jsx +68 -8
- package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +6 -1
- package/src/components/manage/Blocks/Image/Edit.jsx +11 -7
- package/src/components/manage/Blocks/Listing/ListingBody.jsx +30 -8
- package/src/components/manage/Blocks/Title/View.jsx +15 -5
- package/src/components/manage/Blocks/Title/View.test.jsx +16 -1
- package/src/components/manage/Blocks/ToC/View.jsx +8 -1
- package/src/components/manage/Blocks/ToC/variations/DefaultTocRenderer.jsx +17 -4
- package/src/components/manage/Blocks/ToC/variations/HorizontalMenu.jsx +6 -2
- package/src/components/manage/Contents/ContentsUploadModal.jsx +10 -5
- package/src/components/manage/Toast/Toast.jsx +1 -1
- package/src/components/manage/Widgets/FileWidget.jsx +2 -1
- package/src/components/theme/Anontools/Anontools.jsx +45 -72
- package/src/components/theme/Anontools/Anontools.test.jsx +16 -2
- package/src/config/index.js +1 -0
- package/src/helpers/Extensions/withBlockSchemaEnhancer.js +15 -11
- package/src/helpers/Extensions/withBlockSchemaEnhancer.test.js +145 -0
- package/src/helpers/FormValidation/FormValidation.js +29 -0
- package/src/helpers/MessageLabels/MessageLabels.js +8 -0
- package/src/helpers/ScrollToTop/ScrollToTop.jsx +5 -3
- package/src/helpers/index.js +3 -1
- package/src/hooks/clipboard/useClipboard.js +26 -0
- package/src/hooks/content/useContent.js +31 -0
- package/src/hooks/index.js +2 -0
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
import React, { createRef } from 'react';
|
|
1
|
+
import React, { createRef, useMemo } from 'react';
|
|
2
2
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
3
3
|
import cx from 'classnames';
|
|
4
4
|
import { Pagination, Dimmer, Loader } from 'semantic-ui-react';
|
|
5
|
+
import Slugger from 'github-slugger';
|
|
5
6
|
import { Icon } from '@plone/volto/components';
|
|
7
|
+
import { renderLinkElement } from '@plone/volto-slate/editor/render';
|
|
6
8
|
import config from '@plone/volto/registry';
|
|
7
9
|
import withQuerystringResults from './withQuerystringResults';
|
|
8
10
|
|
|
9
11
|
import paginationLeftSVG from '@plone/volto/icons/left-key.svg';
|
|
10
12
|
import paginationRightSVG from '@plone/volto/icons/right-key.svg';
|
|
11
13
|
|
|
14
|
+
const Headline = ({ headlineTag, id, data = {}, listingItems, isEditMode }) => {
|
|
15
|
+
let attr = { id };
|
|
16
|
+
const slug = Slugger.slug(data.headline);
|
|
17
|
+
attr.id = slug || id;
|
|
18
|
+
const LinkedHeadline = useMemo(() => renderLinkElement(headlineTag), [
|
|
19
|
+
headlineTag,
|
|
20
|
+
]);
|
|
21
|
+
return (
|
|
22
|
+
<LinkedHeadline
|
|
23
|
+
mode={!isEditMode && 'view'}
|
|
24
|
+
children={data.headline}
|
|
25
|
+
attributes={attr}
|
|
26
|
+
className={cx('headline', {
|
|
27
|
+
emptyListing: !listingItems?.length > 0,
|
|
28
|
+
})}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
12
33
|
const ListingBody = withQuerystringResults((props) => {
|
|
13
34
|
const {
|
|
14
35
|
data = {},
|
|
@@ -22,6 +43,7 @@ const ListingBody = withQuerystringResults((props) => {
|
|
|
22
43
|
nextBatch,
|
|
23
44
|
isFolderContentsListing,
|
|
24
45
|
hasLoaded,
|
|
46
|
+
id,
|
|
25
47
|
} = props;
|
|
26
48
|
|
|
27
49
|
let ListingBodyTemplate;
|
|
@@ -50,13 +72,13 @@ const ListingBody = withQuerystringResults((props) => {
|
|
|
50
72
|
return (
|
|
51
73
|
<>
|
|
52
74
|
{data.headline && (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
75
|
+
<Headline
|
|
76
|
+
headlineTag={HeadlineTag}
|
|
77
|
+
id={id}
|
|
78
|
+
listingItems={listingItems}
|
|
79
|
+
data={data}
|
|
80
|
+
isEditMode={isEditMode}
|
|
81
|
+
/>
|
|
60
82
|
)}
|
|
61
83
|
{listingItems?.length > 0 ? (
|
|
62
84
|
<div ref={listingRef}>
|
|
@@ -3,19 +3,29 @@
|
|
|
3
3
|
* @module volto-slate/blocks/Title/TitleBlockView
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from 'react';
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
|
+
import Slugger from 'github-slugger';
|
|
9
|
+
import { renderLinkElement } from '@plone/volto-slate/editor/render';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* View title block component.
|
|
11
13
|
* @class View
|
|
12
14
|
* @extends Component
|
|
13
15
|
*/
|
|
14
|
-
const TitleBlockView = ({ properties, metadata }) => {
|
|
16
|
+
const TitleBlockView = ({ properties, metadata, id, children }) => {
|
|
17
|
+
let attr = { id };
|
|
18
|
+
const title = (properties || metadata)['title'];
|
|
19
|
+
const slug = Slugger.slug(title);
|
|
20
|
+
attr.id = slug || id;
|
|
21
|
+
const LinkedTitle = useMemo(() => renderLinkElement('h1'), []);
|
|
15
22
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
<LinkedTitle
|
|
24
|
+
mode="view"
|
|
25
|
+
children={title ?? children}
|
|
26
|
+
attributes={attr}
|
|
27
|
+
className={'documentFirstHeading'}
|
|
28
|
+
/>
|
|
19
29
|
);
|
|
20
30
|
};
|
|
21
31
|
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer from 'react-test-renderer';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
6
|
import View from './View';
|
|
4
7
|
|
|
8
|
+
const mockStore = configureStore();
|
|
9
|
+
|
|
5
10
|
test('renders a view title component', () => {
|
|
11
|
+
const store = mockStore({
|
|
12
|
+
intl: {
|
|
13
|
+
locale: 'en',
|
|
14
|
+
messages: {},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
6
17
|
const component = renderer.create(
|
|
7
|
-
<
|
|
18
|
+
<Provider store={store}>
|
|
19
|
+
<MemoryRouter>
|
|
20
|
+
<View properties={{ title: 'My Title' }} id="a123" />
|
|
21
|
+
</MemoryRouter>
|
|
22
|
+
</Provider>,
|
|
8
23
|
);
|
|
9
24
|
const json = component.toJSON();
|
|
10
25
|
expect(json).toMatchSnapshot();
|
|
@@ -56,7 +56,14 @@ const View = (props) => {
|
|
|
56
56
|
const items = [];
|
|
57
57
|
if (!level || !levels.includes(level)) return;
|
|
58
58
|
tocEntriesLayout.push(id);
|
|
59
|
-
tocEntries[id] = {
|
|
59
|
+
tocEntries[id] = {
|
|
60
|
+
level,
|
|
61
|
+
title: title || block.plaintext,
|
|
62
|
+
items,
|
|
63
|
+
id,
|
|
64
|
+
override_toc: block.override_toc,
|
|
65
|
+
plaintext: block.plaintext,
|
|
66
|
+
};
|
|
60
67
|
if (level < rootLevel) {
|
|
61
68
|
rootLevel = level;
|
|
62
69
|
}
|
|
@@ -8,15 +8,27 @@ import PropTypes from 'prop-types';
|
|
|
8
8
|
import { map } from 'lodash';
|
|
9
9
|
import { List } from 'semantic-ui-react';
|
|
10
10
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
11
|
+
import { useHistory } from 'react-router-dom';
|
|
11
12
|
import AnchorLink from 'react-anchor-link-smooth-scroll';
|
|
13
|
+
import Slugger from 'github-slugger';
|
|
12
14
|
|
|
13
|
-
const RenderListItems = ({ items, data }) => {
|
|
15
|
+
const RenderListItems = ({ items, data, history }) => {
|
|
14
16
|
return map(items, (item) => {
|
|
15
|
-
const { id, level, title } = item;
|
|
17
|
+
const { id, level, title, override_toc, plaintext } = item;
|
|
18
|
+
const slug = override_toc
|
|
19
|
+
? Slugger.slug(plaintext)
|
|
20
|
+
: Slugger.slug(title) || id;
|
|
16
21
|
return (
|
|
17
22
|
item && (
|
|
18
23
|
<List.Item key={id} className={`item headline-${level}`} as="li">
|
|
19
|
-
<AnchorLink
|
|
24
|
+
<AnchorLink
|
|
25
|
+
href={`#${slug}`}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
history.push({ hash: slug });
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{title}
|
|
31
|
+
</AnchorLink>
|
|
20
32
|
{item.items?.length > 0 && (
|
|
21
33
|
<List
|
|
22
34
|
ordered={data.ordered}
|
|
@@ -38,6 +50,7 @@ const RenderListItems = ({ items, data }) => {
|
|
|
38
50
|
* @extends Component
|
|
39
51
|
*/
|
|
40
52
|
const View = ({ data, tocEntries }) => {
|
|
53
|
+
const history = useHistory();
|
|
41
54
|
return (
|
|
42
55
|
<>
|
|
43
56
|
{data.title && !data.hide_title ? (
|
|
@@ -57,7 +70,7 @@ const View = ({ data, tocEntries }) => {
|
|
|
57
70
|
bulleted={!data.ordered}
|
|
58
71
|
as={data.ordered ? 'ol' : 'ul'}
|
|
59
72
|
>
|
|
60
|
-
<RenderListItems items={tocEntries} data={data} />
|
|
73
|
+
<RenderListItems items={tocEntries} data={data} history={history} />
|
|
61
74
|
</List>
|
|
62
75
|
</>
|
|
63
76
|
);
|
|
@@ -9,15 +9,19 @@ import { map } from 'lodash';
|
|
|
9
9
|
import { Menu } from 'semantic-ui-react';
|
|
10
10
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
|
11
11
|
import AnchorLink from 'react-anchor-link-smooth-scroll';
|
|
12
|
+
import Slugger from 'github-slugger';
|
|
12
13
|
|
|
13
14
|
const RenderMenuItems = ({ items }) => {
|
|
14
15
|
return map(items, (item) => {
|
|
15
|
-
const { id, level, title } = item;
|
|
16
|
+
const { id, level, title, override_toc, plaintext } = item;
|
|
17
|
+
const slug = override_toc
|
|
18
|
+
? Slugger.slug(plaintext)
|
|
19
|
+
: Slugger.slug(title) || id;
|
|
16
20
|
return (
|
|
17
21
|
item && (
|
|
18
22
|
<React.Fragment key={id}>
|
|
19
23
|
<Menu.Item className={`headline-${level}`}>
|
|
20
|
-
<AnchorLink href={`#${
|
|
24
|
+
<AnchorLink href={`#${slug}`}>{title}</AnchorLink>
|
|
21
25
|
</Menu.Item>
|
|
22
26
|
{item.items?.length > 0 && <RenderMenuItems items={item.items} />}
|
|
23
27
|
</React.Fragment>
|
|
@@ -25,6 +25,7 @@ import { readAsDataURL } from 'promise-file-reader';
|
|
|
25
25
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
|
26
26
|
import { FormattedRelativeDate } from '@plone/volto/components';
|
|
27
27
|
import { createContent } from '@plone/volto/actions';
|
|
28
|
+
import { validateFileUploadSize } from '@plone/volto/helpers';
|
|
28
29
|
|
|
29
30
|
const Dropzone = loadable(() => import('react-dropzone'));
|
|
30
31
|
|
|
@@ -121,14 +122,18 @@ class ContentsUploadModal extends Component {
|
|
|
121
122
|
* @returns {undefined}
|
|
122
123
|
*/
|
|
123
124
|
onDrop = async (files) => {
|
|
125
|
+
const validFiles = [];
|
|
124
126
|
for (let i = 0; i < files.length; i++) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
if (validateFileUploadSize(files[i], this.props.intl.formatMessage)) {
|
|
128
|
+
await readAsDataURL(files[i]).then((data) => {
|
|
129
|
+
const fields = data.match(/^data:(.*);(.*),(.*)$/);
|
|
130
|
+
files[i].preview = fields[0];
|
|
131
|
+
});
|
|
132
|
+
validFiles.push(files[i]);
|
|
133
|
+
}
|
|
129
134
|
}
|
|
130
135
|
this.setState({
|
|
131
|
-
files: concat(this.state.files,
|
|
136
|
+
files: concat(this.state.files, validFiles),
|
|
132
137
|
});
|
|
133
138
|
};
|
|
134
139
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import
|
|
3
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
4
4
|
|
|
5
5
|
import successSVG from '@plone/volto/icons/ready.svg';
|
|
6
6
|
import infoSVG from '@plone/volto/icons/info.svg';
|
|
@@ -11,7 +11,7 @@ import { injectIntl } from 'react-intl';
|
|
|
11
11
|
import deleteSVG from '@plone/volto/icons/delete.svg';
|
|
12
12
|
import { Icon, FormFieldWrapper } from '@plone/volto/components';
|
|
13
13
|
import loadable from '@loadable/component';
|
|
14
|
-
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
14
|
+
import { flattenToAppURL, validateFileUploadSize } from '@plone/volto/helpers';
|
|
15
15
|
import { defineMessages, useIntl } from 'react-intl';
|
|
16
16
|
|
|
17
17
|
const imageMimetypes = [
|
|
@@ -95,6 +95,7 @@ const FileWidget = (props) => {
|
|
|
95
95
|
*/
|
|
96
96
|
const onDrop = (files) => {
|
|
97
97
|
const file = files[0];
|
|
98
|
+
if (!validateFileUploadSize(file, intl.formatMessage)) return;
|
|
98
99
|
readAsDataURL(file).then((data) => {
|
|
99
100
|
const fields = data.match(/^data:(.*);(.*),(.*)$/);
|
|
100
101
|
onChange(id, {
|
|
@@ -1,83 +1,56 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anontools component.
|
|
3
|
-
* @module components/theme/Anontools/Anontools
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { Component } from 'react';
|
|
7
1
|
import PropTypes from 'prop-types';
|
|
8
|
-
import { connect } from 'react-redux';
|
|
9
2
|
import { Link } from 'react-router-dom';
|
|
10
3
|
import { Menu } from 'semantic-ui-react';
|
|
11
4
|
import { FormattedMessage } from 'react-intl';
|
|
5
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
6
|
+
import { useToken } from '@plone/volto/hooks/userSession/useToken';
|
|
7
|
+
import { useContent } from '@plone/volto/hooks/content/useContent';
|
|
12
8
|
import config from '@plone/volto/registry';
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export class Anontools extends Component {
|
|
18
|
-
/**
|
|
19
|
-
* Property types.
|
|
20
|
-
* @property {Object} propTypes Property types.
|
|
21
|
-
* @static
|
|
22
|
-
*/
|
|
23
|
-
static propTypes = {
|
|
24
|
-
token: PropTypes.string,
|
|
25
|
-
content: PropTypes.shape({
|
|
26
|
-
'@id': PropTypes.string,
|
|
27
|
-
}),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Default properties.
|
|
32
|
-
* @property {Object} defaultProps Default properties.
|
|
33
|
-
* @static
|
|
34
|
-
*/
|
|
35
|
-
static defaultProps = {
|
|
36
|
-
token: null,
|
|
37
|
-
content: {
|
|
38
|
-
'@id': null,
|
|
39
|
-
},
|
|
40
|
-
};
|
|
10
|
+
const Anontools = () => {
|
|
11
|
+
const token = useToken();
|
|
12
|
+
const { data: content } = useContent();
|
|
41
13
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
14
|
+
const { settings } = config;
|
|
15
|
+
return (
|
|
16
|
+
!token && (
|
|
17
|
+
<Menu pointing secondary floated="right">
|
|
18
|
+
<Menu.Item>
|
|
19
|
+
<Link
|
|
20
|
+
aria-label="login"
|
|
21
|
+
to={`/login${
|
|
22
|
+
content?.['@id']
|
|
23
|
+
? `?return_url=${flattenToAppURL(content['@id'])}`
|
|
24
|
+
: ''
|
|
25
|
+
}`}
|
|
26
|
+
>
|
|
27
|
+
<FormattedMessage id="Log in" defaultMessage="Log in" />
|
|
28
|
+
</Link>
|
|
29
|
+
</Menu.Item>
|
|
30
|
+
{settings.showSelfRegistration && (
|
|
52
31
|
<Menu.Item>
|
|
53
|
-
<Link
|
|
54
|
-
|
|
55
|
-
to={`/login${
|
|
56
|
-
this.props.content?.['@id']
|
|
57
|
-
? `?return_url=${this.props.content['@id'].replace(
|
|
58
|
-
settings.apiPath,
|
|
59
|
-
'',
|
|
60
|
-
)}`
|
|
61
|
-
: ''
|
|
62
|
-
}`}
|
|
63
|
-
>
|
|
64
|
-
<FormattedMessage id="Log in" defaultMessage="Log in" />
|
|
32
|
+
<Link aria-label="register" to="/register">
|
|
33
|
+
<FormattedMessage id="Register" defaultMessage="Register" />
|
|
65
34
|
</Link>
|
|
66
35
|
</Menu.Item>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
36
|
+
)}
|
|
37
|
+
</Menu>
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default Anontools;
|
|
43
|
+
|
|
44
|
+
Anontools.propTypes = {
|
|
45
|
+
token: PropTypes.string,
|
|
46
|
+
content: PropTypes.shape({
|
|
47
|
+
'@id': PropTypes.string,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
79
50
|
|
|
80
|
-
|
|
81
|
-
token:
|
|
82
|
-
content:
|
|
83
|
-
|
|
51
|
+
Anontools.defaultProps = {
|
|
52
|
+
token: null,
|
|
53
|
+
content: {
|
|
54
|
+
'@id': null,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -12,7 +12,14 @@ describe('Anontools', () => {
|
|
|
12
12
|
it('renders an anontools component when no token is specified', () => {
|
|
13
13
|
const store = mockStore({
|
|
14
14
|
userSession: { token: null },
|
|
15
|
-
content: {
|
|
15
|
+
content: {
|
|
16
|
+
data: { '@id': 'myid' },
|
|
17
|
+
get: {
|
|
18
|
+
loading: false,
|
|
19
|
+
loaded: true,
|
|
20
|
+
error: null,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
16
23
|
intl: {
|
|
17
24
|
locale: 'en',
|
|
18
25
|
messages: {},
|
|
@@ -32,7 +39,14 @@ describe('Anontools', () => {
|
|
|
32
39
|
it('should not render an anontools component when a token is specified', () => {
|
|
33
40
|
const store = mockStore({
|
|
34
41
|
userSession: { token: '1234' },
|
|
35
|
-
content: {
|
|
42
|
+
content: {
|
|
43
|
+
data: {},
|
|
44
|
+
get: {
|
|
45
|
+
loading: false,
|
|
46
|
+
loaded: true,
|
|
47
|
+
error: null,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
36
50
|
intl: {
|
|
37
51
|
locale: 'en',
|
|
38
52
|
messages: {},
|
package/src/config/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { defineMessages } from 'react-intl';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
|
+
import { find, isEmpty } from 'lodash';
|
|
4
5
|
import config from '@plone/volto/registry';
|
|
5
6
|
import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils';
|
|
6
7
|
|
|
@@ -291,20 +292,23 @@ export const EMPTY_STYLES_SCHEMA = {
|
|
|
291
292
|
};
|
|
292
293
|
|
|
293
294
|
/**
|
|
294
|
-
*
|
|
295
|
+
* Adds the `styles` field and 'styling' fieldset in a given schema
|
|
295
296
|
*/
|
|
296
297
|
export const addStyling = ({ schema, formData, intl }) => {
|
|
297
|
-
schema.fieldsets
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
298
|
+
if (isEmpty(find(schema.fieldsets, { id: 'styling' }))) {
|
|
299
|
+
schema.fieldsets.push({
|
|
300
|
+
id: 'styling',
|
|
301
|
+
title: intl.formatMessage(messages.styling),
|
|
302
|
+
fields: ['styles'],
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
schema.properties.styles = {
|
|
306
|
+
widget: 'object',
|
|
307
|
+
title: intl.formatMessage(messages.styling),
|
|
308
|
+
schema: cloneDeepSchema(EMPTY_STYLES_SCHEMA),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
302
311
|
|
|
303
|
-
schema.properties.styles = {
|
|
304
|
-
widget: 'object',
|
|
305
|
-
title: intl.formatMessage(messages.styling),
|
|
306
|
-
schema: EMPTY_STYLES_SCHEMA,
|
|
307
|
-
};
|
|
308
312
|
return schema;
|
|
309
313
|
};
|
|
310
314
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
addExtensionFieldToSchema,
|
|
3
3
|
applySchemaEnhancer,
|
|
4
4
|
composeSchema,
|
|
5
|
+
addStyling,
|
|
5
6
|
} from './withBlockSchemaEnhancer';
|
|
6
7
|
|
|
7
8
|
import config from '@plone/volto/registry';
|
|
@@ -246,3 +247,147 @@ describe('composeSchema', () => {
|
|
|
246
247
|
expect(res).toStrictEqual([6, 9]);
|
|
247
248
|
});
|
|
248
249
|
});
|
|
250
|
+
|
|
251
|
+
describe('addStyling', () => {
|
|
252
|
+
it('returns an enhanced schema with the styling wrapper object on it', () => {
|
|
253
|
+
const intl = { formatMessage: () => 'Styling' };
|
|
254
|
+
|
|
255
|
+
const schema = {
|
|
256
|
+
fieldsets: [
|
|
257
|
+
{
|
|
258
|
+
id: 'default',
|
|
259
|
+
title: 'Default',
|
|
260
|
+
fields: [],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
properties: {},
|
|
264
|
+
required: [],
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const result = addStyling({ schema, intl });
|
|
268
|
+
|
|
269
|
+
expect(result).toStrictEqual({
|
|
270
|
+
fieldsets: [
|
|
271
|
+
{ id: 'default', title: 'Default', fields: [] },
|
|
272
|
+
{ id: 'styling', title: 'Styling', fields: ['styles'] },
|
|
273
|
+
],
|
|
274
|
+
properties: {
|
|
275
|
+
styles: {
|
|
276
|
+
widget: 'object',
|
|
277
|
+
title: 'Styling',
|
|
278
|
+
schema: {
|
|
279
|
+
fieldsets: [
|
|
280
|
+
{
|
|
281
|
+
fields: [],
|
|
282
|
+
id: 'default',
|
|
283
|
+
title: 'Default',
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
properties: {},
|
|
287
|
+
required: [],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
required: [],
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('multiple schema enhancers', () => {
|
|
296
|
+
const intl = { formatMessage: () => 'Styling' };
|
|
297
|
+
|
|
298
|
+
const schema1 = {
|
|
299
|
+
fieldsets: [
|
|
300
|
+
{
|
|
301
|
+
id: 'default',
|
|
302
|
+
title: 'Default',
|
|
303
|
+
fields: [],
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
properties: {},
|
|
307
|
+
required: [],
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const schema2 = {
|
|
311
|
+
fieldsets: [
|
|
312
|
+
{
|
|
313
|
+
id: 'default',
|
|
314
|
+
title: 'Default',
|
|
315
|
+
fields: [],
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
properties: {},
|
|
319
|
+
required: [],
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const result = addStyling({ schema: schema1, intl });
|
|
323
|
+
|
|
324
|
+
// We add some fields to the styling schema
|
|
325
|
+
result.properties.styles.schema.properties.align = {
|
|
326
|
+
widget: 'align',
|
|
327
|
+
title: 'align',
|
|
328
|
+
actions: ['left', 'right', 'center'],
|
|
329
|
+
default: 'left',
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
result.properties.styles.schema.fieldsets[0].fields = ['align'];
|
|
333
|
+
|
|
334
|
+
const result2 = addStyling({ schema: schema2, intl });
|
|
335
|
+
|
|
336
|
+
expect(result).toStrictEqual({
|
|
337
|
+
fieldsets: [
|
|
338
|
+
{ id: 'default', title: 'Default', fields: [] },
|
|
339
|
+
{ id: 'styling', title: 'Styling', fields: ['styles'] },
|
|
340
|
+
],
|
|
341
|
+
properties: {
|
|
342
|
+
styles: {
|
|
343
|
+
widget: 'object',
|
|
344
|
+
title: 'Styling',
|
|
345
|
+
schema: {
|
|
346
|
+
fieldsets: [
|
|
347
|
+
{
|
|
348
|
+
fields: ['align'],
|
|
349
|
+
id: 'default',
|
|
350
|
+
title: 'Default',
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
properties: {
|
|
354
|
+
align: {
|
|
355
|
+
widget: 'align',
|
|
356
|
+
title: 'align',
|
|
357
|
+
actions: ['left', 'right', 'center'],
|
|
358
|
+
default: 'left',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
required: [],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
required: [],
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
expect(result2).toStrictEqual({
|
|
369
|
+
fieldsets: [
|
|
370
|
+
{ id: 'default', title: 'Default', fields: [] },
|
|
371
|
+
{ id: 'styling', title: 'Styling', fields: ['styles'] },
|
|
372
|
+
],
|
|
373
|
+
properties: {
|
|
374
|
+
styles: {
|
|
375
|
+
widget: 'object',
|
|
376
|
+
title: 'Styling',
|
|
377
|
+
schema: {
|
|
378
|
+
fieldsets: [
|
|
379
|
+
{
|
|
380
|
+
fields: [],
|
|
381
|
+
id: 'default',
|
|
382
|
+
title: 'Default',
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
properties: {},
|
|
386
|
+
required: [],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
required: [],
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { map, uniq, keys, intersection, isEmpty } from 'lodash';
|
|
2
2
|
import { messages } from '../MessageLabels/MessageLabels';
|
|
3
|
+
import config from '@plone/volto/registry';
|
|
4
|
+
import { toast } from 'react-toastify';
|
|
5
|
+
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Will return the intl message if invalid
|
|
@@ -369,3 +372,29 @@ class FormValidation {
|
|
|
369
372
|
}
|
|
370
373
|
|
|
371
374
|
export default FormValidation;
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Check if a file upload is within the maximum size limit.
|
|
378
|
+
* @param {File} file
|
|
379
|
+
* @param {Function} intlFunc
|
|
380
|
+
* @returns {Boolean}
|
|
381
|
+
*/
|
|
382
|
+
export const validateFileUploadSize = (file, intlFunc) => {
|
|
383
|
+
const isValid =
|
|
384
|
+
!config.settings.maxFileUploadSize ||
|
|
385
|
+
file.size <= config.settings.maxFileUploadSize;
|
|
386
|
+
if (!isValid) {
|
|
387
|
+
toast.error(
|
|
388
|
+
<Toast
|
|
389
|
+
error
|
|
390
|
+
title={intlFunc(messages.error)}
|
|
391
|
+
content={intlFunc(messages.fileTooLarge, {
|
|
392
|
+
limit: `${Math.floor(
|
|
393
|
+
config.settings.maxFileUploadSize / 1024 / 1024,
|
|
394
|
+
)}MB`,
|
|
395
|
+
})}
|
|
396
|
+
/>,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return isValid;
|
|
400
|
+
};
|