@plone/volto 17.7.0 → 17.9.0
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 +36 -0
- package/addon-registry.js +34 -1
- package/package.json +1 -1
- package/packages/volto-slate/package.json +1 -1
- package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +5 -2
- package/src/components/manage/Blocks/Block/StyleWrapper.jsx +7 -1
- package/src/components/manage/Blocks/Grid/View.jsx +2 -1
- package/src/components/manage/Blocks/Image/View.jsx +2 -1
- package/src/components/manage/Blocks/Listing/View.jsx +2 -1
- package/src/components/manage/Blocks/Search/hocs/withSearch.jsx +46 -4
- package/src/components/manage/Blocks/Teaser/DefaultBody.jsx +2 -2
- package/src/components/manage/Sidebar/SidebarPopup.jsx +1 -1
- package/src/components/manage/Widgets/RecurrenceWidget/EndField.jsx +7 -1
- package/src/components/manage/Widgets/RecurrenceWidget/RecurrenceWidget.jsx +79 -31
- package/src/components/theme/Login/Login.jsx +12 -2
- package/src/components/theme/Navigation/Navigation.jsx +34 -32
- package/src/components/theme/PreviewImage/PreviewImage.jsx +7 -2
- package/src/components/theme/Sitemap/Sitemap.jsx +4 -4
- package/src/components/theme/Sitemap/Sitemap.test.jsx +52 -0
- package/src/components/theme/Unauthorized/Unauthorized.jsx +12 -11
- package/src/helpers/Blocks/Blocks.js +64 -1
- package/src/helpers/Blocks/Blocks.test.js +77 -0
- package/src/helpers/index.js +1 -0
- package/types/components/manage/Blocks/Image/View.d.ts +2 -1
- package/types/helpers/Blocks/Blocks.d.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,42 @@ myst:
|
|
|
17
17
|
|
|
18
18
|
<!-- towncrier release notes start -->
|
|
19
19
|
|
|
20
|
+
## 17.9.0 (2024-01-04)
|
|
21
|
+
|
|
22
|
+
### Feature
|
|
23
|
+
|
|
24
|
+
- Allow to opt out of the nested prefixed name build in the custom CSS properties style name generator if an object is found in the style wrapper object. @sneridagh [#5586](https://github.com/plone/volto/issues/5586)
|
|
25
|
+
|
|
26
|
+
### Bugfix
|
|
27
|
+
|
|
28
|
+
- Refactoring the code for extraction of videoDetails from the video URL, adding code for extracting videoDetails from youtube video URLs with '/live/' in its URL which previously used to throw an error and adding jest tests for same. @IshaanDasgupta [#5416](https://github.com/plone/volto/issues/5416)
|
|
29
|
+
|
|
30
|
+
### Internal
|
|
31
|
+
|
|
32
|
+
- Pin mrs-developer to latest version, not to star @sneridagh [#5593](https://github.com/plone/volto/issues/5593)
|
|
33
|
+
|
|
34
|
+
## 17.8.0 (2024-01-02)
|
|
35
|
+
|
|
36
|
+
### Feature
|
|
37
|
+
|
|
38
|
+
- Added support for custom CSS properties in the `StyleWrapper`. @sneridagh [#5581](https://github.com/plone/volto/issues/5581)
|
|
39
|
+
|
|
40
|
+
### Bugfix
|
|
41
|
+
|
|
42
|
+
- Fixed utc problems in RecurrenceWidget [#5002](https://github.com/plone/volto/issues/5002)
|
|
43
|
+
- The hamburger icon to open the mobile/tablet navigation is now hidden if there are no navigation items. @Aarav238 [#5353](https://github.com/plone/volto/issues/5353)
|
|
44
|
+
- Fix preview image component @steffenri [#5379](https://github.com/plone/volto/issues/5379)
|
|
45
|
+
- Fix autopopulated value of facet when settings the value for another one. @iFlameing [#5432](https://github.com/plone/volto/issues/5432)
|
|
46
|
+
- Fix sitemap for multilingual sites
|
|
47
|
+
[erral] [#5501](https://github.com/plone/volto/issues/5501)
|
|
48
|
+
- Replace createRef with useRef in SidebarPopup
|
|
49
|
+
[razvanMiu] [#5519](https://github.com/plone/volto/issues/5519)
|
|
50
|
+
- Fixed edge case error in Unauthorised page and Login route behavior @sneridagh [#5536](https://github.com/plone/volto/issues/5536)
|
|
51
|
+
|
|
52
|
+
### Internal
|
|
53
|
+
|
|
54
|
+
- Update Plone to 6.0.9 @sneridagh [#5562](https://github.com/plone/volto/issues/5562)
|
|
55
|
+
|
|
20
56
|
## 17.7.0 (2023-12-13)
|
|
21
57
|
|
|
22
58
|
### Feature
|
package/addon-registry.js
CHANGED
|
@@ -482,8 +482,41 @@ class AddonConfigurationRegistry {
|
|
|
482
482
|
`${customPath}/**/*.*(svg|png|jpg|jpeg|gif|ico|less|js|jsx|ts|tsx)`,
|
|
483
483
|
),
|
|
484
484
|
(filename) => {
|
|
485
|
+
function changeFileExtension(filePath) {
|
|
486
|
+
// Extract the current file extension
|
|
487
|
+
const currentExtension = filePath.split('.').pop();
|
|
488
|
+
|
|
489
|
+
// Define the mapping between file extensions
|
|
490
|
+
const extensionMapping = {
|
|
491
|
+
jsx: 'tsx',
|
|
492
|
+
tsx: 'jsx',
|
|
493
|
+
js: 'ts',
|
|
494
|
+
ts: 'js',
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Check if the current extension is in the mapping
|
|
498
|
+
if (currentExtension in extensionMapping) {
|
|
499
|
+
// Replace the current extension with the corresponding one from the mapping
|
|
500
|
+
const newExtension = extensionMapping[currentExtension];
|
|
501
|
+
const newPath = filePath.replace(
|
|
502
|
+
`.${currentExtension}`,
|
|
503
|
+
`.${newExtension}`,
|
|
504
|
+
);
|
|
505
|
+
return newPath;
|
|
506
|
+
} else {
|
|
507
|
+
// If the current extension is not in the mapping, return the original path
|
|
508
|
+
return filePath;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
485
512
|
const targetPath = filename.replace(customPath, sourcePath);
|
|
486
|
-
|
|
513
|
+
// We try to find the source to shadow with the exact path
|
|
514
|
+
// and we try also with the extension changed in search for JS<->TS
|
|
515
|
+
// correspondence
|
|
516
|
+
if (
|
|
517
|
+
fs.existsSync(targetPath) ||
|
|
518
|
+
fs.existsSync(changeFileExtension(targetPath))
|
|
519
|
+
) {
|
|
487
520
|
aliases[
|
|
488
521
|
filename
|
|
489
522
|
.replace(customPath, name)
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { Icon } from '@plone/volto/components';
|
|
|
3
3
|
import {
|
|
4
4
|
blockHasValue,
|
|
5
5
|
buildStyleClassNamesFromData,
|
|
6
|
+
buildStyleObjectFromData,
|
|
6
7
|
} from '@plone/volto/helpers';
|
|
7
8
|
import dragSVG from '@plone/volto/icons/drag.svg';
|
|
8
9
|
import { Button } from 'semantic-ui-react';
|
|
@@ -57,7 +58,8 @@ const EditBlockWrapper = (props) => {
|
|
|
57
58
|
? data.required
|
|
58
59
|
: includes(config.blocks.requiredBlocks, type);
|
|
59
60
|
|
|
60
|
-
const
|
|
61
|
+
const classNames = buildStyleClassNamesFromData(data.styles);
|
|
62
|
+
const style = buildStyleObjectFromData(data.styles);
|
|
61
63
|
|
|
62
64
|
return (
|
|
63
65
|
<div
|
|
@@ -66,9 +68,10 @@ const EditBlockWrapper = (props) => {
|
|
|
66
68
|
// Right now, we can have the alignment information in the styles property or in the
|
|
67
69
|
// block data root, we inject the classname here for having control over the whole
|
|
68
70
|
// Block Edit wrapper
|
|
69
|
-
className={cx(`block-editor-${data['@type']}`,
|
|
71
|
+
className={cx(`block-editor-${data['@type']}`, classNames, {
|
|
70
72
|
[data.align]: data.align,
|
|
71
73
|
})}
|
|
74
|
+
style={style}
|
|
72
75
|
>
|
|
73
76
|
<div style={{ position: 'relative' }}>
|
|
74
77
|
<div
|
|
@@ -3,10 +3,12 @@ import cx from 'classnames';
|
|
|
3
3
|
import {
|
|
4
4
|
buildStyleClassNamesFromData,
|
|
5
5
|
buildStyleClassNamesExtenders,
|
|
6
|
+
buildStyleObjectFromData,
|
|
6
7
|
} from '@plone/volto/helpers';
|
|
7
8
|
|
|
8
9
|
const StyleWrapper = (props) => {
|
|
9
|
-
let classNames
|
|
10
|
+
let classNames,
|
|
11
|
+
style = [];
|
|
10
12
|
const { children, content, data = {}, block } = props;
|
|
11
13
|
classNames = buildStyleClassNamesFromData(data.styles);
|
|
12
14
|
|
|
@@ -16,11 +18,15 @@ const StyleWrapper = (props) => {
|
|
|
16
18
|
data,
|
|
17
19
|
classNames,
|
|
18
20
|
});
|
|
21
|
+
|
|
22
|
+
style = buildStyleObjectFromData(data.styles);
|
|
23
|
+
|
|
19
24
|
const rewrittenChildren = React.Children.map(children, (child) => {
|
|
20
25
|
if (React.isValidElement(child)) {
|
|
21
26
|
const childProps = {
|
|
22
27
|
...props,
|
|
23
28
|
className: cx([child.props.className, ...classNames]),
|
|
29
|
+
style: { ...child.props.style, ...style },
|
|
24
30
|
};
|
|
25
31
|
return React.cloneElement(child, childProps);
|
|
26
32
|
}
|
|
@@ -5,7 +5,7 @@ import { withBlockExtensions } from '@plone/volto/helpers';
|
|
|
5
5
|
import config from '@plone/volto/registry';
|
|
6
6
|
|
|
7
7
|
const GridBlockView = (props) => {
|
|
8
|
-
const { data, path, className } = props;
|
|
8
|
+
const { data, path, className, style } = props;
|
|
9
9
|
const metadata = props.metadata || props.properties;
|
|
10
10
|
const columns = data.blocks_layout.items;
|
|
11
11
|
const blocksConfig =
|
|
@@ -22,6 +22,7 @@ const GridBlockView = (props) => {
|
|
|
22
22
|
three: columns?.length === 3,
|
|
23
23
|
four: columns?.length === 4,
|
|
24
24
|
})}
|
|
25
|
+
style={style}
|
|
25
26
|
>
|
|
26
27
|
{data.headline && <h2 className="headline">{data.headline}</h2>}
|
|
27
28
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@plone/volto/helpers';
|
|
10
10
|
import config from '@plone/volto/registry';
|
|
11
11
|
|
|
12
|
-
export const View = ({ className, data, detached, properties }) => {
|
|
12
|
+
export const View = ({ className, data, detached, properties, style }) => {
|
|
13
13
|
const href = data?.href?.[0]?.['@id'] || '';
|
|
14
14
|
|
|
15
15
|
const Image = config.getComponent({ name: 'Image' }).component;
|
|
@@ -25,6 +25,7 @@ export const View = ({ className, data, detached, properties }) => {
|
|
|
25
25
|
data.align,
|
|
26
26
|
className,
|
|
27
27
|
)}
|
|
28
|
+
style={style}
|
|
28
29
|
>
|
|
29
30
|
{data.url && (
|
|
30
31
|
<>
|
|
@@ -6,11 +6,12 @@ import { withBlockExtensions } from '@plone/volto/helpers';
|
|
|
6
6
|
import { ListingBlockBody as ListingBody } from '@plone/volto/components';
|
|
7
7
|
|
|
8
8
|
const View = (props) => {
|
|
9
|
-
const { data, path, pathname, className } = props;
|
|
9
|
+
const { data, path, pathname, className, style } = props;
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
12
|
<div
|
|
13
13
|
className={cx('block listing', data.variation || 'default', className)}
|
|
14
|
+
style={style}
|
|
14
15
|
>
|
|
15
16
|
<ListingBody {...props} path={path ?? pathname} />
|
|
16
17
|
</div>
|
|
@@ -96,9 +96,21 @@ function normalizeState({
|
|
|
96
96
|
const { types: facetWidgetTypes } =
|
|
97
97
|
config.blocks.blocksConfig.search.extensions.facetWidgets;
|
|
98
98
|
|
|
99
|
+
// Here, we are removing the QueryString of the Listing ones, which is present in the Facet
|
|
100
|
+
// because we already initialize the facet with those values.
|
|
101
|
+
const configuredFacets = facetSettings
|
|
102
|
+
? facetSettings.map((facet) => facet?.field?.value)
|
|
103
|
+
: [];
|
|
104
|
+
|
|
105
|
+
let copyOfQuery = query.query ? [...query.query] : [];
|
|
106
|
+
|
|
107
|
+
const queryWithoutFacet = copyOfQuery.filter((query) => {
|
|
108
|
+
return !configuredFacets.includes(query.i);
|
|
109
|
+
});
|
|
110
|
+
|
|
99
111
|
const params = {
|
|
100
112
|
query: [
|
|
101
|
-
...(
|
|
113
|
+
...(queryWithoutFacet || []),
|
|
102
114
|
...(facetSettings || []).map((facet) => {
|
|
103
115
|
if (!facet?.field) return null;
|
|
104
116
|
|
|
@@ -261,14 +273,34 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
261
273
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
262
274
|
const configuredFacets =
|
|
263
275
|
data.facets?.map((facet) => facet?.field?.value) || [];
|
|
276
|
+
|
|
277
|
+
// Here we are getting the initial value of the facet if Listing Query contains the same criteria as
|
|
278
|
+
// facet.
|
|
279
|
+
const queryData = data?.query?.query
|
|
280
|
+
? deserializeQuery(JSON.stringify(data?.query?.query))
|
|
281
|
+
: [];
|
|
282
|
+
|
|
283
|
+
let intializeFacetWithQueryValue = [];
|
|
284
|
+
|
|
285
|
+
for (let value of configuredFacets) {
|
|
286
|
+
const queryString = queryData.find((item) => item.i === value);
|
|
287
|
+
if (queryString) {
|
|
288
|
+
intializeFacetWithQueryValue = [
|
|
289
|
+
...intializeFacetWithQueryValue,
|
|
290
|
+
{ [queryString.i]: queryString.v },
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
264
295
|
const multiFacets = data.facets
|
|
265
296
|
?.filter((facet) => facet?.multiple)
|
|
266
297
|
.map((facet) => facet?.field?.value);
|
|
267
298
|
const [facets, setFacets] = React.useState(
|
|
268
299
|
Object.assign(
|
|
269
300
|
{},
|
|
270
|
-
...urlQuery.map(({ i, v }) => ({ [i]: v })),
|
|
271
|
-
|
|
301
|
+
...urlQuery.map(({ i, v }) => ({ [i]: v })),
|
|
302
|
+
// TODO: the 'o' should be kept. This would be a major refactoring of the facets
|
|
303
|
+
...intializeFacetWithQueryValue,
|
|
272
304
|
// support for simple filters like ?Subject=something
|
|
273
305
|
// TODO: since the move to hash params this is no longer working.
|
|
274
306
|
// We'd have to treat the location.search and manage it just like the
|
|
@@ -288,8 +320,17 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
288
320
|
);
|
|
289
321
|
const previousUrlQuery = usePrevious(urlQuery);
|
|
290
322
|
|
|
323
|
+
// During first render the previousUrlQuery is undefined and urlQuery
|
|
324
|
+
// is empty so it ressetting the facet when you are navigating but during reload we have urlQuery and we need
|
|
325
|
+
// to set the facet at first render.
|
|
326
|
+
const preventOverrideOfFacetState =
|
|
327
|
+
previousUrlQuery === undefined && urlQuery.length === 0;
|
|
328
|
+
|
|
291
329
|
React.useEffect(() => {
|
|
292
|
-
if (
|
|
330
|
+
if (
|
|
331
|
+
!isEqual(urlQuery, previousUrlQuery) &&
|
|
332
|
+
!preventOverrideOfFacetState
|
|
333
|
+
) {
|
|
293
334
|
setFacets(
|
|
294
335
|
Object.assign(
|
|
295
336
|
{},
|
|
@@ -319,6 +360,7 @@ const withSearch = (options) => (WrappedComponent) => {
|
|
|
319
360
|
locationSearchData,
|
|
320
361
|
multiFacets,
|
|
321
362
|
previousUrlQuery,
|
|
363
|
+
preventOverrideOfFacetState,
|
|
322
364
|
]);
|
|
323
365
|
|
|
324
366
|
const [sortOn, setSortOn] = React.useState(data?.query?.sort_on);
|
|
@@ -18,7 +18,7 @@ const messages = defineMessages({
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
const TeaserDefaultTemplate = (props) => {
|
|
21
|
-
const { className, data, isEditMode } = props;
|
|
21
|
+
const { className, data, isEditMode, style } = props;
|
|
22
22
|
const intl = useIntl();
|
|
23
23
|
const href = data.href?.[0];
|
|
24
24
|
const image = data.preview_image?.[0];
|
|
@@ -27,7 +27,7 @@ const TeaserDefaultTemplate = (props) => {
|
|
|
27
27
|
const { openExternalLinkInNewTab } = config.settings;
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div className={cx('block teaser', className)}>
|
|
30
|
+
<div className={cx('block teaser', className)} style={style}>
|
|
31
31
|
<>
|
|
32
32
|
{!href && isEditMode && (
|
|
33
33
|
<Message>
|
|
@@ -9,7 +9,7 @@ const DEFAULT_TIMEOUT = 500;
|
|
|
9
9
|
const SidebarPopup = (props) => {
|
|
10
10
|
const { children, open, onClose, overlay } = props;
|
|
11
11
|
|
|
12
|
-
const asideElement = React.
|
|
12
|
+
const asideElement = React.useRef();
|
|
13
13
|
|
|
14
14
|
const handleClickOutside = (e) => {
|
|
15
15
|
if (asideElement && doesNodeContainClick(asideElement.current, e)) return;
|
|
@@ -81,7 +81,13 @@ const EndField = ({ value, count, until, onChange, intl }) => {
|
|
|
81
81
|
id="until"
|
|
82
82
|
title={intl.formatMessage(messages.recurrenceEndsUntil)}
|
|
83
83
|
dateOnly={true}
|
|
84
|
-
value={
|
|
84
|
+
value={
|
|
85
|
+
until
|
|
86
|
+
? typeof until === 'string'
|
|
87
|
+
? until
|
|
88
|
+
: until?.toISOString()
|
|
89
|
+
: ''
|
|
90
|
+
}
|
|
85
91
|
resettable={false}
|
|
86
92
|
onChange={(id, value) => {
|
|
87
93
|
onChange(id, value === '' ? undefined : value);
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
FREQUENCES,
|
|
37
37
|
WEEKLY_DAYS,
|
|
38
38
|
MONDAYFRIDAY_DAYS,
|
|
39
|
-
toISOString,
|
|
40
39
|
rrulei18n,
|
|
41
40
|
} from './Utils';
|
|
42
41
|
|
|
@@ -218,26 +217,41 @@ class RecurrenceWidget extends Component {
|
|
|
218
217
|
|
|
219
218
|
componentDidUpdate(prevProps) {
|
|
220
219
|
if (this.props.value) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
this.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
220
|
+
const changedStart =
|
|
221
|
+
prevProps.formData?.start !== this.props.formData?.start;
|
|
222
|
+
const changedEnd = prevProps.formData?.end !== this.props.formData?.end;
|
|
223
|
+
|
|
224
|
+
if (changedStart || changedEnd) {
|
|
225
|
+
let start = this.getUTCDate(this.props.formData?.start).toDate();
|
|
226
|
+
// let end = this.getUTCDate(this.props.formData?.end).toDate();
|
|
227
|
+
|
|
228
|
+
let changeFormValues = {};
|
|
229
|
+
if (changedEnd) {
|
|
230
|
+
changeFormValues.until = this.getUTCDate(
|
|
231
|
+
this.props.formData?.end,
|
|
232
|
+
).toDate();
|
|
233
|
+
}
|
|
234
|
+
this.setState(
|
|
235
|
+
(prevState) => {
|
|
236
|
+
let rruleSet = prevState.rruleSet;
|
|
237
|
+
|
|
238
|
+
rruleSet = this.updateRruleSet(
|
|
239
|
+
rruleSet,
|
|
240
|
+
{ ...prevState.formValues, ...changeFormValues },
|
|
241
|
+
'dtstart',
|
|
242
|
+
start,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
...prevState,
|
|
247
|
+
rruleSet,
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
() => {
|
|
251
|
+
//then, after set state, set recurrence rrule value
|
|
252
|
+
this.saveRrule();
|
|
253
|
+
},
|
|
254
|
+
);
|
|
241
255
|
}
|
|
242
256
|
}
|
|
243
257
|
}
|
|
@@ -249,7 +263,7 @@ class RecurrenceWidget extends Component {
|
|
|
249
263
|
setRecurrenceStartEnd = () => {
|
|
250
264
|
const start = this.props.formData?.start;
|
|
251
265
|
|
|
252
|
-
|
|
266
|
+
const _start = new Date(start); //The date is already in utc from plone, so this is not necessary: this.getUTCDate(start).startOf('day').toDate();
|
|
253
267
|
|
|
254
268
|
this.setState((prevState) => {
|
|
255
269
|
let rruleSet = prevState.rruleSet;
|
|
@@ -338,7 +352,7 @@ class RecurrenceWidget extends Component {
|
|
|
338
352
|
case 'until':
|
|
339
353
|
if (value != null) {
|
|
340
354
|
formValues['recurrenceEnds'] = option;
|
|
341
|
-
formValues[option] =
|
|
355
|
+
formValues[option] = value;
|
|
342
356
|
}
|
|
343
357
|
break;
|
|
344
358
|
case 'byweekday':
|
|
@@ -421,7 +435,24 @@ class RecurrenceWidget extends Component {
|
|
|
421
435
|
}
|
|
422
436
|
break;
|
|
423
437
|
case 'until':
|
|
424
|
-
|
|
438
|
+
let mDate = null;
|
|
439
|
+
if (value) {
|
|
440
|
+
mDate = this.moment(new Date(value));
|
|
441
|
+
if (typeof value === 'string') {
|
|
442
|
+
mDate = this.moment(new Date(value));
|
|
443
|
+
} else {
|
|
444
|
+
//object-->Date()
|
|
445
|
+
mDate = this.moment(value);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (this.props.formData.end) {
|
|
449
|
+
//set time from formData.end
|
|
450
|
+
const mEnd = this.moment(new Date(this.props.formData.end));
|
|
451
|
+
mDate.set('hour', mEnd.get('hour'));
|
|
452
|
+
mDate.set('minute', mEnd.get('minute'));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
value = value ? mDate.toDate() : null;
|
|
425
456
|
break;
|
|
426
457
|
default:
|
|
427
458
|
break;
|
|
@@ -446,7 +477,7 @@ class RecurrenceWidget extends Component {
|
|
|
446
477
|
? value
|
|
447
478
|
: rruleSet.dtstart()
|
|
448
479
|
? rruleSet.dtstart()
|
|
449
|
-
:
|
|
480
|
+
: new Date();
|
|
450
481
|
var exdates =
|
|
451
482
|
field === 'exdates' ? value : Object.assign([], rruleSet.exdates());
|
|
452
483
|
|
|
@@ -470,12 +501,12 @@ class RecurrenceWidget extends Component {
|
|
|
470
501
|
getDefaultUntil = (freq) => {
|
|
471
502
|
const moment = this.moment;
|
|
472
503
|
var end = this.props.formData?.end
|
|
473
|
-
?
|
|
504
|
+
? moment(new Date(this.props.formData.end))
|
|
474
505
|
: null;
|
|
475
|
-
var tomorrow =
|
|
476
|
-
var nextWeek =
|
|
477
|
-
var nextMonth =
|
|
478
|
-
var nextYear =
|
|
506
|
+
var tomorrow = moment().add(1, 'days');
|
|
507
|
+
var nextWeek = moment().add(7, 'days');
|
|
508
|
+
var nextMonth = moment().add(1, 'months');
|
|
509
|
+
var nextYear = moment().add(1, 'years');
|
|
479
510
|
|
|
480
511
|
var until = end;
|
|
481
512
|
switch (freq) {
|
|
@@ -500,6 +531,19 @@ class RecurrenceWidget extends Component {
|
|
|
500
531
|
default:
|
|
501
532
|
break;
|
|
502
533
|
}
|
|
534
|
+
if (this.props.formData.end) {
|
|
535
|
+
//set default end time
|
|
536
|
+
until.set('hour', end.get('hour'));
|
|
537
|
+
until.set('minute', end.get('minute'));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
until = new Date(
|
|
541
|
+
until.get('year'),
|
|
542
|
+
until.get('month'),
|
|
543
|
+
until.get('date'),
|
|
544
|
+
until.get('hour'),
|
|
545
|
+
until.get('minute'),
|
|
546
|
+
);
|
|
503
547
|
|
|
504
548
|
return until;
|
|
505
549
|
};
|
|
@@ -715,9 +759,13 @@ class RecurrenceWidget extends Component {
|
|
|
715
759
|
}
|
|
716
760
|
};
|
|
717
761
|
|
|
718
|
-
|
|
762
|
+
saveRrule = () => {
|
|
719
763
|
var value = this.state.rruleSet.toString();
|
|
720
764
|
this.props.onChange(this.props.id, value);
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
save = () => {
|
|
768
|
+
this.saveRrule();
|
|
721
769
|
this.close();
|
|
722
770
|
};
|
|
723
771
|
|
|
@@ -77,8 +77,9 @@ const Login = (props) => {
|
|
|
77
77
|
qs.parse(props.location?.search ?? location.search).return_url ||
|
|
78
78
|
location.pathname.replace(/\/login\/?$/, '').replace(/\/logout\/?$/, '') ||
|
|
79
79
|
'/';
|
|
80
|
+
|
|
80
81
|
useEffect(() => {
|
|
81
|
-
if (token && !props.isLogout) {
|
|
82
|
+
if (token && !(props.isLogout || location?.state?.isLogout)) {
|
|
82
83
|
history.push(returnUrl || '/');
|
|
83
84
|
if (toast.isActive('loggedOut')) {
|
|
84
85
|
toast.dismiss('loggedOut');
|
|
@@ -108,7 +109,16 @@ const Login = (props) => {
|
|
|
108
109
|
dispatch(resetLoginRequest());
|
|
109
110
|
}
|
|
110
111
|
};
|
|
111
|
-
}, [
|
|
112
|
+
}, [
|
|
113
|
+
dispatch,
|
|
114
|
+
token,
|
|
115
|
+
error,
|
|
116
|
+
intl,
|
|
117
|
+
history,
|
|
118
|
+
returnUrl,
|
|
119
|
+
props.isLogout,
|
|
120
|
+
location?.state?.isLogout,
|
|
121
|
+
]);
|
|
112
122
|
|
|
113
123
|
const onLogin = (event) => {
|
|
114
124
|
dispatch(
|
|
@@ -48,40 +48,42 @@ const Navigation = (props) => {
|
|
|
48
48
|
}
|
|
49
49
|
setisMobileMenuOpen(false);
|
|
50
50
|
};
|
|
51
|
-
|
|
52
51
|
return (
|
|
53
52
|
<nav className="navigation" id="navigation" aria-label="Site">
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<span className="hamburger-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
53
|
+
{items?.length ? (
|
|
54
|
+
<div className="hamburger-wrapper mobile tablet only">
|
|
55
|
+
<button
|
|
56
|
+
className={cx('hamburger hamburger--spin', {
|
|
57
|
+
'is-active': isMobileMenuOpen,
|
|
58
|
+
})}
|
|
59
|
+
aria-label={
|
|
60
|
+
isMobileMenuOpen
|
|
61
|
+
? intl.formatMessage(messages.closeMobileMenu, {
|
|
62
|
+
type: type,
|
|
63
|
+
})
|
|
64
|
+
: intl.formatMessage(messages.openMobileMenu, {
|
|
65
|
+
type: type,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
title={
|
|
69
|
+
isMobileMenuOpen
|
|
70
|
+
? intl.formatMessage(messages.closeMobileMenu, {
|
|
71
|
+
type: type,
|
|
72
|
+
})
|
|
73
|
+
: intl.formatMessage(messages.openMobileMenu, {
|
|
74
|
+
type: type,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={toggleMobileMenu}
|
|
79
|
+
>
|
|
80
|
+
<span className="hamburger-box">
|
|
81
|
+
<span className="hamburger-inner" />
|
|
82
|
+
</span>
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
) : null}
|
|
86
|
+
|
|
85
87
|
<Menu
|
|
86
88
|
stackable
|
|
87
89
|
pointing
|
|
@@ -11,12 +11,17 @@ function PreviewImage({ item, alt, image_field, showDefault = true, ...rest }) {
|
|
|
11
11
|
const Image = config.getComponent({ name: 'Image' }).component;
|
|
12
12
|
|
|
13
13
|
const image = (
|
|
14
|
-
<Image
|
|
14
|
+
<Image
|
|
15
|
+
item={item}
|
|
16
|
+
image_field={image_field || item.image_field}
|
|
17
|
+
alt={alt}
|
|
18
|
+
{...rest}
|
|
19
|
+
/>
|
|
15
20
|
);
|
|
16
21
|
|
|
17
22
|
if (!image && !showDefault) return null;
|
|
18
23
|
|
|
19
|
-
if (
|
|
24
|
+
if (image_field || item?.image_field) {
|
|
20
25
|
return image;
|
|
21
26
|
} else {
|
|
22
27
|
return (
|
|
@@ -33,16 +33,16 @@ export function getSitemapPath(pathname = '', lang) {
|
|
|
33
33
|
function Sitemap(props) {
|
|
34
34
|
const {
|
|
35
35
|
location: { pathname },
|
|
36
|
-
|
|
36
|
+
lang,
|
|
37
37
|
getNavigation,
|
|
38
38
|
} = props;
|
|
39
39
|
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
const { settings } = config;
|
|
42
|
-
const
|
|
43
|
-
const path = getSitemapPath(pathname,
|
|
42
|
+
const language = settings.isMultilingual ? `${toBackendLang(lang)}` : null;
|
|
43
|
+
const path = getSitemapPath(pathname, language);
|
|
44
44
|
getNavigation(path, 4);
|
|
45
|
-
}, [pathname,
|
|
45
|
+
}, [pathname, lang, getNavigation]);
|
|
46
46
|
|
|
47
47
|
const renderItems = (items) => {
|
|
48
48
|
return (
|
|
@@ -3,6 +3,7 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
+
import config from '@plone/volto/registry';
|
|
6
7
|
|
|
7
8
|
import { __test__ as Sitemap, getSitemapPath } from './Sitemap';
|
|
8
9
|
|
|
@@ -55,6 +56,57 @@ describe('Sitemap', () => {
|
|
|
55
56
|
});
|
|
56
57
|
});
|
|
57
58
|
|
|
59
|
+
describe('Sitemap in a multilingual site', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
config.settings.isMultilingual = true;
|
|
62
|
+
config.settings.supportedLanguages = ['en', 'es'];
|
|
63
|
+
});
|
|
64
|
+
it('renders a sitemap component', () => {
|
|
65
|
+
const store = mockStore({
|
|
66
|
+
navigation: {
|
|
67
|
+
url: 'http://localhost:8080/Plone/en/@navigation',
|
|
68
|
+
items: [
|
|
69
|
+
{
|
|
70
|
+
url: 'http://localhost:8080/Plone/en/page-1',
|
|
71
|
+
description: '',
|
|
72
|
+
items: [
|
|
73
|
+
{
|
|
74
|
+
url: 'http://localhost:8080/Plone/en/page-1/page-1-2',
|
|
75
|
+
description: '',
|
|
76
|
+
title: 'Page 1-2',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
url: 'http://localhost:8080/Plone/en/page-1/page-1-3',
|
|
80
|
+
description: '',
|
|
81
|
+
title: 'Page 1-3',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
title: 'Page 1-3',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
url: 'http://localhost:8080/Plone/en/page-2',
|
|
88
|
+
description: '',
|
|
89
|
+
title: 'Page 2',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
intl: {
|
|
94
|
+
locale: 'en',
|
|
95
|
+
messages: {},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const component = renderer.create(
|
|
99
|
+
<Provider store={store}>
|
|
100
|
+
<MemoryRouter>
|
|
101
|
+
<Sitemap location={{ pathname: '/en/' }} />
|
|
102
|
+
</MemoryRouter>
|
|
103
|
+
</Provider>,
|
|
104
|
+
);
|
|
105
|
+
const json = component.toJSON();
|
|
106
|
+
expect(json).toMatchSnapshot();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
58
110
|
describe('getSitemapPath', () => {
|
|
59
111
|
it('accepts empty path', () => {
|
|
60
112
|
expect(getSitemapPath('', null)).toBe('');
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module components/theme/Unauthorized/Unauthorized
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import React from 'react';
|
|
6
1
|
import { FormattedMessage } from 'react-intl';
|
|
7
2
|
import { Link } from 'react-router-dom';
|
|
8
3
|
import { Container } from 'semantic-ui-react';
|
|
@@ -11,11 +6,6 @@ import { useLocation } from 'react-router-dom';
|
|
|
11
6
|
import { withServerErrorCode } from '@plone/volto/helpers/Utils/Utils';
|
|
12
7
|
import { getBaseUrl } from '@plone/volto/helpers';
|
|
13
8
|
|
|
14
|
-
/**
|
|
15
|
-
* unauthorized function.
|
|
16
|
-
* @function Unauthorized
|
|
17
|
-
* @returns {string} Markup of the unauthorized page.
|
|
18
|
-
*/
|
|
19
9
|
const Unauthorized = () => {
|
|
20
10
|
const error_message = useSelector((state) => state.apierror?.message);
|
|
21
11
|
let location = useLocation();
|
|
@@ -32,7 +22,18 @@ const Unauthorized = () => {
|
|
|
32
22
|
defaultMessage="You are trying to access a protected resource, please {login} first."
|
|
33
23
|
values={{
|
|
34
24
|
login: (
|
|
35
|
-
<Link
|
|
25
|
+
<Link
|
|
26
|
+
to={{
|
|
27
|
+
pathname: `${getBaseUrl(location.pathname)}/login`,
|
|
28
|
+
state: {
|
|
29
|
+
// This is needed to cover the use case of being logged in in
|
|
30
|
+
// another backend (eg. in development), having a token for
|
|
31
|
+
// localhost and try to use it, the login route has to know that
|
|
32
|
+
// it's the same as it comes from a logout
|
|
33
|
+
isLogout: true,
|
|
34
|
+
},
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
36
37
|
<FormattedMessage id="log in" defaultMessage="log in" />
|
|
37
38
|
</Link>
|
|
38
39
|
),
|
|
@@ -563,7 +563,7 @@ export const styleToClassName = (key, value, prefix = '') => {
|
|
|
563
563
|
};
|
|
564
564
|
|
|
565
565
|
export const buildStyleClassNamesFromData = (obj = {}, prefix = '') => {
|
|
566
|
-
//
|
|
566
|
+
// style wrapper object has the form:
|
|
567
567
|
// const styles = {
|
|
568
568
|
// color: 'red',
|
|
569
569
|
// backgroundColor: '#AABBCC',
|
|
@@ -571,6 +571,7 @@ export const buildStyleClassNamesFromData = (obj = {}, prefix = '') => {
|
|
|
571
571
|
// Returns: ['has--color--red', 'has--backgroundColor--AABBCC']
|
|
572
572
|
|
|
573
573
|
return Object.entries(obj)
|
|
574
|
+
.filter(([k, v]) => !k.startsWith('--'))
|
|
574
575
|
.reduce(
|
|
575
576
|
(acc, [k, v]) => [
|
|
576
577
|
...acc,
|
|
@@ -602,6 +603,68 @@ export const buildStyleClassNamesExtenders = ({
|
|
|
602
603
|
);
|
|
603
604
|
};
|
|
604
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Converts a name+value style pair (ex: color/red) to a pair of [k, v],
|
|
608
|
+
* such as ["color", "red"] so it can be converted back to an object.
|
|
609
|
+
* For now, only covering the 'CSSProperty' use case.
|
|
610
|
+
*/
|
|
611
|
+
export const styleDataToStyleObject = (key, value, prefix = '') => {
|
|
612
|
+
if (prefix) {
|
|
613
|
+
return [`--${prefix}${key.replace('--', '')}`, value];
|
|
614
|
+
} else {
|
|
615
|
+
return [key, value];
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Generate styles object from data
|
|
621
|
+
*
|
|
622
|
+
* @function buildStyleObjectFromData
|
|
623
|
+
* @param {Object} obj A style wrapper object data
|
|
624
|
+
* @param {string} prefix The prefix (could be dragged from a recursive call, initially empty)
|
|
625
|
+
* @return {Object} The style object ready to be passed as prop
|
|
626
|
+
*/
|
|
627
|
+
export const buildStyleObjectFromData = (obj = {}, prefix = '') => {
|
|
628
|
+
// style wrapper object has the form:
|
|
629
|
+
// const styles = {
|
|
630
|
+
// color: 'red',
|
|
631
|
+
// '--background-color': '#AABBCC',
|
|
632
|
+
// }
|
|
633
|
+
// Returns: {'--background-color: '#AABBCC'}
|
|
634
|
+
|
|
635
|
+
return Object.fromEntries(
|
|
636
|
+
Object.entries(obj)
|
|
637
|
+
.filter(([k, v]) => k.startsWith('--') || isObject(v))
|
|
638
|
+
.reduce(
|
|
639
|
+
(acc, [k, v]) => [
|
|
640
|
+
...acc,
|
|
641
|
+
// Kept for easy debugging
|
|
642
|
+
// ...(() => {
|
|
643
|
+
// if (isObject(v)) {
|
|
644
|
+
// return Object.entries(
|
|
645
|
+
// buildStyleObjectFromData(
|
|
646
|
+
// v,
|
|
647
|
+
// `${k.endsWith(':noprefix') ? '' : `${prefix}${k}--`}`,
|
|
648
|
+
// ),
|
|
649
|
+
// );
|
|
650
|
+
// }
|
|
651
|
+
// return [styleDataToStyleObject(k, v, prefix)];
|
|
652
|
+
// })(),
|
|
653
|
+
...(isObject(v)
|
|
654
|
+
? Object.entries(
|
|
655
|
+
buildStyleObjectFromData(
|
|
656
|
+
v,
|
|
657
|
+
`${k.endsWith(':noprefix') ? '' : `${prefix}${k}--`}`, // We don't add a prefix if the key ends with the marker suffix
|
|
658
|
+
),
|
|
659
|
+
)
|
|
660
|
+
: [styleDataToStyleObject(k, v, prefix)]),
|
|
661
|
+
],
|
|
662
|
+
[],
|
|
663
|
+
)
|
|
664
|
+
.filter((v) => !!v),
|
|
665
|
+
);
|
|
666
|
+
};
|
|
667
|
+
|
|
605
668
|
/**
|
|
606
669
|
* Return previous/next blocks given the content object and the current block id
|
|
607
670
|
*
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
applySchemaDefaults,
|
|
19
19
|
buildStyleClassNamesFromData,
|
|
20
20
|
buildStyleClassNamesExtenders,
|
|
21
|
+
buildStyleObjectFromData,
|
|
21
22
|
getPreviousNextBlock,
|
|
22
23
|
blocksFormGenerator,
|
|
23
24
|
findBlocks,
|
|
@@ -1066,6 +1067,82 @@ describe('Blocks', () => {
|
|
|
1066
1067
|
};
|
|
1067
1068
|
expect(buildStyleClassNamesFromData(styles)).toEqual([]);
|
|
1068
1069
|
});
|
|
1070
|
+
|
|
1071
|
+
it('It does not output any className for style converter values', () => {
|
|
1072
|
+
const styles = {
|
|
1073
|
+
color: 'red',
|
|
1074
|
+
'--background-color': '#FFF',
|
|
1075
|
+
};
|
|
1076
|
+
expect(buildStyleClassNamesFromData(styles)).toEqual(['has--color--red']);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
it.skip('It does not output any className for unknown converter values', () => {
|
|
1080
|
+
const styles = {
|
|
1081
|
+
color: 'red',
|
|
1082
|
+
'backgroundColor:style': '#FFF',
|
|
1083
|
+
};
|
|
1084
|
+
expect(buildStyleClassNamesFromData(styles)).toEqual(['has--color--red']);
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
describe('buildStyleObjectFromData', () => {
|
|
1089
|
+
it('Understands style converter for style values, no styles found', () => {
|
|
1090
|
+
const styles = {
|
|
1091
|
+
color: 'red',
|
|
1092
|
+
backgroundColor: '#FFF',
|
|
1093
|
+
};
|
|
1094
|
+
expect(buildStyleObjectFromData(styles)).toEqual({});
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it('Understands style converter for style values', () => {
|
|
1098
|
+
const styles = {
|
|
1099
|
+
color: 'red',
|
|
1100
|
+
'--background-color': '#FFF',
|
|
1101
|
+
};
|
|
1102
|
+
expect(buildStyleObjectFromData(styles)).toEqual({
|
|
1103
|
+
'--background-color': '#FFF',
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('Supports multiple nested levels', () => {
|
|
1108
|
+
const styles = {
|
|
1109
|
+
'--color': 'red',
|
|
1110
|
+
backgroundColor: '#AABBCC',
|
|
1111
|
+
nested: {
|
|
1112
|
+
l1: 'white',
|
|
1113
|
+
'--foo': 'white',
|
|
1114
|
+
level2: {
|
|
1115
|
+
'--foo': '#fff',
|
|
1116
|
+
bar: '#000',
|
|
1117
|
+
},
|
|
1118
|
+
},
|
|
1119
|
+
};
|
|
1120
|
+
expect(buildStyleObjectFromData(styles)).toEqual({
|
|
1121
|
+
'--color': 'red',
|
|
1122
|
+
'--nested--foo': 'white',
|
|
1123
|
+
'--nested--level2--foo': '#fff',
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it('Supports multiple nested levels and optional inclusion of the name of the level', () => {
|
|
1128
|
+
const styles = {
|
|
1129
|
+
'--color': 'red',
|
|
1130
|
+
backgroundColor: '#AABBCC',
|
|
1131
|
+
'nested:noprefix': {
|
|
1132
|
+
l1: 'white',
|
|
1133
|
+
'--foo': 'white',
|
|
1134
|
+
level2: {
|
|
1135
|
+
'--foo': '#fff',
|
|
1136
|
+
bar: '#000',
|
|
1137
|
+
},
|
|
1138
|
+
},
|
|
1139
|
+
};
|
|
1140
|
+
expect(buildStyleObjectFromData(styles)).toEqual({
|
|
1141
|
+
'--color': 'red',
|
|
1142
|
+
'--foo': 'white',
|
|
1143
|
+
'--level2--foo': '#fff',
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1069
1146
|
});
|
|
1070
1147
|
|
|
1071
1148
|
describe('getPreviousNextBlock', () => {
|
package/src/helpers/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export function View({ className, data, detached, properties }: {
|
|
1
|
+
export function View({ className, data, detached, properties, style }: {
|
|
2
2
|
className: any;
|
|
3
3
|
data: any;
|
|
4
4
|
detached: any;
|
|
5
5
|
properties: any;
|
|
6
|
+
style: any;
|
|
6
7
|
}): JSX.Element;
|
|
7
8
|
export namespace View {
|
|
8
9
|
namespace propTypes {
|
|
@@ -146,4 +146,6 @@ export function getBlocks(properties: any): any[];
|
|
|
146
146
|
export function styleToClassName(key: any, value: any, prefix?: string): any;
|
|
147
147
|
export function buildStyleClassNamesFromData(obj?: {}, prefix?: string): any;
|
|
148
148
|
export function buildStyleClassNamesExtenders({ block, content, data, classNames, }: any): any[];
|
|
149
|
+
export function styleDataToStyleObject(key: any, value: any, prefix?: string): any[];
|
|
150
|
+
export function buildStyleObjectFromData(obj?: any, prefix?: string): any;
|
|
149
151
|
export function getPreviousNextBlock({ content, block }: any): any[];
|