@kitconcept/volto-light-theme 8.0.0-alpha.2 → 8.0.0-alpha.21
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.draft +3 -4
- package/CHANGELOG.md +232 -0
- package/locales/de/LC_MESSAGES/volto.po +30 -115
- package/locales/en/LC_MESSAGES/volto.po +30 -115
- package/locales/es/LC_MESSAGES/volto.po +31 -116
- package/locales/eu/LC_MESSAGES/volto.po +58 -124
- package/locales/pt_BR/LC_MESSAGES/volto.po +38 -123
- package/locales/volto.pot +31 -116
- package/package.json +7 -4
- package/src/__mocks__/semantic-ui-react.ts +31 -0
- package/src/components/Blocks/Block/Edit.jsx +14 -6
- package/src/components/Blocks/Block/EditBlockWrapper.jsx +9 -3
- package/src/components/Blocks/Block/ErrorBoundary.test.tsx +55 -0
- package/src/components/Blocks/Block/ErrorBoundary.tsx +92 -0
- package/src/components/Blocks/Block/ErrorBoundaryMessage.tsx +66 -0
- package/src/components/Blocks/EventCalendar/Search/components/EventTemplate.tsx +1 -1
- package/src/components/Blocks/Image/Edit.jsx +1 -0
- package/src/components/Blocks/Listing/DefaultTemplate.jsx +12 -6
- package/src/components/Blocks/Listing/GridTemplate.jsx +16 -7
- package/src/components/Blocks/Listing/ListingBody.jsx +4 -1
- package/src/components/Blocks/Listing/SummaryTemplate.jsx +16 -7
- package/src/components/Blocks/Teaser/DefaultBody.tsx +25 -5
- package/src/components/Blocks/schema.ts +69 -0
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +128 -0
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +117 -0
- package/src/components/Caption/Caption.test.tsx +31 -0
- package/src/components/Caption/{Caption.jsx → Caption.tsx} +14 -21
- package/src/components/Footer/ColumnLinks.tsx +2 -2
- package/src/components/Footer/slots/Colophon.tsx +13 -1
- package/src/components/Footer/slots/CoreFooter.tsx +4 -2
- package/src/components/Header/Header.tsx +3 -3
- package/src/components/LanguageSelector/LanguageSelector.tsx +91 -0
- package/src/components/MobileNavigation/MobileNavigation.jsx +11 -9
- package/src/components/Navigation/Navigation.test.tsx +176 -0
- package/src/components/Navigation/{Navigation.jsx → Navigation.tsx} +77 -37
- package/src/components/StickyMenu/MobileCarouselArrowButton.tsx +81 -0
- package/src/components/StickyMenu/MobileStickyMenu.tsx +76 -0
- package/src/components/Summary/DefaultSummary.tsx +10 -3
- package/src/components/Summary/EventSummary.tsx +10 -3
- package/src/components/Summary/FileSummary.tsx +10 -3
- package/src/components/Summary/NewsItemSummary.tsx +10 -3
- package/src/components/Summary/PersonSummary.tsx +10 -3
- package/src/components/Summary/Summary.stories.tsx +46 -30
- package/src/components/Tags/Tags.test.tsx +71 -0
- package/src/components/Tags/{Tags.jsx → Tags.tsx} +9 -25
- package/src/components/Theme/EventView.jsx +4 -4
- package/src/components/Theme/NewsItemView.jsx +4 -4
- package/src/components/Theme/RenderBlocks.jsx +45 -37
- package/src/components/Theme/RenderBlocksV2.jsx +51 -20
- package/src/components/Widgets/ColorSwatch.stories.tsx +197 -0
- package/src/components/Widgets/ColorSwatch.test.tsx +188 -0
- package/src/components/Widgets/ColorSwatch.tsx +77 -39
- package/src/components/Widgets/SoftTextWidget.tsx +129 -0
- package/src/components/Widgets/SoftTextareaWidget.tsx +118 -0
- package/src/components/Widgets/ThemeColorSwatch.tsx +5 -9
- package/src/config/blocks.tsx +21 -29
- package/src/config/slots.ts +7 -0
- package/src/config/widgets.ts +5 -9
- package/src/customizations/volto/components/manage/DragDropList/DragDropList.jsx +263 -0
- package/src/customizations/volto/components/theme/LanguageSelector/LanguageSelector.tsx +10 -0
- package/src/helpers/styleDefinitions.test.tsx +30 -0
- package/src/helpers/styleDefinitions.ts +49 -0
- package/src/internalChecks.test.ts +94 -0
- package/src/primitives/Card/Card.stories.tsx +4 -1
- package/src/primitives/Card/Card.test.tsx +11 -33
- package/src/primitives/Card/Card.tsx +33 -43
- package/src/primitives/IconLinkList.tsx +53 -52
- package/src/theme/_bgcolor-blocks-layout.scss +43 -45
- package/src/theme/_content.scss +12 -13
- package/src/theme/_export_import.scss +94 -0
- package/src/theme/_footer.scss +64 -19
- package/src/theme/_header.scss +21 -4
- package/src/theme/_insets.scss +1 -1
- package/src/theme/_layout.scss +34 -15
- package/src/theme/_mobile-sticky-menu.scss +92 -0
- package/src/theme/_search-page.scss +249 -0
- package/src/theme/_typo-custom.scss +16 -5
- package/src/theme/_variables.scss +19 -4
- package/src/theme/_widgets.scss +15 -27
- package/src/theme/blocks/_accordion.scss +11 -4
- package/src/theme/blocks/_grid.scss +9 -77
- package/src/theme/blocks/_listing.scss +60 -126
- package/src/theme/blocks/_search.scss +3 -4
- package/src/theme/blocks/_table.scss +1 -0
- package/src/theme/blocks/_teaser.scss +7 -117
- package/src/theme/blocks/error-boundary.scss +11 -0
- package/src/theme/card.scss +107 -70
- package/src/theme/main.scss +5 -0
- package/src/theme/notfound.scss +27 -0
- package/src/theme/person.scss +28 -12
- package/src/theme/sticky-menu.scss +7 -5
- package/src/types.d.ts +1 -0
- package/vitest.config.mjs +4 -0
- package/razzle.extend.js +0 -38
- package/src/components/Blocks/schema.js +0 -44
- package/src/components/Breadcrumbs/Breadcrumbs.jsx +0 -118
- package/src/components/Widgets/AlignWidget.tsx +0 -84
- package/src/components/Widgets/BlockAlignment.tsx +0 -88
- package/src/components/Widgets/BlockWidth.tsx +0 -101
- package/src/components/Widgets/Buttons.tsx +0 -144
- package/src/components/Widgets/Size.tsx +0 -78
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Label, TextArea } from 'semantic-ui-react';
|
|
4
|
+
import { injectIntl } from 'react-intl';
|
|
5
|
+
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
6
|
+
/**
|
|
7
|
+
* TextareaWidget, a widget for multiple lines text
|
|
8
|
+
*
|
|
9
|
+
* To use it, in schema properties, declare a field like:
|
|
10
|
+
*
|
|
11
|
+
* ```jsx
|
|
12
|
+
* {
|
|
13
|
+
* title: "Text",
|
|
14
|
+
* widget: 'textarea',
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const SoftTextareaWidget = (props) => {
|
|
19
|
+
const {
|
|
20
|
+
id,
|
|
21
|
+
maxLength,
|
|
22
|
+
value,
|
|
23
|
+
onChange,
|
|
24
|
+
placeholder,
|
|
25
|
+
isDisabled,
|
|
26
|
+
softMaxLength,
|
|
27
|
+
} = props;
|
|
28
|
+
const [lengthError, setlengthError] = useState('');
|
|
29
|
+
const [softLengthWarning, setSoftLengthWarning] = useState('');
|
|
30
|
+
const onhandleChange = (id, value) => {
|
|
31
|
+
if (maxLength && value?.length) {
|
|
32
|
+
let remlength = maxLength - value.length;
|
|
33
|
+
if (remlength < 0) {
|
|
34
|
+
setlengthError(
|
|
35
|
+
`You have exceeded word limit by ${Math.abs(remlength)}`,
|
|
36
|
+
);
|
|
37
|
+
} else {
|
|
38
|
+
setlengthError('');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//START CUSTOMIZATION
|
|
42
|
+
if (softMaxLength && value?.length) {
|
|
43
|
+
let remaining = softMaxLength - value.length;
|
|
44
|
+
if (remaining < 0) {
|
|
45
|
+
setSoftLengthWarning(
|
|
46
|
+
`You have exceeded the recommended limit by ${Math.abs(remaining)}`,
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
setSoftLengthWarning('');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//END CUSTOMIZATION
|
|
53
|
+
onChange(id, value);
|
|
54
|
+
};
|
|
55
|
+
return (
|
|
56
|
+
<FormFieldWrapper {...props} className="textarea">
|
|
57
|
+
<TextArea
|
|
58
|
+
id={`field-${id}`}
|
|
59
|
+
name={id}
|
|
60
|
+
value={value || ''}
|
|
61
|
+
disabled={isDisabled}
|
|
62
|
+
placeholder={placeholder}
|
|
63
|
+
onChange={({ target }) =>
|
|
64
|
+
onhandleChange(id, target.value === '' ? undefined : target.value)
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
{/* START CUSTOMIZATION */}
|
|
68
|
+
{softLengthWarning.length > 0 && (
|
|
69
|
+
<Label key={softLengthWarning} basic color="yellow" pointing>
|
|
70
|
+
{softLengthWarning}
|
|
71
|
+
</Label>
|
|
72
|
+
)}
|
|
73
|
+
{/* END CUSTOMIZATION */}
|
|
74
|
+
{lengthError.length > 0 && (
|
|
75
|
+
<Label key={lengthError} basic color="red" pointing>
|
|
76
|
+
{lengthError}
|
|
77
|
+
</Label>
|
|
78
|
+
)}
|
|
79
|
+
</FormFieldWrapper>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Property types.
|
|
84
|
+
* @property {Object} propTypes Property types.
|
|
85
|
+
* @static
|
|
86
|
+
*/
|
|
87
|
+
SoftTextareaWidget.propTypes = {
|
|
88
|
+
id: PropTypes.string.isRequired,
|
|
89
|
+
title: PropTypes.string.isRequired,
|
|
90
|
+
description: PropTypes.string,
|
|
91
|
+
maxLength: PropTypes.number,
|
|
92
|
+
softMaxLength: PropTypes.number,
|
|
93
|
+
required: PropTypes.bool,
|
|
94
|
+
error: PropTypes.arrayOf(PropTypes.string),
|
|
95
|
+
value: PropTypes.string,
|
|
96
|
+
onChange: PropTypes.func,
|
|
97
|
+
onEdit: PropTypes.func,
|
|
98
|
+
onDelete: PropTypes.func,
|
|
99
|
+
wrapped: PropTypes.bool,
|
|
100
|
+
placeholder: PropTypes.string,
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Default properties.
|
|
104
|
+
* @property {Object} defaultProps Default properties.
|
|
105
|
+
* @static
|
|
106
|
+
*/
|
|
107
|
+
SoftTextareaWidget.defaultProps = {
|
|
108
|
+
description: null,
|
|
109
|
+
maxLength: null,
|
|
110
|
+
softMaxLength: null,
|
|
111
|
+
required: false,
|
|
112
|
+
error: [],
|
|
113
|
+
value: null,
|
|
114
|
+
onChange: null,
|
|
115
|
+
onEdit: null,
|
|
116
|
+
onDelete: null,
|
|
117
|
+
};
|
|
118
|
+
export default injectIntl(SoftTextareaWidget);
|
|
@@ -2,16 +2,12 @@ import ColorSwatchWidget from './ColorSwatch';
|
|
|
2
2
|
import config from '@plone/volto/registry';
|
|
3
3
|
import type { ColorSwatchProps } from './ColorSwatch';
|
|
4
4
|
|
|
5
|
-
const ThemeColorSwatch = (
|
|
6
|
-
|
|
5
|
+
const ThemeColorSwatch = (
|
|
6
|
+
props: Omit<ColorSwatchProps, 'themes' | 'colors'>,
|
|
7
|
+
) => {
|
|
8
|
+
const themes: ColorSwatchProps['themes'] = config.blocks.themes;
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
(color) => color.name === config.settings.defaultBackgroundColor,
|
|
10
|
-
)?.style;
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<ColorSwatchWidget {...props} default={defaultValue} colors={colors} />
|
|
14
|
-
);
|
|
10
|
+
return <ColorSwatchWidget {...props} themes={themes} />;
|
|
15
11
|
};
|
|
16
12
|
|
|
17
13
|
export default ThemeColorSwatch;
|
package/src/config/blocks.tsx
CHANGED
|
@@ -4,8 +4,12 @@ import type { StyleDefinition } from '@plone/types';
|
|
|
4
4
|
import cloneDeep from 'lodash/cloneDeep';
|
|
5
5
|
|
|
6
6
|
import { composeSchema } from '@plone/volto/helpers/Extensions';
|
|
7
|
-
import { findStyleByName } from '@plone/volto/helpers/Blocks/Blocks';
|
|
8
7
|
import { defaultStylingSchema } from '../components/Blocks/schema';
|
|
8
|
+
import {
|
|
9
|
+
blockThemesEnhancer,
|
|
10
|
+
styleDefinitionsEnhancer,
|
|
11
|
+
} from '../helpers/styleDefinitions';
|
|
12
|
+
|
|
9
13
|
import {
|
|
10
14
|
gridTeaserDisableStylingSchema,
|
|
11
15
|
teaserSchemaEnhancer,
|
|
@@ -40,7 +44,6 @@ import { tocBlockSchemaEnhancer } from '../components/Blocks/Toc/schema';
|
|
|
40
44
|
import { mapsBlockSchemaEnhancer } from '../components/Blocks/Maps/schema';
|
|
41
45
|
import { sliderBlockSchemaEnhancer } from '../components/Blocks/Slider/schema';
|
|
42
46
|
import EventMetadataView from '../components/Blocks/EventMetadata/View';
|
|
43
|
-
import isEmpty from 'lodash/isEmpty';
|
|
44
47
|
|
|
45
48
|
import SearchBlockViewEvent from '../components/Blocks/EventCalendar/Search/SearchBlockView';
|
|
46
49
|
import SearchBlockEditEvent from '../components/Blocks/EventCalendar/Search/SearchBlockEdit';
|
|
@@ -51,6 +54,7 @@ declare module '@plone/types' {
|
|
|
51
54
|
export interface BlocksConfigData {
|
|
52
55
|
introduction: BlockConfigBase;
|
|
53
56
|
heading: BlockConfigBase;
|
|
57
|
+
banner: BlockConfigBase;
|
|
54
58
|
__button: BlockConfigBase;
|
|
55
59
|
separator: BlockConfigBase;
|
|
56
60
|
slider: BlockConfigBase;
|
|
@@ -62,6 +66,7 @@ declare module '@plone/types' {
|
|
|
62
66
|
}
|
|
63
67
|
export interface BlockConfigBase {
|
|
64
68
|
themes?: StyleDefinition[];
|
|
69
|
+
defaultTheme: string;
|
|
65
70
|
allowedBlocks?: string[];
|
|
66
71
|
allowed_headline_tags?: string[][];
|
|
67
72
|
unwantedButtons?: string[];
|
|
@@ -142,38 +147,18 @@ export default function install(config: ConfigType) {
|
|
|
142
147
|
},
|
|
143
148
|
];
|
|
144
149
|
|
|
145
|
-
function blockThemesEnhancer({ data, container }) {
|
|
146
|
-
if (!data['@type']) return {};
|
|
147
|
-
const blockConfig = config.blocks.blocksConfig[data['@type']];
|
|
148
|
-
if (!blockConfig) return {};
|
|
149
|
-
const blockStyleDefinitions =
|
|
150
|
-
// We look up for the blockThemes in the block's config, then in the global config
|
|
151
|
-
// We keep `colors` for BBB, but `themes` should be used
|
|
152
|
-
blockConfig.themes || blockConfig.colors || config.blocks.themes || [];
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
!isEmpty(container) &&
|
|
156
|
-
container.theme &&
|
|
157
|
-
(!data.theme || data.theme === 'default')
|
|
158
|
-
) {
|
|
159
|
-
return findStyleByName(blockStyleDefinitions, container.theme);
|
|
160
|
-
}
|
|
161
|
-
if (data.theme) {
|
|
162
|
-
return data.theme
|
|
163
|
-
? findStyleByName(blockStyleDefinitions, data.theme)
|
|
164
|
-
: {};
|
|
165
|
-
} else {
|
|
166
|
-
// No theme, return default color
|
|
167
|
-
return findStyleByName(config.blocks.themes, 'default');
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
150
|
config.registerUtility({
|
|
172
151
|
name: 'blockThemesEnhancer',
|
|
173
152
|
type: 'styleWrapperStyleObjectEnhancer',
|
|
174
153
|
method: blockThemesEnhancer,
|
|
175
154
|
});
|
|
176
155
|
|
|
156
|
+
config.registerUtility({
|
|
157
|
+
name: 'styleDefinitionsEnhancer',
|
|
158
|
+
type: 'styleWrapperStyleObjectEnhancer',
|
|
159
|
+
method: styleDefinitionsEnhancer,
|
|
160
|
+
});
|
|
161
|
+
|
|
177
162
|
// No required blocks except eventMetadata
|
|
178
163
|
config.blocks.requiredBlocks = [
|
|
179
164
|
...config.blocks.requiredBlocks,
|
|
@@ -314,6 +299,10 @@ export default function install(config: ConfigType) {
|
|
|
314
299
|
),
|
|
315
300
|
);
|
|
316
301
|
|
|
302
|
+
config.blocks.blocksConfig.banner = {
|
|
303
|
+
...config.blocks.blocksConfig.banner,
|
|
304
|
+
schemaEnhancer: defaultStylingSchema,
|
|
305
|
+
};
|
|
317
306
|
config.blocks.blocksConfig.introduction = {
|
|
318
307
|
...config.blocks.blocksConfig.introduction,
|
|
319
308
|
unwantedButtons: ['heading-three', 'blockquote'],
|
|
@@ -416,7 +405,10 @@ export default function install(config: ConfigType) {
|
|
|
416
405
|
};
|
|
417
406
|
|
|
418
407
|
// Slider Block
|
|
419
|
-
config.blocks.blocksConfig.slider
|
|
408
|
+
config.blocks.blocksConfig.slider = {
|
|
409
|
+
...config.blocks.blocksConfig.slider,
|
|
410
|
+
schemaEnhancer: sliderBlockSchemaEnhancer,
|
|
411
|
+
};
|
|
420
412
|
|
|
421
413
|
return config;
|
|
422
414
|
}
|
package/src/config/slots.ts
CHANGED
|
@@ -5,6 +5,7 @@ import FollowUsLogoAndLinks from '../components/Footer/slots/FollowUsLogoAndLink
|
|
|
5
5
|
import Colophon from '../components/Footer/slots/Colophon';
|
|
6
6
|
import CoreFooter from '../components/Footer/slots/CoreFooter';
|
|
7
7
|
import StickyMenu from '../components/StickyMenu/StickyMenu';
|
|
8
|
+
import MobileStickyMenu from '../components/StickyMenu/MobileStickyMenu';
|
|
8
9
|
import Anontools from '../components/Anontools/Anontools';
|
|
9
10
|
import type { Content } from '@plone/types';
|
|
10
11
|
|
|
@@ -57,5 +58,11 @@ export default function install(config: ConfigType) {
|
|
|
57
58
|
component: Colophon,
|
|
58
59
|
});
|
|
59
60
|
|
|
61
|
+
config.registerSlotComponent({
|
|
62
|
+
name: 'MobileStickyMenu',
|
|
63
|
+
slot: 'preFooter',
|
|
64
|
+
component: MobileStickyMenu,
|
|
65
|
+
});
|
|
66
|
+
|
|
60
67
|
return config;
|
|
61
68
|
}
|
package/src/config/widgets.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { ConfigType } from '@plone/registry';
|
|
2
|
-
import BlockWidth from '../components/Widgets/BlockWidth';
|
|
3
|
-
import BlockAlignment from '../components/Widgets/BlockAlignment';
|
|
4
2
|
import ColorSwatch from '../components/Widgets/ColorSwatch';
|
|
5
|
-
import Size from '../components/Widgets/Size';
|
|
6
3
|
import ColorPicker from '../components/Widgets/ColorPicker';
|
|
7
4
|
import ThemeColorSwatch from '../components/Widgets/ThemeColorSwatch';
|
|
8
5
|
// import BlocksObject from '../components/Widgets/BlocksObject';
|
|
@@ -12,7 +9,8 @@ import { footerLogosSchema } from '../components/Widgets/schema/footerLogosSchem
|
|
|
12
9
|
import { footerLinksSchema } from '../components/Widgets/schema/footerLinksSchema';
|
|
13
10
|
import { iconLinkListSchema } from '../components/Widgets/schema/iconLinkListSchema';
|
|
14
11
|
import ModalJSONEditor from '../components/Widgets/ModalJSONEditor';
|
|
15
|
-
import
|
|
12
|
+
import SoftTextWidget from '../components/Widgets/SoftTextWidget';
|
|
13
|
+
import SoftTextareaWidget from '../components/Widgets/SoftTextareaWidget';
|
|
16
14
|
|
|
17
15
|
export default function install(config: ConfigType) {
|
|
18
16
|
// Color picker widget override - use our own non-semanticUI widget
|
|
@@ -20,21 +18,19 @@ export default function install(config: ConfigType) {
|
|
|
20
18
|
// `color_picker` is a terrible name for this widget, it should be `colorSwatch`
|
|
21
19
|
// ToDo: Rename it in Volto 19
|
|
22
20
|
config.widgets.widget.color_picker = ColorSwatch;
|
|
21
|
+
config.widgets.widget.colorSwatch = ColorSwatch;
|
|
23
22
|
|
|
24
23
|
// ObjectList widget override - use our own non-semanticUI widget
|
|
25
24
|
// it uses also dnd-kit for drag and drop
|
|
26
25
|
config.widgets.widget.object_list = ObjectList;
|
|
27
26
|
|
|
28
|
-
config.widgets.widget.align = AlignWidget;
|
|
29
|
-
|
|
30
27
|
// Force Preview image link widget to the image widget
|
|
31
28
|
config.widgets.id.preview_image_link = config.widgets.widget.image;
|
|
32
29
|
|
|
33
|
-
config.widgets.widget.blockWidth = BlockWidth;
|
|
34
|
-
config.widgets.widget.blockAlignment = BlockAlignment;
|
|
35
30
|
config.widgets.widget.colorPicker = ColorPicker;
|
|
36
|
-
config.widgets.widget.size = Size;
|
|
37
31
|
config.widgets.widget.themeColorSwatch = ThemeColorSwatch;
|
|
32
|
+
config.widgets.widget.softTextWidget = SoftTextWidget;
|
|
33
|
+
config.widgets.widget.softTextareaWidget = SoftTextareaWidget;
|
|
38
34
|
|
|
39
35
|
config.widgets.widget.modalJSONEditor = ModalJSONEditor;
|
|
40
36
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OVERRIDE: DragDropList.jsx
|
|
3
|
+
* REASON: Adding BlockModelv3 guards and customizations.
|
|
4
|
+
* Not moved to core yet, and as it's temporary, we keep it here.
|
|
5
|
+
* DATE: 2025-12-01
|
|
6
|
+
* DEVELOPER: @sneridagh
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useRef } from 'react';
|
|
10
|
+
import isEmpty from 'lodash/isEmpty';
|
|
11
|
+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
12
|
+
import { v4 as uuid } from 'uuid';
|
|
13
|
+
import config from '@plone/volto/registry';
|
|
14
|
+
|
|
15
|
+
const getVerticalPlaceholder = (
|
|
16
|
+
draggedDOM,
|
|
17
|
+
sourceIndex,
|
|
18
|
+
destinationIndex,
|
|
19
|
+
parentDOM,
|
|
20
|
+
) => {
|
|
21
|
+
// Because of the margin rendering rules, there is no easy
|
|
22
|
+
// way to calculate the offset of the placeholder.
|
|
23
|
+
//
|
|
24
|
+
// (Note that this is the reason we cannot use the solutions
|
|
25
|
+
// published on the net, because they assume that we are in control
|
|
26
|
+
// of the content and there are no additional margins involved.)
|
|
27
|
+
//
|
|
28
|
+
// To get a placeholder that looks good in all cases, we
|
|
29
|
+
// fill up the space between the previous and the next element.
|
|
30
|
+
const childrenArray = [...parentDOM.children];
|
|
31
|
+
// Remove the source element
|
|
32
|
+
childrenArray.splice(sourceIndex, 1);
|
|
33
|
+
// Also remove the placeholder that the library always inserts at the end
|
|
34
|
+
childrenArray.splice(-1, 1);
|
|
35
|
+
const parentRect = parentDOM.getBoundingClientRect();
|
|
36
|
+
const prevNode = childrenArray[destinationIndex - 1];
|
|
37
|
+
const nextNode = childrenArray[destinationIndex];
|
|
38
|
+
let top, bottom;
|
|
39
|
+
if (prevNode) {
|
|
40
|
+
const prevRect = prevNode.getBoundingClientRect();
|
|
41
|
+
top = prevRect.top + prevRect.height - parentRect.top;
|
|
42
|
+
} else {
|
|
43
|
+
top = 0;
|
|
44
|
+
}
|
|
45
|
+
if (nextNode) {
|
|
46
|
+
const nextRect = nextNode.getBoundingClientRect();
|
|
47
|
+
bottom = nextRect.top - parentRect.top;
|
|
48
|
+
} else {
|
|
49
|
+
bottom =
|
|
50
|
+
parentRect.bottom +
|
|
51
|
+
draggedDOM.getBoundingClientRect().height -
|
|
52
|
+
parentRect.top;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
clientY: top,
|
|
56
|
+
clientHeight: bottom - top,
|
|
57
|
+
clientX: parseFloat(window.getComputedStyle(parentDOM).paddingLeft),
|
|
58
|
+
clientWidth: draggedDOM.clientWidth,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getHorizontalPlaceholder = (draggedDOM, destinationIndex, parentDOM) => {
|
|
63
|
+
// The next element will be the one with a gray placeholder behind it.
|
|
64
|
+
//
|
|
65
|
+
// If the element is the last (doesn't have a next element), the whole draggable
|
|
66
|
+
// area will be highlighted
|
|
67
|
+
|
|
68
|
+
const parentRect = parentDOM.getBoundingClientRect();
|
|
69
|
+
let height;
|
|
70
|
+
|
|
71
|
+
height = parentRect.bottom - parentRect.top;
|
|
72
|
+
|
|
73
|
+
let clientWidth = draggedDOM.clientWidth;
|
|
74
|
+
|
|
75
|
+
let innerDroppablePlaceholderMarginLeft =
|
|
76
|
+
parentDOM.children[destinationIndex].getBoundingClientRect().x;
|
|
77
|
+
if (parentDOM.children.length - 1 === destinationIndex) {
|
|
78
|
+
innerDroppablePlaceholderMarginLeft = parentRect.x;
|
|
79
|
+
clientWidth = parentDOM.getBoundingClientRect().width;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
clientY: 0,
|
|
83
|
+
clientHeight: height,
|
|
84
|
+
clientX: parseFloat(innerDroppablePlaceholderMarginLeft - parentRect.x),
|
|
85
|
+
clientWidth: clientWidth,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const DragDropList = (props) => {
|
|
90
|
+
const {
|
|
91
|
+
childList,
|
|
92
|
+
children,
|
|
93
|
+
direction = 'vertical',
|
|
94
|
+
onMoveItem,
|
|
95
|
+
as = 'div',
|
|
96
|
+
style,
|
|
97
|
+
forwardedAriaLabelledBy,
|
|
98
|
+
reactBeautifulDnd,
|
|
99
|
+
} = props; //renderChild
|
|
100
|
+
const { DragDropContext, Draggable, Droppable } = reactBeautifulDnd;
|
|
101
|
+
const [placeholderProps, setPlaceholderProps] = React.useState({});
|
|
102
|
+
const [uid] = React.useState(uuid());
|
|
103
|
+
// queueing timed action
|
|
104
|
+
const timer = useRef(null);
|
|
105
|
+
const parentRef = useRef(null);
|
|
106
|
+
const onDragStart = React.useCallback(
|
|
107
|
+
(event) => {
|
|
108
|
+
clearTimeout(timer.current);
|
|
109
|
+
const queryAttr = 'data-rbd-draggable-id';
|
|
110
|
+
const domQuery = `[${queryAttr}='${event.draggableId}']`;
|
|
111
|
+
const draggedDOM = document.querySelector(domQuery);
|
|
112
|
+
if (!draggedDOM) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const sourceIndex = event.source.index;
|
|
116
|
+
setPlaceholderProps(
|
|
117
|
+
direction === 'horizontal'
|
|
118
|
+
? getHorizontalPlaceholder(draggedDOM, sourceIndex, parentRef.current)
|
|
119
|
+
: getVerticalPlaceholder(
|
|
120
|
+
draggedDOM,
|
|
121
|
+
sourceIndex,
|
|
122
|
+
sourceIndex,
|
|
123
|
+
parentRef.current,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
[direction],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const onDragEnd = React.useCallback(
|
|
131
|
+
(result) => {
|
|
132
|
+
clearTimeout(timer.current);
|
|
133
|
+
onMoveItem(result);
|
|
134
|
+
setPlaceholderProps({});
|
|
135
|
+
},
|
|
136
|
+
[onMoveItem],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const onDragUpdate = React.useCallback(
|
|
140
|
+
(update) => {
|
|
141
|
+
clearTimeout(timer.current);
|
|
142
|
+
setPlaceholderProps({});
|
|
143
|
+
if (!update.destination) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const draggableId = update.draggableId;
|
|
147
|
+
const queryAttr = 'data-rbd-draggable-id';
|
|
148
|
+
const domQuery = `[${queryAttr}='${draggableId}']`;
|
|
149
|
+
const draggedDOM = document.querySelector(domQuery);
|
|
150
|
+
if (!draggedDOM) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const sourceIndex = update.source.index;
|
|
154
|
+
const destinationIndex = update.destination.index;
|
|
155
|
+
// Wait until the animations have finished, to make it look good.
|
|
156
|
+
timer.current = setTimeout(() => {
|
|
157
|
+
setPlaceholderProps(
|
|
158
|
+
direction === 'horizontal'
|
|
159
|
+
? getHorizontalPlaceholder(
|
|
160
|
+
draggedDOM,
|
|
161
|
+
destinationIndex,
|
|
162
|
+
parentRef.current,
|
|
163
|
+
)
|
|
164
|
+
: getVerticalPlaceholder(
|
|
165
|
+
draggedDOM,
|
|
166
|
+
sourceIndex,
|
|
167
|
+
destinationIndex,
|
|
168
|
+
parentRef.current,
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
}, 250);
|
|
172
|
+
},
|
|
173
|
+
[direction],
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const AsDomComponent = as;
|
|
177
|
+
return (
|
|
178
|
+
<DragDropContext
|
|
179
|
+
onDragStart={onDragStart}
|
|
180
|
+
onDragUpdate={onDragUpdate}
|
|
181
|
+
onDragEnd={onDragEnd}
|
|
182
|
+
>
|
|
183
|
+
<Droppable
|
|
184
|
+
droppableId={uid}
|
|
185
|
+
direction={direction}
|
|
186
|
+
renderClone={(provided, snapshot, rubric) => {
|
|
187
|
+
const index = rubric.source.index;
|
|
188
|
+
return children({
|
|
189
|
+
child: childList[index][1],
|
|
190
|
+
childId: childList[index][0],
|
|
191
|
+
index,
|
|
192
|
+
draginfo: provided,
|
|
193
|
+
});
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
{(provided, snapshot) => (
|
|
197
|
+
<AsDomComponent
|
|
198
|
+
ref={(el) => {
|
|
199
|
+
provided.innerRef(el);
|
|
200
|
+
parentRef.current = el;
|
|
201
|
+
}}
|
|
202
|
+
{...provided.droppableProps}
|
|
203
|
+
style={{ ...style, position: 'relative' }}
|
|
204
|
+
aria-labelledby={forwardedAriaLabelledBy}
|
|
205
|
+
>
|
|
206
|
+
{childList
|
|
207
|
+
.filter(([id, child]) => id && child) // beware numbers!
|
|
208
|
+
.map(([childId, child], index) => {
|
|
209
|
+
const dragDisabled =
|
|
210
|
+
config.blocks.blocksConfig?.[child?.['@type']]?.blockModel ===
|
|
211
|
+
3;
|
|
212
|
+
if (!dragDisabled) {
|
|
213
|
+
return (
|
|
214
|
+
<Draggable
|
|
215
|
+
draggableId={childId.toString()}
|
|
216
|
+
index={index}
|
|
217
|
+
key={childId}
|
|
218
|
+
isDragDisabled={!!dragDisabled}
|
|
219
|
+
style={{
|
|
220
|
+
userSelect: 'none',
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
{(draginfo) =>
|
|
224
|
+
children({ child, childId, index, draginfo })
|
|
225
|
+
}
|
|
226
|
+
</Draggable>
|
|
227
|
+
);
|
|
228
|
+
} else {
|
|
229
|
+
return (
|
|
230
|
+
<React.Fragment key={childId}>
|
|
231
|
+
{children({
|
|
232
|
+
child,
|
|
233
|
+
childId,
|
|
234
|
+
index,
|
|
235
|
+
draginfo: { draggableProps: {} },
|
|
236
|
+
})}
|
|
237
|
+
</React.Fragment>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
})}
|
|
241
|
+
{provided.placeholder}
|
|
242
|
+
{!isEmpty(placeholderProps) && snapshot.isDraggingOver && (
|
|
243
|
+
<div
|
|
244
|
+
style={{
|
|
245
|
+
position: 'absolute',
|
|
246
|
+
top: placeholderProps.clientY,
|
|
247
|
+
left: placeholderProps.clientX,
|
|
248
|
+
height: placeholderProps.clientHeight,
|
|
249
|
+
background: '#eee',
|
|
250
|
+
width: placeholderProps.clientWidth,
|
|
251
|
+
borderRadius: '3px',
|
|
252
|
+
...(direction === 'horizontal' && { zIndex: -1 }),
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
)}
|
|
256
|
+
</AsDomComponent>
|
|
257
|
+
)}
|
|
258
|
+
</Droppable>
|
|
259
|
+
</DragDropContext>
|
|
260
|
+
);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export default injectLazyLibs(['reactBeautifulDnd'])(DragDropList);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OVERRIDE LanguageSelector.tsx
|
|
3
|
+
* REASON: Show only two letters of the native language name.
|
|
4
|
+
* SemanticUI-free located at the components folder.
|
|
5
|
+
* To override it, override the @kitconcept/volto-light-theme one instead of
|
|
6
|
+
* this one.
|
|
7
|
+
*/
|
|
8
|
+
import LanguageSelector from '../../../../../components/LanguageSelector/LanguageSelector';
|
|
9
|
+
|
|
10
|
+
export default LanguageSelector;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { styleDefinitionsEnhancer } from './styleDefinitions';
|
|
2
|
+
import config from '@plone/volto/registry';
|
|
3
|
+
|
|
4
|
+
describe('styleDefinitionsEnhancer', () => {
|
|
5
|
+
it('should enhance style definitions correctly', () => {
|
|
6
|
+
const data = {
|
|
7
|
+
styles: {
|
|
8
|
+
backgroundColor: 'red',
|
|
9
|
+
textColor: 'blue',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
config.registerUtility({
|
|
14
|
+
type: 'styleFieldDefinition',
|
|
15
|
+
name: 'backgroundColor',
|
|
16
|
+
method: () => {
|
|
17
|
+
return [
|
|
18
|
+
{ name: 'red', label: 'Red', style: { '--bg-color': 'red' } },
|
|
19
|
+
{ name: 'green', label: 'Green', style: { '--bg-color': 'green' } },
|
|
20
|
+
];
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const container = {};
|
|
25
|
+
|
|
26
|
+
const result = styleDefinitionsEnhancer({ data, container });
|
|
27
|
+
|
|
28
|
+
expect(result).toEqual({ '--bg-color': 'red' });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import config from '@plone/volto/registry';
|
|
2
|
+
import { findStyleByName } from '@plone/volto/helpers/Blocks/Blocks';
|
|
3
|
+
import isEmpty from 'lodash/isEmpty';
|
|
4
|
+
|
|
5
|
+
export function blockThemesEnhancer({ data, container }) {
|
|
6
|
+
if (!data['@type']) return {};
|
|
7
|
+
const blockConfig = config.blocks.blocksConfig[data['@type']];
|
|
8
|
+
if (!blockConfig) return {};
|
|
9
|
+
const blockStyleDefinitions =
|
|
10
|
+
// We look up for the blockThemes in the block's config, then in the global config
|
|
11
|
+
// We keep `colors` for BBB, but `themes` should be used
|
|
12
|
+
blockConfig.themes || blockConfig.colors || config.blocks.themes || [];
|
|
13
|
+
|
|
14
|
+
if (
|
|
15
|
+
!isEmpty(container) &&
|
|
16
|
+
container.theme &&
|
|
17
|
+
(!data.theme || data.theme === 'default')
|
|
18
|
+
) {
|
|
19
|
+
return findStyleByName(blockStyleDefinitions, container.theme);
|
|
20
|
+
}
|
|
21
|
+
if (data.theme) {
|
|
22
|
+
return data.theme ? findStyleByName(blockStyleDefinitions, data.theme) : {};
|
|
23
|
+
} else {
|
|
24
|
+
// No theme, return default color
|
|
25
|
+
return findStyleByName(config.blocks.themes, 'default');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function styleDefinitionsEnhancer({ data, container }) {
|
|
30
|
+
let resultantStyles = {};
|
|
31
|
+
Object.keys(data.styles || {}).forEach((fieldName) => {
|
|
32
|
+
const styleFieldEnhancer = config.getUtility({
|
|
33
|
+
type: 'styleFieldDefinition',
|
|
34
|
+
name: fieldName,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (styleFieldEnhancer.method) {
|
|
38
|
+
resultantStyles = {
|
|
39
|
+
...resultantStyles,
|
|
40
|
+
...findStyleByName(
|
|
41
|
+
styleFieldEnhancer.method({ data, container }),
|
|
42
|
+
data.styles[fieldName],
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return resultantStyles;
|
|
49
|
+
}
|