@eeacms/volto-n2k 1.0.17 → 1.0.19
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 +15 -2
- package/package.json +4 -4
- package/src/components/manage/Blocks/ConnectedLinkList/View.jsx +31 -0
- package/src/components/manage/Blocks/ConnectedLinkList/index.js +17 -0
- package/src/components/manage/Blocks/ConnectedLinkList/schema.js +39 -0
- package/src/components/manage/Blocks/ConnectedLinkList/style.less +0 -0
- 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 +36 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,14 +4,27 @@ 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.19](https://github.com/eea/volto-n2k/compare/1.0.18...1.0.19) - 6 March 2023
|
|
8
|
+
|
|
9
|
+
#### :hammer_and_wrench: Others
|
|
10
|
+
|
|
11
|
+
- Add connected link list [Miu Razvan - [`633f1de`](https://github.com/eea/volto-n2k/commit/633f1ded7a208f42f1c25dee271c9671c53c01c3)]
|
|
12
|
+
### [1.0.18](https://github.com/eea/volto-n2k/compare/1.0.17...1.0.18) - 25 February 2023
|
|
13
|
+
|
|
14
|
+
#### :rocket: New Features
|
|
15
|
+
|
|
16
|
+
- feat: conditional rendering for section group [Miu Razvan - [`6afd9a5`](https://github.com/eea/volto-n2k/commit/6afd9a536a520c51ad457127aa02f07fd135010e)]
|
|
17
|
+
|
|
18
|
+
#### :hammer_and_wrench: Others
|
|
19
|
+
|
|
20
|
+
- update [Miu Razvan - [`ab24f16`](https://github.com/eea/volto-n2k/commit/ab24f1624eb541d3ab492aa934c923f6cbdd245c)]
|
|
21
|
+
- uopdate label on habitats page [Claudia Ifrim - [`aa708e2`](https://github.com/eea/volto-n2k/commit/aa708e24b8f456407192cf1b889d893fd7b92f0e)]
|
|
7
22
|
### [1.0.17](https://github.com/eea/volto-n2k/compare/1.0.16...1.0.17) - 24 February 2023
|
|
8
23
|
|
|
9
24
|
#### :hammer_and_wrench: Others
|
|
10
25
|
|
|
11
26
|
- eslint fix [Miu Razvan - [`417aa35`](https://github.com/eea/volto-n2k/commit/417aa3588b32920a3e378fad8dd72de6d0f02ca3)]
|
|
12
27
|
- 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
28
|
### [1.0.16](https://github.com/eea/volto-n2k/compare/1.0.15...1.0.16) - 22 February 2023
|
|
16
29
|
|
|
17
30
|
#### :hammer_and_wrench: Others
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-n2k",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "volto-n2k: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"url": "git@github.com:eea/volto-n2k.git"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@eeacms/volto-bise": "*",
|
|
27
28
|
"@eeacms/volto-datablocks": "*",
|
|
28
|
-
"@eeacms/volto-resize-helper": "*",
|
|
29
29
|
"@eeacms/volto-openlayers-map": "*",
|
|
30
|
+
"@eeacms/volto-resize-helper": "*",
|
|
30
31
|
"@eeacms/volto-tabs-block": "*",
|
|
31
|
-
"@eeacms/volto-bise": "*",
|
|
32
32
|
"d3": "^7.6.1",
|
|
33
33
|
"d3-shape": "^3.1.0",
|
|
34
34
|
"react-lazy-load-image-component": "1.5.1",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"slick-carousel": "1.8.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@plone/scripts": "*",
|
|
42
41
|
"@cypress/code-coverage": "^3.10.0",
|
|
42
|
+
"@plone/scripts": "*",
|
|
43
43
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
|
44
44
|
"md5": "^2.3.0"
|
|
45
45
|
},
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* eslint-disable react/jsx-no-target-blank */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { FormattedValue } from '@eeacms/volto-datablocks/Utils';
|
|
4
|
+
import './style.less';
|
|
5
|
+
|
|
6
|
+
const View = (props) => {
|
|
7
|
+
const { data = {} } = props;
|
|
8
|
+
const provider_data = props.provider_data || {};
|
|
9
|
+
const columns = provider_data[Object.keys(provider_data)?.[0]]?.length || 0;
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="connected-list">
|
|
13
|
+
{props.mode === 'edit' ? <p>Connected link list</p> : ''}
|
|
14
|
+
<p>
|
|
15
|
+
{Array(Math.max(0, columns))
|
|
16
|
+
.fill()
|
|
17
|
+
.map((_, column) => (
|
|
18
|
+
<FormattedValue
|
|
19
|
+
textTemplate={data.textTemplate}
|
|
20
|
+
linkTemplate={data.linkTemplate}
|
|
21
|
+
value={provider_data[data.value]?.[column]}
|
|
22
|
+
linkValue={provider_data[data.linkValue]?.[column]}
|
|
23
|
+
link={true}
|
|
24
|
+
/>
|
|
25
|
+
))}
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default View;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ConnectedLinkList from './View';
|
|
2
|
+
import getSchema from './schema';
|
|
3
|
+
|
|
4
|
+
export default (config) => {
|
|
5
|
+
config.blocks.blocksConfig.custom_connected_block = {
|
|
6
|
+
...config.blocks.blocksConfig.custom_connected_block,
|
|
7
|
+
blocks: {
|
|
8
|
+
...config.blocks.blocksConfig.custom_connected_block.blocks,
|
|
9
|
+
connected_link_list: {
|
|
10
|
+
view: ConnectedLinkList,
|
|
11
|
+
getSchema: getSchema,
|
|
12
|
+
title: 'Connected link list',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
return config;
|
|
17
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const getSchema = (props) => {
|
|
2
|
+
const data = props.provider_data || {};
|
|
3
|
+
const choices = Object.keys(data).map((key) => [key, key]);
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
title: 'Connected link list',
|
|
7
|
+
|
|
8
|
+
fieldsets: [
|
|
9
|
+
{
|
|
10
|
+
id: 'default',
|
|
11
|
+
title: 'Default',
|
|
12
|
+
fields: ['value', 'linkValue', 'textTemplate', 'linkTemplate'],
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
properties: {
|
|
16
|
+
value: {
|
|
17
|
+
title: 'Value',
|
|
18
|
+
type: 'array',
|
|
19
|
+
choices,
|
|
20
|
+
},
|
|
21
|
+
linkValue: {
|
|
22
|
+
title: 'Link value',
|
|
23
|
+
type: 'array',
|
|
24
|
+
choices,
|
|
25
|
+
},
|
|
26
|
+
textTemplate: {
|
|
27
|
+
title: 'Text template',
|
|
28
|
+
description: 'Add suffix/prefix to text. Use {} for value placeholder',
|
|
29
|
+
},
|
|
30
|
+
linkTemplate: {
|
|
31
|
+
title: 'Link template',
|
|
32
|
+
description: 'Add suffix/prefix to link. Use {} for value placeholder',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: [],
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default getSchema;
|
|
File without changes
|
|
@@ -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
|
|
|
@@ -12,6 +14,7 @@ import installBubbleChart from './components/manage/Blocks/BubbleChart';
|
|
|
12
14
|
import installCarouselHorizontal from './components/manage/Blocks/CarouselHorizontal';
|
|
13
15
|
import installCddaShape from './components/manage/Blocks/CddaShape';
|
|
14
16
|
import installConnectedList from './components/manage/Blocks/List';
|
|
17
|
+
import installConnectedLinkList from './components/manage/Blocks/ConnectedLinkList';
|
|
15
18
|
import installContactBlock from './components/manage/Blocks/ContactBlock';
|
|
16
19
|
import installExplodedPiesChart from './components/manage/Blocks/ExplodedPiesChart';
|
|
17
20
|
import installExploreHabitats from './components/manage/Blocks/ExploreHabitats';
|
|
@@ -125,6 +128,38 @@ const applyConfig = (config) => {
|
|
|
125
128
|
],
|
|
126
129
|
};
|
|
127
130
|
|
|
131
|
+
config.blocks.blocksConfig.group = {
|
|
132
|
+
...(config.blocks.blocksConfig.group || {}),
|
|
133
|
+
conditions: {
|
|
134
|
+
bird: compose(
|
|
135
|
+
connectToProviderData((props) => ({
|
|
136
|
+
provider_url: '/data/natura-2000-species',
|
|
137
|
+
})),
|
|
138
|
+
)(({ children, provider_data }) => {
|
|
139
|
+
if (
|
|
140
|
+
provider_data?.species_group_name?.[0] &&
|
|
141
|
+
provider_data?.species_group_name?.[0] === 'Birds'
|
|
142
|
+
) {
|
|
143
|
+
return children;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}),
|
|
147
|
+
species: compose(
|
|
148
|
+
connectToProviderData((props) => ({
|
|
149
|
+
provider_url: '/data/natura-2000-species',
|
|
150
|
+
})),
|
|
151
|
+
)(({ children, provider_data }) => {
|
|
152
|
+
if (
|
|
153
|
+
provider_data?.species_group_name?.[0] &&
|
|
154
|
+
provider_data?.species_group_name?.[0] !== 'Birds'
|
|
155
|
+
) {
|
|
156
|
+
return children;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
128
163
|
config.settings.slate.elements[LINK] = LinkElement;
|
|
129
164
|
|
|
130
165
|
config.settings.loadables = {
|
|
@@ -139,6 +174,7 @@ const applyConfig = (config) => {
|
|
|
139
174
|
installCarouselHorizontal,
|
|
140
175
|
installCddaShape,
|
|
141
176
|
installConnectedList,
|
|
177
|
+
installConnectedLinkList,
|
|
142
178
|
installContactBlock,
|
|
143
179
|
installExplodedPiesChart,
|
|
144
180
|
installExploreHabitats,
|