@eeacms/volto-n2k 1.0.17 → 1.0.18
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 +10 -2
- package/package.json +1 -1
- package/src/components/manage/Blocks/HabitatsBanner/View.jsx +1 -1
- package/src/components/manage/Blocks/SpeciesBanner/style.less +2 -1
- package/src/components/theme/Navigation/Navigation.jsx +5 -4
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/Edit.jsx +266 -0
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/EditBlockWrapper.jsx +188 -0
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/EditSchema.jsx +41 -0
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/LayoutSchema.jsx +116 -0
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/View.jsx +40 -0
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/editor.less +99 -0
- package/src/index.js +34 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [1.0.18](https://github.com/eea/volto-n2k/compare/1.0.17...1.0.18) - 25 February 2023
|
|
8
|
+
|
|
9
|
+
#### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
- feat: conditional rendering for section group [Miu Razvan - [`6afd9a5`](https://github.com/eea/volto-n2k/commit/6afd9a536a520c51ad457127aa02f07fd135010e)]
|
|
12
|
+
|
|
13
|
+
#### :hammer_and_wrench: Others
|
|
14
|
+
|
|
15
|
+
- update [Miu Razvan - [`ab24f16`](https://github.com/eea/volto-n2k/commit/ab24f1624eb541d3ab492aa934c923f6cbdd245c)]
|
|
16
|
+
- uopdate label on habitats page [Claudia Ifrim - [`aa708e2`](https://github.com/eea/volto-n2k/commit/aa708e24b8f456407192cf1b889d893fd7b92f0e)]
|
|
7
17
|
### [1.0.17](https://github.com/eea/volto-n2k/compare/1.0.16...1.0.17) - 24 February 2023
|
|
8
18
|
|
|
9
19
|
#### :hammer_and_wrench: Others
|
|
10
20
|
|
|
11
21
|
- eslint fix [Miu Razvan - [`417aa35`](https://github.com/eea/volto-n2k/commit/417aa3588b32920a3e378fad8dd72de6d0f02ca3)]
|
|
12
22
|
- species distribution map for birds group [Miu Razvan - [`7712d28`](https://github.com/eea/volto-n2k/commit/7712d28c0c5a79ffd33d4fc8d1d8e73af55937a2)]
|
|
13
|
-
- update [Miu Razvan - [`6ce1f47`](https://github.com/eea/volto-n2k/commit/6ce1f4776686307f867a481b07d5bddd34e10961)]
|
|
14
|
-
- update [Miu Razvan - [`d017393`](https://github.com/eea/volto-n2k/commit/d017393b02d3628a6a72c9f6fccf59b34e517933)]
|
|
15
23
|
### [1.0.16](https://github.com/eea/volto-n2k/compare/1.0.15...1.0.16) - 22 February 2023
|
|
16
24
|
|
|
17
25
|
#### :hammer_and_wrench: Others
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ const View = (props) => {
|
|
|
21
21
|
<div className="habitat-metadata">
|
|
22
22
|
<h2 className="name">{scientific_name[0]}</h2>
|
|
23
23
|
<p className="info">
|
|
24
|
-
|
|
24
|
+
Habitats Directive Annex I code {code_2000[0]}
|
|
25
25
|
</p>
|
|
26
26
|
<br />
|
|
27
27
|
{/* {number_sites[0] && (
|
|
@@ -417,8 +417,7 @@ class Navigation extends Component {
|
|
|
417
417
|
)}
|
|
418
418
|
</Dropdown>
|
|
419
419
|
) : (
|
|
420
|
-
<
|
|
421
|
-
to={flatUrl === '' ? '/' : flatUrl}
|
|
420
|
+
<div
|
|
422
421
|
key={flatUrl}
|
|
423
422
|
className={
|
|
424
423
|
this.isActive(flatUrl)
|
|
@@ -426,8 +425,10 @@ class Navigation extends Component {
|
|
|
426
425
|
: 'item firstLevel'
|
|
427
426
|
}
|
|
428
427
|
>
|
|
429
|
-
{
|
|
430
|
-
|
|
428
|
+
<Link to={flatUrl === '' ? '/' : flatUrl}>
|
|
429
|
+
{item.title}
|
|
430
|
+
</Link>
|
|
431
|
+
</div>
|
|
431
432
|
);
|
|
432
433
|
})}
|
|
433
434
|
</>
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { isEmpty } from 'lodash';
|
|
3
|
+
import {
|
|
4
|
+
BlocksForm,
|
|
5
|
+
SidebarPortal,
|
|
6
|
+
Icon,
|
|
7
|
+
BlockDataForm,
|
|
8
|
+
} from '@plone/volto/components';
|
|
9
|
+
import { emptyBlocksForm } from '@plone/volto/helpers';
|
|
10
|
+
import delightedSVG from '@plone/volto/icons/delighted.svg';
|
|
11
|
+
import dissatisfiedSVG from '@plone/volto/icons/dissatisfied.svg';
|
|
12
|
+
import PropTypes from 'prop-types';
|
|
13
|
+
import { Button, Segment } from 'semantic-ui-react';
|
|
14
|
+
import EditBlockWrapper from './EditBlockWrapper';
|
|
15
|
+
import EditSchema from './EditSchema';
|
|
16
|
+
import helpSVG from '@plone/volto/icons/help.svg';
|
|
17
|
+
import cx from 'classnames';
|
|
18
|
+
import './editor.less';
|
|
19
|
+
|
|
20
|
+
const Edit = (props) => {
|
|
21
|
+
const {
|
|
22
|
+
block,
|
|
23
|
+
data,
|
|
24
|
+
onChangeBlock,
|
|
25
|
+
onChangeField,
|
|
26
|
+
pathname,
|
|
27
|
+
selected,
|
|
28
|
+
manage,
|
|
29
|
+
formDescription,
|
|
30
|
+
} = props;
|
|
31
|
+
|
|
32
|
+
const metadata = props.metadata || props.properties;
|
|
33
|
+
const data_blocks = data?.data?.blocks;
|
|
34
|
+
const properties = isEmpty(data_blocks) ? emptyBlocksForm() : data.data;
|
|
35
|
+
|
|
36
|
+
const [selectedBlock, setSelectedBlock] = useState(
|
|
37
|
+
properties.blocks_layout.items[0],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
if (
|
|
42
|
+
isEmpty(data_blocks) &&
|
|
43
|
+
properties.blocks_layout.items[0] !== selectedBlock
|
|
44
|
+
) {
|
|
45
|
+
setSelectedBlock(properties.blocks_layout.items[0]);
|
|
46
|
+
onChangeBlock(block, {
|
|
47
|
+
...data,
|
|
48
|
+
data: properties,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}, [onChangeBlock, properties, selectedBlock, block, data, data_blocks]);
|
|
52
|
+
|
|
53
|
+
const blockState = {};
|
|
54
|
+
let charCount = 0;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Count the number of characters that are anything except using Regex
|
|
58
|
+
* @param {string} paragraph
|
|
59
|
+
* @returns
|
|
60
|
+
*/
|
|
61
|
+
const countCharsWithoutSpaces = (paragraph) => {
|
|
62
|
+
const regex = /[^\s\\]/g;
|
|
63
|
+
|
|
64
|
+
return (paragraph.match(regex) || []).length;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Count the number of characters
|
|
69
|
+
* @param {string} paragraph
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
const countCharsWithSpaces = (paragraph) => {
|
|
73
|
+
return paragraph?.length || 0;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Recursively look for any block that contains text or plaintext
|
|
78
|
+
* @param {Object} blocksObject
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
81
|
+
const countTextInBlocks = (blocksObject) => {
|
|
82
|
+
let groupCharCount = 0;
|
|
83
|
+
if (!props.data.maxChars) {
|
|
84
|
+
return groupCharCount;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Object.keys(blocksObject).forEach((blockId) => {
|
|
88
|
+
const foundText = blocksObject[blockId]?.plaintext
|
|
89
|
+
? blocksObject[blockId]?.plaintext
|
|
90
|
+
: blocksObject[blockId]?.text?.blocks[0]?.text
|
|
91
|
+
? blocksObject[blockId].text.blocks[0].text
|
|
92
|
+
: blocksObject[blockId]?.data?.blocks
|
|
93
|
+
? countTextInBlocks(blocksObject[blockId]?.data?.blocks)
|
|
94
|
+
: blocksObject[blockId]?.blocks
|
|
95
|
+
? countTextInBlocks(blocksObject[blockId]?.blocks)
|
|
96
|
+
: '';
|
|
97
|
+
const resultText =
|
|
98
|
+
typeof foundText === 'string' || foundText instanceof String
|
|
99
|
+
? foundText
|
|
100
|
+
: '';
|
|
101
|
+
|
|
102
|
+
groupCharCount += props.data.ignoreSpaces
|
|
103
|
+
? countCharsWithoutSpaces(resultText)
|
|
104
|
+
: countCharsWithSpaces(resultText);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return groupCharCount;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const showCharCounter = () => {
|
|
111
|
+
if (data_blocks) {
|
|
112
|
+
charCount = countTextInBlocks(data_blocks);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
showCharCounter();
|
|
116
|
+
|
|
117
|
+
const counterClass =
|
|
118
|
+
charCount < Math.ceil(props.data.maxChars / 1.05)
|
|
119
|
+
? 'info'
|
|
120
|
+
: charCount < props.data.maxChars
|
|
121
|
+
? 'warning'
|
|
122
|
+
: 'danger';
|
|
123
|
+
|
|
124
|
+
const counterComponent = props.data.maxChars ? (
|
|
125
|
+
<p
|
|
126
|
+
className={cx('counter', counterClass)}
|
|
127
|
+
onClick={() => {
|
|
128
|
+
setSelectedBlock();
|
|
129
|
+
props.setSidebarTab(1);
|
|
130
|
+
}}
|
|
131
|
+
aria-hidden="true"
|
|
132
|
+
>
|
|
133
|
+
{props.data.maxChars - charCount < 0 ? (
|
|
134
|
+
<>
|
|
135
|
+
<span>{`${
|
|
136
|
+
charCount - props.data.maxChars
|
|
137
|
+
} characters over the limit`}</span>
|
|
138
|
+
<Icon name={dissatisfiedSVG} size="24px" />
|
|
139
|
+
</>
|
|
140
|
+
) : (
|
|
141
|
+
<>
|
|
142
|
+
<span>{`${
|
|
143
|
+
props.data.maxChars - charCount
|
|
144
|
+
} characters remaining out of ${props.data.maxChars}`}</span>
|
|
145
|
+
<Icon name={delightedSVG} size="24px" />
|
|
146
|
+
</>
|
|
147
|
+
)}
|
|
148
|
+
</p>
|
|
149
|
+
) : null;
|
|
150
|
+
|
|
151
|
+
// Get editing instructions from block settings or props
|
|
152
|
+
let instructions = data?.instructions?.data || data?.instructions;
|
|
153
|
+
if (!instructions || instructions === '<p><br/></p>') {
|
|
154
|
+
instructions = formDescription;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<fieldset className="section-block">
|
|
159
|
+
<legend
|
|
160
|
+
onClick={() => {
|
|
161
|
+
setSelectedBlock();
|
|
162
|
+
props.setSidebarTab(1);
|
|
163
|
+
}}
|
|
164
|
+
aria-hidden="true"
|
|
165
|
+
>
|
|
166
|
+
{data.title || 'Section'}
|
|
167
|
+
</legend>
|
|
168
|
+
<BlocksForm
|
|
169
|
+
metadata={metadata}
|
|
170
|
+
properties={properties}
|
|
171
|
+
manage={manage}
|
|
172
|
+
selectedBlock={selected ? selectedBlock : null}
|
|
173
|
+
allowedBlocks={data.allowedBlocks}
|
|
174
|
+
title={data.placeholder}
|
|
175
|
+
description={instructions}
|
|
176
|
+
onSelectBlock={(id) => {
|
|
177
|
+
setSelectedBlock(id);
|
|
178
|
+
}}
|
|
179
|
+
onChangeFormData={(newFormData) => {
|
|
180
|
+
onChangeBlock(block, {
|
|
181
|
+
...data,
|
|
182
|
+
data: newFormData,
|
|
183
|
+
});
|
|
184
|
+
}}
|
|
185
|
+
onChangeField={(id, value) => {
|
|
186
|
+
if (['blocks', 'blocks_layout'].indexOf(id) > -1) {
|
|
187
|
+
blockState[id] = value;
|
|
188
|
+
onChangeBlock(block, {
|
|
189
|
+
...data,
|
|
190
|
+
data: {
|
|
191
|
+
...data.data,
|
|
192
|
+
...blockState,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
onChangeField(id, value);
|
|
197
|
+
}
|
|
198
|
+
}}
|
|
199
|
+
pathname={pathname}
|
|
200
|
+
>
|
|
201
|
+
{({ draginfo }, editBlock, blockProps) => (
|
|
202
|
+
<EditBlockWrapper
|
|
203
|
+
draginfo={draginfo}
|
|
204
|
+
blockProps={blockProps}
|
|
205
|
+
disabled={data.disableInnerButtons}
|
|
206
|
+
extraControls={
|
|
207
|
+
<>
|
|
208
|
+
{instructions && (
|
|
209
|
+
<>
|
|
210
|
+
<Button
|
|
211
|
+
icon
|
|
212
|
+
basic
|
|
213
|
+
title="Section help"
|
|
214
|
+
onClick={() => {
|
|
215
|
+
setSelectedBlock();
|
|
216
|
+
const tab = manage ? 0 : 1;
|
|
217
|
+
props.setSidebarTab(tab);
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<Icon name={helpSVG} className="" size="19px" />
|
|
221
|
+
</Button>
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
</>
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
{editBlock}
|
|
228
|
+
</EditBlockWrapper>
|
|
229
|
+
)}
|
|
230
|
+
</BlocksForm>
|
|
231
|
+
|
|
232
|
+
{counterComponent}
|
|
233
|
+
<SidebarPortal selected={selected && !selectedBlock}>
|
|
234
|
+
{instructions && (
|
|
235
|
+
<Segment attached>
|
|
236
|
+
<div dangerouslySetInnerHTML={{ __html: instructions }} />
|
|
237
|
+
</Segment>
|
|
238
|
+
)}
|
|
239
|
+
{!data?.readOnlySettings && (
|
|
240
|
+
<BlockDataForm
|
|
241
|
+
schema={EditSchema}
|
|
242
|
+
title="Section (Group) settings"
|
|
243
|
+
formData={data}
|
|
244
|
+
onChangeField={(id, value) => {
|
|
245
|
+
props.onChangeBlock(props.block, {
|
|
246
|
+
...props.data,
|
|
247
|
+
[id]: value,
|
|
248
|
+
});
|
|
249
|
+
}}
|
|
250
|
+
/>
|
|
251
|
+
)}
|
|
252
|
+
</SidebarPortal>
|
|
253
|
+
</fieldset>
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
Edit.propTypes = {
|
|
258
|
+
block: PropTypes.string.isRequired,
|
|
259
|
+
data: PropTypes.object.isRequired,
|
|
260
|
+
onChangeBlock: PropTypes.func.isRequired,
|
|
261
|
+
pathname: PropTypes.string.isRequired,
|
|
262
|
+
selected: PropTypes.bool.isRequired,
|
|
263
|
+
manage: PropTypes.bool.isRequired,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export default Edit;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Icon, BlockChooser } from '@plone/volto/components';
|
|
3
|
+
import { blockHasValue } from '@plone/volto/helpers';
|
|
4
|
+
import config from '@plone/volto/registry';
|
|
5
|
+
import { Button } from 'semantic-ui-react';
|
|
6
|
+
import includes from 'lodash/includes';
|
|
7
|
+
import isBoolean from 'lodash/isBoolean';
|
|
8
|
+
import { defineMessages, injectIntl } from 'react-intl';
|
|
9
|
+
import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib';
|
|
10
|
+
import cx from 'classnames';
|
|
11
|
+
|
|
12
|
+
import dragSVG from '@plone/volto/icons/drag.svg';
|
|
13
|
+
import addSVG from '@plone/volto/icons/circle-plus.svg';
|
|
14
|
+
import trashSVG from '@plone/volto/icons/delete.svg';
|
|
15
|
+
|
|
16
|
+
const messages = defineMessages({
|
|
17
|
+
unknownBlock: {
|
|
18
|
+
id: 'Unknown Block',
|
|
19
|
+
defaultMessage: 'Unknown Block {block}',
|
|
20
|
+
},
|
|
21
|
+
delete: {
|
|
22
|
+
id: 'delete',
|
|
23
|
+
defaultMessage: 'delete',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
class EditBlockWrapper extends React.Component {
|
|
28
|
+
constructor(props) {
|
|
29
|
+
super(props);
|
|
30
|
+
this.state = {
|
|
31
|
+
addNewBlockOpened: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
componentDidMount() {
|
|
36
|
+
document.addEventListener('mousedown', this.handleClickOutside, false);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
componentWillUnmount() {
|
|
40
|
+
document.removeEventListener('mousedown', this.handleClickOutside);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handleClickOutside = (e) => {
|
|
44
|
+
if (
|
|
45
|
+
this.blockNode.current &&
|
|
46
|
+
doesNodeContainClick(this.blockNode.current, e)
|
|
47
|
+
)
|
|
48
|
+
return;
|
|
49
|
+
|
|
50
|
+
if (this.state.addNewBlockOpened) {
|
|
51
|
+
this.setState({
|
|
52
|
+
addNewBlockOpened: false,
|
|
53
|
+
});
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
blockNode = React.createRef();
|
|
59
|
+
|
|
60
|
+
render() {
|
|
61
|
+
const {
|
|
62
|
+
intl,
|
|
63
|
+
blockProps,
|
|
64
|
+
draginfo,
|
|
65
|
+
extraControls,
|
|
66
|
+
disabled,
|
|
67
|
+
children,
|
|
68
|
+
} = this.props;
|
|
69
|
+
|
|
70
|
+
const {
|
|
71
|
+
allowedBlocks,
|
|
72
|
+
block,
|
|
73
|
+
data,
|
|
74
|
+
onSelectBlock,
|
|
75
|
+
onDeleteBlock,
|
|
76
|
+
onMutateBlock,
|
|
77
|
+
onInsertBlock,
|
|
78
|
+
selected,
|
|
79
|
+
} = blockProps;
|
|
80
|
+
const type = data['@type'];
|
|
81
|
+
const { disableNewBlocks } = data;
|
|
82
|
+
const dragVisible = !data.fixed;
|
|
83
|
+
const visible = selected;
|
|
84
|
+
|
|
85
|
+
const required = isBoolean(data.required)
|
|
86
|
+
? data.required
|
|
87
|
+
: includes(config.blocks.requiredBlocks, type);
|
|
88
|
+
|
|
89
|
+
// Get editing instructions from block settings or props
|
|
90
|
+
let instructions = data?.instructions?.data || data?.instructions;
|
|
91
|
+
if (!instructions || instructions === '<p><br/></p>') {
|
|
92
|
+
instructions = '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div ref={this.blockNode}>
|
|
97
|
+
<div
|
|
98
|
+
ref={draginfo?.innerRef}
|
|
99
|
+
{...(selected ? draginfo?.draggableProps : null)}
|
|
100
|
+
className={`block-editor-${data['@type']}`}
|
|
101
|
+
>
|
|
102
|
+
{(!selected || !visible || disabled) && (
|
|
103
|
+
<div
|
|
104
|
+
style={{
|
|
105
|
+
display: 'none',
|
|
106
|
+
// keep react-beautiful-dnd happy
|
|
107
|
+
}}
|
|
108
|
+
{...draginfo.dragHandleProps}
|
|
109
|
+
></div>
|
|
110
|
+
)}
|
|
111
|
+
{visible && (
|
|
112
|
+
<div className="block-toolbar">
|
|
113
|
+
{instructions ? extraControls : ''}
|
|
114
|
+
|
|
115
|
+
{!disabled && (
|
|
116
|
+
<>
|
|
117
|
+
<div
|
|
118
|
+
style={{
|
|
119
|
+
display: dragVisible ? 'inline-block' : 'none',
|
|
120
|
+
}}
|
|
121
|
+
{...draginfo.dragHandleProps}
|
|
122
|
+
className="drag handle wrapper-group-block"
|
|
123
|
+
>
|
|
124
|
+
<Button icon basic title="Drag and drop">
|
|
125
|
+
<Icon name={dragSVG} size="19px" />
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{!disableNewBlocks && !blockHasValue(data) && (
|
|
130
|
+
<Button
|
|
131
|
+
icon
|
|
132
|
+
basic
|
|
133
|
+
title="Add block"
|
|
134
|
+
onClick={() => {
|
|
135
|
+
this.setState({
|
|
136
|
+
addNewBlockOpened: !this.state.addNewBlockOpened,
|
|
137
|
+
});
|
|
138
|
+
}}
|
|
139
|
+
className="group-block-add-button"
|
|
140
|
+
>
|
|
141
|
+
<Icon name={addSVG} className="" size="19px" />
|
|
142
|
+
</Button>
|
|
143
|
+
)}
|
|
144
|
+
{!required && (
|
|
145
|
+
<Button
|
|
146
|
+
icon
|
|
147
|
+
basic
|
|
148
|
+
title="Remove block"
|
|
149
|
+
onClick={() => onDeleteBlock(block)}
|
|
150
|
+
className="delete-button-group-block"
|
|
151
|
+
aria-label={intl.formatMessage(messages.delete)}
|
|
152
|
+
>
|
|
153
|
+
<Icon name={trashSVG} size="19px" />
|
|
154
|
+
</Button>
|
|
155
|
+
)}
|
|
156
|
+
{this.state.addNewBlockOpened && (
|
|
157
|
+
<BlockChooser
|
|
158
|
+
onMutateBlock={(id, value) => {
|
|
159
|
+
onMutateBlock(id, value);
|
|
160
|
+
this.setState({ addNewBlockOpened: false });
|
|
161
|
+
}}
|
|
162
|
+
onInsertBlock={(id, value) => {
|
|
163
|
+
onSelectBlock(onInsertBlock(id, value));
|
|
164
|
+
this.setState({ addNewBlockOpened: false });
|
|
165
|
+
}}
|
|
166
|
+
currentBlock={block}
|
|
167
|
+
allowedBlocks={allowedBlocks}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
<div
|
|
176
|
+
className={cx('ui drag block wrapper inner', type, {
|
|
177
|
+
multiSelected: this.props.multiSelected,
|
|
178
|
+
})}
|
|
179
|
+
>
|
|
180
|
+
{children}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export default injectIntl(EditBlockWrapper);
|
package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/EditSchema.jsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const Schema = {
|
|
2
|
+
title: 'Section block',
|
|
3
|
+
fieldsets: [
|
|
4
|
+
{
|
|
5
|
+
id: 'default',
|
|
6
|
+
title: 'Default',
|
|
7
|
+
fields: ['title', 'as', 'condition'],
|
|
8
|
+
},
|
|
9
|
+
],
|
|
10
|
+
properties: {
|
|
11
|
+
title: {
|
|
12
|
+
title: 'Title',
|
|
13
|
+
description: 'Section friendly name',
|
|
14
|
+
type: 'string',
|
|
15
|
+
},
|
|
16
|
+
as: {
|
|
17
|
+
title: 'HTML5 element',
|
|
18
|
+
description: 'Select HTML5 element to be used for this block',
|
|
19
|
+
type: 'string',
|
|
20
|
+
factory: 'Choice',
|
|
21
|
+
default: 'div',
|
|
22
|
+
choices: [
|
|
23
|
+
['div', 'div'],
|
|
24
|
+
['section', 'section'],
|
|
25
|
+
['article', 'article'],
|
|
26
|
+
['aside', 'aside'],
|
|
27
|
+
['details', 'details'],
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
condition: {
|
|
31
|
+
title: 'Condition',
|
|
32
|
+
choices: [
|
|
33
|
+
['bird', 'Is bird'],
|
|
34
|
+
['species', 'Is not bird'],
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: [],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default Schema;
|
package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/LayoutSchema.jsx
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const Schema = {
|
|
2
|
+
title: 'Section (Group) settings',
|
|
3
|
+
fieldsets: [
|
|
4
|
+
{
|
|
5
|
+
id: 'default',
|
|
6
|
+
title: 'Default',
|
|
7
|
+
fields: [
|
|
8
|
+
'title',
|
|
9
|
+
'placeholder',
|
|
10
|
+
'instructions',
|
|
11
|
+
'allowedBlocks',
|
|
12
|
+
'as',
|
|
13
|
+
'maxChars',
|
|
14
|
+
'ignoreSpaces',
|
|
15
|
+
'readOnlySettings',
|
|
16
|
+
'disableInnerButtons',
|
|
17
|
+
'required',
|
|
18
|
+
'fixed',
|
|
19
|
+
'fixedLayout',
|
|
20
|
+
'disableNewBlocks',
|
|
21
|
+
'readOnly',
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
properties: {
|
|
26
|
+
title: {
|
|
27
|
+
title: 'Title',
|
|
28
|
+
description: 'Section friendly name',
|
|
29
|
+
type: 'string',
|
|
30
|
+
},
|
|
31
|
+
allowedBlocks: {
|
|
32
|
+
title: 'Allowed blocks',
|
|
33
|
+
description: 'Allow only the following blocks types',
|
|
34
|
+
type: 'array',
|
|
35
|
+
items: {
|
|
36
|
+
choices: [],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
placeholder: {
|
|
40
|
+
title: 'Helper text',
|
|
41
|
+
description:
|
|
42
|
+
'A short hint that describes the expected value within this block',
|
|
43
|
+
type: 'string',
|
|
44
|
+
},
|
|
45
|
+
instructions: {
|
|
46
|
+
title: 'Instructions',
|
|
47
|
+
description: 'Detailed expected value within this block',
|
|
48
|
+
type: 'string',
|
|
49
|
+
widget: 'richtext',
|
|
50
|
+
},
|
|
51
|
+
as: {
|
|
52
|
+
title: 'HTML5 element',
|
|
53
|
+
description: 'Select HTML5 element to be used for this block',
|
|
54
|
+
type: 'string',
|
|
55
|
+
factory: 'Choice',
|
|
56
|
+
default: 'div',
|
|
57
|
+
choices: [
|
|
58
|
+
['div', 'div'],
|
|
59
|
+
['section', 'section'],
|
|
60
|
+
['article', 'article'],
|
|
61
|
+
['aside', 'aside'],
|
|
62
|
+
['details', 'details'],
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
maxChars: {
|
|
66
|
+
title: 'Maximum Characters',
|
|
67
|
+
description: 'The maximum number of characters.',
|
|
68
|
+
type: 'integer',
|
|
69
|
+
factory: 'Integer',
|
|
70
|
+
},
|
|
71
|
+
ignoreSpaces: {
|
|
72
|
+
title: 'Ignore spaces',
|
|
73
|
+
description: 'Ignore spaces while calculating maximum characters',
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
},
|
|
76
|
+
required: {
|
|
77
|
+
title: 'Required',
|
|
78
|
+
description: "Don't allow deletion of this block",
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
},
|
|
81
|
+
fixed: {
|
|
82
|
+
title: 'Fixed position',
|
|
83
|
+
description: 'Disable drag & drop on this block',
|
|
84
|
+
type: 'boolean',
|
|
85
|
+
},
|
|
86
|
+
fixedLayout: {
|
|
87
|
+
title: 'Fixed layout',
|
|
88
|
+
description:
|
|
89
|
+
'Fixed layout, New blocks created by Editor within this block will be ignored',
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
},
|
|
92
|
+
disableNewBlocks: {
|
|
93
|
+
title: 'Disable new blocks',
|
|
94
|
+
description: 'Disable creation of new blocks after this block',
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
},
|
|
97
|
+
readOnly: {
|
|
98
|
+
title: 'Read-only',
|
|
99
|
+
description: 'Disable editing on this block',
|
|
100
|
+
type: 'boolean',
|
|
101
|
+
},
|
|
102
|
+
readOnlySettings: {
|
|
103
|
+
title: 'Read-only settings',
|
|
104
|
+
description: 'Disable editing on section block settings',
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
},
|
|
107
|
+
disableInnerButtons: {
|
|
108
|
+
title: 'Disable inner buttons',
|
|
109
|
+
description: 'Hide all block related buttons within this block',
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: [],
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default Schema;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RenderBlocks } from '@plone/volto/components';
|
|
3
|
+
import config from '@plone/volto/registry';
|
|
4
|
+
|
|
5
|
+
const View = (props) => {
|
|
6
|
+
const { data } = props;
|
|
7
|
+
const condition = data.condition;
|
|
8
|
+
const metadata = props.metadata || props.properties;
|
|
9
|
+
const CustomTag = `${data.as || 'div'}`;
|
|
10
|
+
const customId = data?.title
|
|
11
|
+
?.toLowerCase()
|
|
12
|
+
?.replace(/[^a-zA-Z-\s]/gi, '')
|
|
13
|
+
?.trim()
|
|
14
|
+
?.replace(/\s+/gi, '-');
|
|
15
|
+
|
|
16
|
+
const ConditionalRendering =
|
|
17
|
+
config.blocks.blocksConfig.group.conditions?.[condition] || null;
|
|
18
|
+
|
|
19
|
+
if (ConditionalRendering) {
|
|
20
|
+
return (
|
|
21
|
+
<ConditionalRendering>
|
|
22
|
+
<CustomTag id={customId}>
|
|
23
|
+
<RenderBlocks
|
|
24
|
+
{...props}
|
|
25
|
+
metadata={metadata}
|
|
26
|
+
content={data?.data || {}}
|
|
27
|
+
/>
|
|
28
|
+
</CustomTag>
|
|
29
|
+
</ConditionalRendering>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<CustomTag id={customId}>
|
|
35
|
+
<RenderBlocks {...props} metadata={metadata} content={data?.data || {}} />
|
|
36
|
+
</CustomTag>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default View;
|
package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/editor.less
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
@type: 'extra';
|
|
2
|
+
@element: 'custom';
|
|
3
|
+
|
|
4
|
+
@import (multiple, reference, optional) '../../theme.config';
|
|
5
|
+
|
|
6
|
+
@borderColor: rgba(120, 192, 215, 0.75);
|
|
7
|
+
|
|
8
|
+
.block-editor-group {
|
|
9
|
+
[data-rbd-draggable-context-id] {
|
|
10
|
+
margin-bottom: 1rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.block-add-button {
|
|
14
|
+
display: none !important;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.block.group.selected::before,
|
|
18
|
+
.block.group:hover::before {
|
|
19
|
+
border-style: dashed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fieldset {
|
|
23
|
+
border: none;
|
|
24
|
+
|
|
25
|
+
legend {
|
|
26
|
+
position: absolute;
|
|
27
|
+
z-index: 3;
|
|
28
|
+
top: -1.3em;
|
|
29
|
+
left: 0;
|
|
30
|
+
width: fit-content;
|
|
31
|
+
padding: 0 1rem;
|
|
32
|
+
margin-right: auto;
|
|
33
|
+
margin-left: auto;
|
|
34
|
+
background-color: @pageBackground;
|
|
35
|
+
color: @borderColor;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
text-align: center;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.section-block {
|
|
42
|
+
padding-top: 1rem;
|
|
43
|
+
padding-bottom: 0.1rem;
|
|
44
|
+
margin: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.counter {
|
|
48
|
+
display: grid;
|
|
49
|
+
align-items: center;
|
|
50
|
+
font-size: 85%;
|
|
51
|
+
grid-gap: 0.5em;
|
|
52
|
+
grid-template-columns: 98% auto;
|
|
53
|
+
text-align: end;
|
|
54
|
+
|
|
55
|
+
&.info {
|
|
56
|
+
color: #ccc;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&.danger {
|
|
60
|
+
color: crimson;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&.warning {
|
|
64
|
+
color: darkorange;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.blocks-form {
|
|
69
|
+
margin-top: 0.5rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.blocks-chooser {
|
|
73
|
+
right: 0;
|
|
74
|
+
left: auto;
|
|
75
|
+
margin-top: 3rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.block-toolbar {
|
|
79
|
+
position: absolute;
|
|
80
|
+
z-index: 3;
|
|
81
|
+
right: -9px;
|
|
82
|
+
display: flex;
|
|
83
|
+
border: none;
|
|
84
|
+
border: 1px solid @borderColor;
|
|
85
|
+
border-bottom: 1px solid @pageBackground;
|
|
86
|
+
margin-top: -45px;
|
|
87
|
+
background-color: @pageBackground;
|
|
88
|
+
border-top-left-radius: 1rem;
|
|
89
|
+
border-top-right-radius: 1rem;
|
|
90
|
+
|
|
91
|
+
.ui.basic.button {
|
|
92
|
+
padding: 8px 5px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.ui.basic.button:hover {
|
|
96
|
+
background: transparent !important;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-extend-native */
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import loadable from '@loadable/component';
|
|
4
|
+
import { compose } from 'redux';
|
|
5
|
+
import { connectToProviderData } from '@eeacms/volto-datablocks/hocs';
|
|
4
6
|
|
|
5
7
|
import { hashlink, localStorage } from './reducers';
|
|
6
8
|
|
|
@@ -125,6 +127,38 @@ const applyConfig = (config) => {
|
|
|
125
127
|
],
|
|
126
128
|
};
|
|
127
129
|
|
|
130
|
+
config.blocks.blocksConfig.group = {
|
|
131
|
+
...(config.blocks.blocksConfig.group || {}),
|
|
132
|
+
conditions: {
|
|
133
|
+
bird: compose(
|
|
134
|
+
connectToProviderData((props) => ({
|
|
135
|
+
provider_url: '/data/natura-2000-species',
|
|
136
|
+
})),
|
|
137
|
+
)(({ children, provider_data }) => {
|
|
138
|
+
if (
|
|
139
|
+
provider_data?.species_group_name?.[0] &&
|
|
140
|
+
provider_data?.species_group_name?.[0] === 'Birds'
|
|
141
|
+
) {
|
|
142
|
+
return children;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}),
|
|
146
|
+
species: compose(
|
|
147
|
+
connectToProviderData((props) => ({
|
|
148
|
+
provider_url: '/data/natura-2000-species',
|
|
149
|
+
})),
|
|
150
|
+
)(({ children, provider_data }) => {
|
|
151
|
+
if (
|
|
152
|
+
provider_data?.species_group_name?.[0] &&
|
|
153
|
+
provider_data?.species_group_name?.[0] !== 'Birds'
|
|
154
|
+
) {
|
|
155
|
+
return children;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
128
162
|
config.settings.slate.elements[LINK] = LinkElement;
|
|
129
163
|
|
|
130
164
|
config.settings.loadables = {
|