@plone/volto 14.1.1 → 14.2.3
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 +38 -0
- package/README.md +25 -4
- package/package.json +3 -3
- package/src/components/manage/Add/Add.jsx +1 -0
- package/src/components/manage/Contents/Contents.jsx +225 -220
- package/src/components/manage/Edit/Edit.jsx +1 -0
- package/src/components/manage/Form/Form.jsx +21 -5
- package/src/components/manage/Multilingual/TranslationObject.jsx +1 -0
- package/src/components/manage/Widgets/ArrayWidget.jsx +19 -1
- package/src/components/manage/Widgets/ArrayWidget.stories.jsx +8 -0
- package/src/components/manage/Widgets/DatetimeWidget.jsx +2 -2
- package/src/components/theme/Anontools/Anontools.jsx +1 -1
- package/src/components/theme/Navigation/NavItem.jsx +3 -1
- package/src/config/Blocks.jsx +8 -1
- package/src/config/index.js +2 -0
- package/src/express-middleware/files.js +2 -3
- package/src/express-middleware/images.js +2 -4
- package/src/helpers/Api/APIResourceWithAuth.js +1 -7
- package/src/helpers/FormValidation/FormValidation.js +1 -1
- package/src/server.jsx +28 -0
- package/theme/themes/pastanaga/extras/contents.less +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 14.2.3 (2022-01-20)
|
|
4
|
+
|
|
5
|
+
### Bugfix
|
|
6
|
+
|
|
7
|
+
- Fix overflow table in Content view @giuliaghisini
|
|
8
|
+
- Fixed url validation in FormValidation to admit ip addresses. @giuliaghisini
|
|
9
|
+
|
|
10
|
+
## 14.2.2 (2022-01-13)
|
|
11
|
+
|
|
12
|
+
### Bugfix
|
|
13
|
+
|
|
14
|
+
- Fix home URL item in Navigation, which was evaluating as non-internal @sneridagh
|
|
15
|
+
- Improve the request handling in `getAPIResourceWithAuth` and in `Api` helper. This fixes the "Cannot set headers once the content has being sent" @sneridagh
|
|
16
|
+
- Fix when you remove the time from DatetimeWidget @iRohitSingh
|
|
17
|
+
|
|
18
|
+
### Internal
|
|
19
|
+
|
|
20
|
+
- Fix URL for Climate-Energy, a Volto website @tiberiuichim
|
|
21
|
+
- Fix quirky Cypress test in "DX control panel schema" (see https://github.com/plone/volto/runs/4803206906?check_suite_focus=true) @sneridagh
|
|
22
|
+
|
|
23
|
+
## 14.2.1 (2022-01-12)
|
|
24
|
+
|
|
25
|
+
### Bugfix
|
|
26
|
+
|
|
27
|
+
- Fix home URL item in Navigation, which was evaluating as non-internal
|
|
28
|
+
|
|
29
|
+
### Internal
|
|
30
|
+
|
|
31
|
+
- Use plone-backend docker images for Cypress tests @sneridagh
|
|
32
|
+
- Upgrade `query-string` library so it supports Plone `:list` qs marker @sneridagh
|
|
33
|
+
|
|
34
|
+
## 14.2.0 (2022-01-04)
|
|
35
|
+
|
|
36
|
+
### Feature
|
|
37
|
+
|
|
38
|
+
- Allow `creatable` prop to be passed to `ArrayWidgets`, in case they don't have a vocabulary @giuliaghisini
|
|
39
|
+
- Added initialBlocksFocus to blocks config, to set default focus on non-first block. @giuliaghisini
|
|
40
|
+
|
|
3
41
|
## 14.1.1 (2022-01-03)
|
|
4
42
|
|
|
5
43
|
### Internal
|
package/README.md
CHANGED
|
@@ -76,20 +76,41 @@ follow the prompts questions, provide `myvoltoproject` as project name then, whe
|
|
|
76
76
|
|
|
77
77
|
We recommend Plone as backend of choice for Volto.
|
|
78
78
|
|
|
79
|
-
You can bootstrap a ready Docker Plone container with all the dependencies and ready for Volto use:
|
|
79
|
+
You can bootstrap a ready Docker Plone container with all the dependencies and ready for Volto use. We recommend to use the Plone docker builds based in `pip` [plone/plone-backend](https://github.com/plone/plone-backend) image:
|
|
80
80
|
|
|
81
81
|
```shell
|
|
82
|
-
docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e
|
|
82
|
+
docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e ADDONS="plone.restapi==8.18.0 plone.app.iterate==4.0.2 plone.rest==2.0.0a1 plone.app.vocabularies==4.3.0 plone.volto==3.1.0a7" -e PROFILES="plone.volto:default-homepage" plone/plone-backend
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
or as an alternative if you have experience with Plone and you have all the
|
|
86
|
-
dependencies installed on your system, you can use the supplied buildout in the
|
|
86
|
+
dependencies installed on your system, you can use the supplied convenience buildout in the
|
|
87
87
|
`api` folder by issuing the command:
|
|
88
88
|
|
|
89
89
|
```shell
|
|
90
90
|
make build-backend
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
#### Recommended Plone version
|
|
94
|
+
|
|
95
|
+
Volto is Plone 6 default UI, so it will work for all Plone 6 released versions.
|
|
96
|
+
|
|
97
|
+
For the Plone 5 series latest released version (with Python 3) and above is recommended (at the time of writing 5.2.6).
|
|
98
|
+
|
|
99
|
+
The following KGS (or above) are also recommended, for any Plone version used.
|
|
100
|
+
|
|
101
|
+
#### KGS (known good versions) for backend packages
|
|
102
|
+
|
|
103
|
+
Volto always works best with latest versions of the "Frontend stack" or at least the recommended ones (in parenthesis) which are:
|
|
104
|
+
|
|
105
|
+
- plone.restapi (8.18.0)
|
|
106
|
+
- plone.rest (2.0.0a1)
|
|
107
|
+
- plone.volto (3.1.0a7)
|
|
108
|
+
|
|
109
|
+
and the following core packages since some features require up to date versions:
|
|
110
|
+
|
|
111
|
+
- plone.app.iterate (4.0.2)
|
|
112
|
+
- plone.app.vocabularies (4.3.0)
|
|
113
|
+
|
|
93
114
|
### Start Volto
|
|
94
115
|
|
|
95
116
|
```shell
|
|
@@ -109,7 +130,7 @@ Volto is actively developed since 2017 and used in production since 2018 on the
|
|
|
109
130
|
- [Excellence at Humboldt-Universität zu Berlin](https://www.alles-beginnt-mit-einer-frage.de) (Website for the excellence initiative of the [Humboldt University Berlin](https://hu-berlin.de), developed by [kitconcept GmbH](https://kitconcept.com), 2019)
|
|
110
131
|
- [Forest Information System for Europe](https://forest.eea.europa.eu) (Thematic website focusing on European forests, developed by [Eau de Web](https://www.eaudeweb.ro), 2019)
|
|
111
132
|
- [Industrial Emissions portal for Europe](https://industry.eea.europa.eu) (Thematic website focusing on European industrial emissions, developed by [Eau de Web](https://www.eaudeweb.ro), 2020)
|
|
112
|
-
- [Energy Climate Union portal for Europe](https://
|
|
133
|
+
- [Energy Climate Union portal for Europe](https://climate-energy.eea.europa.eu/) (Thematic website focusing on European strides towards mitigating climate change, developed by [Eau de Web](https://www.eaudeweb.ro), 2020)
|
|
113
134
|
- [Talke Carrer Website](https://karriere.talke.com/) (Carrer website for [Talke](https://www.talke.com), one of the leading a chemical and petrochemical logistics companies in Germany, developed by [kitconcept GmbH](https://kitconcept.com), 2020)
|
|
114
135
|
- [Stradanove](http://www.stradanove.it/) (Website of the Department of Youth Policies of the Municipality of Modena, developed by [RedTurtle](https://redturtle.it), 2020)
|
|
115
136
|
- [VisitModena](https://www.visitmodena.it/) (Tourist website of the Municipality of Modena, developed by [RedTurtle](https://redturtle.it), 2020)
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "14.
|
|
12
|
+
"version": "14.2.3",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git@github.com:plone/volto.git"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"start-spa": "razzle start --type=spa",
|
|
35
35
|
"build": "razzle build",
|
|
36
36
|
"build-spa": "razzle build --type=spa",
|
|
37
|
-
"test": "razzle test --env=jest-environment-jsdom-sixteen",
|
|
37
|
+
"test": "razzle test --env=jest-environment-jsdom-sixteen --maxWorkers=50%",
|
|
38
38
|
"test:ci": "CI=true razzle test --env=jest-environment-jsdom-sixteen",
|
|
39
39
|
"test:husky": "CI=true yarn test --bail --findRelatedTests",
|
|
40
40
|
"test:debug": "node --inspect node_modules/.bin/jest --runInBand",
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
"prismjs": "1.25.0",
|
|
343
343
|
"promise-file-reader": "1.0.2",
|
|
344
344
|
"prop-types": "15.7.2",
|
|
345
|
-
"query-string": "7.
|
|
345
|
+
"query-string": "7.1.0",
|
|
346
346
|
"razzle": "3.4.5",
|
|
347
347
|
"razzle-plugin-bundle-analyzer": "1.2.0",
|
|
348
348
|
"rc-time-picker": "3.7.3",
|
|
@@ -1491,236 +1491,241 @@ class Contents extends Component {
|
|
|
1491
1491
|
</Dropdown.Menu>
|
|
1492
1492
|
</Dropdown>
|
|
1493
1493
|
</Segment>
|
|
1494
|
-
<
|
|
1495
|
-
<Table
|
|
1496
|
-
<Table.
|
|
1497
|
-
<Table.
|
|
1498
|
-
<
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
icon={
|
|
1506
|
-
<Icon
|
|
1507
|
-
name={configurationSVG}
|
|
1508
|
-
size="24px"
|
|
1509
|
-
color="#826a6a"
|
|
1510
|
-
className="configuration-svg"
|
|
1511
|
-
/>
|
|
1512
|
-
}
|
|
1513
|
-
>
|
|
1514
|
-
<Dropdown.Menu>
|
|
1515
|
-
<Dropdown.Header
|
|
1516
|
-
content={this.props.intl.formatMessage(
|
|
1517
|
-
messages.rearrangeBy,
|
|
1518
|
-
)}
|
|
1519
|
-
/>
|
|
1520
|
-
{map(
|
|
1521
|
-
[
|
|
1522
|
-
'id',
|
|
1523
|
-
'sortable_title',
|
|
1524
|
-
'EffectiveDate',
|
|
1525
|
-
'CreationDate',
|
|
1526
|
-
'ModificationDate',
|
|
1527
|
-
'portal_type',
|
|
1528
|
-
],
|
|
1529
|
-
(index) => (
|
|
1530
|
-
<Dropdown.Item
|
|
1531
|
-
key={index}
|
|
1532
|
-
className={`sort_${index} icon-align`}
|
|
1533
|
-
>
|
|
1534
|
-
<Icon name={downKeySVG} size="24px" />
|
|
1535
|
-
<FormattedMessage
|
|
1536
|
-
id={Indexes[index].label}
|
|
1537
|
-
/>
|
|
1538
|
-
<Dropdown.Menu>
|
|
1539
|
-
<Dropdown.Item
|
|
1540
|
-
onClick={this.onSortItems}
|
|
1541
|
-
value={`${Indexes[index].sort_on}|ascending`}
|
|
1542
|
-
className={`sort_${Indexes[index].sort_on}_ascending icon-align`}
|
|
1543
|
-
>
|
|
1544
|
-
<Icon
|
|
1545
|
-
name={sortDownSVG}
|
|
1546
|
-
size="24px"
|
|
1547
|
-
/>{' '}
|
|
1548
|
-
<FormattedMessage
|
|
1549
|
-
id="Ascending"
|
|
1550
|
-
defaultMessage="Ascending"
|
|
1551
|
-
/>
|
|
1552
|
-
</Dropdown.Item>
|
|
1553
|
-
<Dropdown.Item
|
|
1554
|
-
onClick={this.onSortItems}
|
|
1555
|
-
value={`${Indexes[index].sort_on}|descending`}
|
|
1556
|
-
className={`sort_${Indexes[index].sort_on}_descending icon-align`}
|
|
1557
|
-
>
|
|
1558
|
-
<Icon
|
|
1559
|
-
name={sortUpSVG}
|
|
1560
|
-
size="24px"
|
|
1561
|
-
/>{' '}
|
|
1562
|
-
<FormattedMessage
|
|
1563
|
-
id="Descending"
|
|
1564
|
-
defaultMessage="Descending"
|
|
1565
|
-
/>
|
|
1566
|
-
</Dropdown.Item>
|
|
1567
|
-
</Dropdown.Menu>
|
|
1568
|
-
</Dropdown.Item>
|
|
1569
|
-
),
|
|
1494
|
+
<div className="contents-table-wrapper">
|
|
1495
|
+
<Table selectable compact singleLine attached>
|
|
1496
|
+
<Table.Header>
|
|
1497
|
+
<Table.Row>
|
|
1498
|
+
<Table.HeaderCell>
|
|
1499
|
+
<Dropdown
|
|
1500
|
+
item
|
|
1501
|
+
upward={false}
|
|
1502
|
+
className="sort-icon"
|
|
1503
|
+
aria-label={this.props.intl.formatMessage(
|
|
1504
|
+
messages.sort,
|
|
1570
1505
|
)}
|
|
1571
|
-
|
|
1572
|
-
</Dropdown>
|
|
1573
|
-
</Table.HeaderCell>
|
|
1574
|
-
<Table.HeaderCell>
|
|
1575
|
-
<Dropdown
|
|
1576
|
-
upward={false}
|
|
1577
|
-
trigger={
|
|
1578
|
-
<Icon
|
|
1579
|
-
name={
|
|
1580
|
-
this.state.selected.length === 0
|
|
1581
|
-
? checkboxUncheckedSVG
|
|
1582
|
-
: this.state.selected.length ===
|
|
1583
|
-
this.state.items.length
|
|
1584
|
-
? checkboxCheckedSVG
|
|
1585
|
-
: checkboxIndeterminateSVG
|
|
1586
|
-
}
|
|
1587
|
-
color={
|
|
1588
|
-
this.state.selected.length > 0
|
|
1589
|
-
? '#007eb1'
|
|
1590
|
-
: '#826a6a'
|
|
1591
|
-
}
|
|
1592
|
-
size="24px"
|
|
1593
|
-
/>
|
|
1594
|
-
}
|
|
1595
|
-
icon={null}
|
|
1596
|
-
>
|
|
1597
|
-
<Dropdown.Menu>
|
|
1598
|
-
<Dropdown.Header
|
|
1599
|
-
content={this.props.intl.formatMessage(
|
|
1600
|
-
messages.select,
|
|
1601
|
-
)}
|
|
1602
|
-
/>
|
|
1603
|
-
<Dropdown.Item onClick={this.onSelectAll}>
|
|
1506
|
+
icon={
|
|
1604
1507
|
<Icon
|
|
1605
|
-
name={
|
|
1606
|
-
color="#007eb1"
|
|
1508
|
+
name={configurationSVG}
|
|
1607
1509
|
size="24px"
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
id="All"
|
|
1611
|
-
defaultMessage="All"
|
|
1510
|
+
color="#826a6a"
|
|
1511
|
+
className="configuration-svg"
|
|
1612
1512
|
/>
|
|
1613
|
-
|
|
1614
|
-
|
|
1513
|
+
}
|
|
1514
|
+
>
|
|
1515
|
+
<Dropdown.Menu>
|
|
1516
|
+
<Dropdown.Header
|
|
1517
|
+
content={this.props.intl.formatMessage(
|
|
1518
|
+
messages.rearrangeBy,
|
|
1519
|
+
)}
|
|
1520
|
+
/>
|
|
1521
|
+
{map(
|
|
1522
|
+
[
|
|
1523
|
+
'id',
|
|
1524
|
+
'sortable_title',
|
|
1525
|
+
'EffectiveDate',
|
|
1526
|
+
'CreationDate',
|
|
1527
|
+
'ModificationDate',
|
|
1528
|
+
'portal_type',
|
|
1529
|
+
],
|
|
1530
|
+
(index) => (
|
|
1531
|
+
<Dropdown.Item
|
|
1532
|
+
key={index}
|
|
1533
|
+
className={`sort_${index} icon-align`}
|
|
1534
|
+
>
|
|
1535
|
+
<Icon name={downKeySVG} size="24px" />
|
|
1536
|
+
<FormattedMessage
|
|
1537
|
+
id={Indexes[index].label}
|
|
1538
|
+
/>
|
|
1539
|
+
<Dropdown.Menu>
|
|
1540
|
+
<Dropdown.Item
|
|
1541
|
+
onClick={this.onSortItems}
|
|
1542
|
+
value={`${Indexes[index].sort_on}|ascending`}
|
|
1543
|
+
className={`sort_${Indexes[index].sort_on}_ascending icon-align`}
|
|
1544
|
+
>
|
|
1545
|
+
<Icon
|
|
1546
|
+
name={sortDownSVG}
|
|
1547
|
+
size="24px"
|
|
1548
|
+
/>{' '}
|
|
1549
|
+
<FormattedMessage
|
|
1550
|
+
id="Ascending"
|
|
1551
|
+
defaultMessage="Ascending"
|
|
1552
|
+
/>
|
|
1553
|
+
</Dropdown.Item>
|
|
1554
|
+
<Dropdown.Item
|
|
1555
|
+
onClick={this.onSortItems}
|
|
1556
|
+
value={`${Indexes[index].sort_on}|descending`}
|
|
1557
|
+
className={`sort_${Indexes[index].sort_on}_descending icon-align`}
|
|
1558
|
+
>
|
|
1559
|
+
<Icon
|
|
1560
|
+
name={sortUpSVG}
|
|
1561
|
+
size="24px"
|
|
1562
|
+
/>{' '}
|
|
1563
|
+
<FormattedMessage
|
|
1564
|
+
id="Descending"
|
|
1565
|
+
defaultMessage="Descending"
|
|
1566
|
+
/>
|
|
1567
|
+
</Dropdown.Item>
|
|
1568
|
+
</Dropdown.Menu>
|
|
1569
|
+
</Dropdown.Item>
|
|
1570
|
+
),
|
|
1571
|
+
)}
|
|
1572
|
+
</Dropdown.Menu>
|
|
1573
|
+
</Dropdown>
|
|
1574
|
+
</Table.HeaderCell>
|
|
1575
|
+
<Table.HeaderCell>
|
|
1576
|
+
<Dropdown
|
|
1577
|
+
upward={false}
|
|
1578
|
+
trigger={
|
|
1615
1579
|
<Icon
|
|
1616
|
-
name={
|
|
1580
|
+
name={
|
|
1581
|
+
this.state.selected.length === 0
|
|
1582
|
+
? checkboxUncheckedSVG
|
|
1583
|
+
: this.state.selected.length ===
|
|
1584
|
+
this.state.items.length
|
|
1585
|
+
? checkboxCheckedSVG
|
|
1586
|
+
: checkboxIndeterminateSVG
|
|
1587
|
+
}
|
|
1588
|
+
color={
|
|
1589
|
+
this.state.selected.length > 0
|
|
1590
|
+
? '#007eb1'
|
|
1591
|
+
: '#826a6a'
|
|
1592
|
+
}
|
|
1617
1593
|
size="24px"
|
|
1618
|
-
/>{' '}
|
|
1619
|
-
<FormattedMessage
|
|
1620
|
-
id="None"
|
|
1621
|
-
defaultMessage="None"
|
|
1622
1594
|
/>
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
{
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1595
|
+
}
|
|
1596
|
+
icon={null}
|
|
1597
|
+
>
|
|
1598
|
+
<Dropdown.Menu>
|
|
1599
|
+
<Dropdown.Header
|
|
1600
|
+
content={this.props.intl.formatMessage(
|
|
1601
|
+
messages.select,
|
|
1602
|
+
)}
|
|
1603
|
+
/>
|
|
1604
|
+
<Dropdown.Item onClick={this.onSelectAll}>
|
|
1605
|
+
<Icon
|
|
1606
|
+
name={checkboxCheckedSVG}
|
|
1607
|
+
color="#007eb1"
|
|
1608
|
+
size="24px"
|
|
1609
|
+
/>{' '}
|
|
1610
|
+
<FormattedMessage
|
|
1611
|
+
id="All"
|
|
1612
|
+
defaultMessage="All"
|
|
1613
|
+
/>
|
|
1614
|
+
</Dropdown.Item>
|
|
1615
|
+
<Dropdown.Item onClick={this.onSelectNone}>
|
|
1616
|
+
<Icon
|
|
1617
|
+
name={checkboxUncheckedSVG}
|
|
1618
|
+
size="24px"
|
|
1619
|
+
/>{' '}
|
|
1620
|
+
<FormattedMessage
|
|
1621
|
+
id="None"
|
|
1622
|
+
defaultMessage="None"
|
|
1623
|
+
/>
|
|
1624
|
+
</Dropdown.Item>
|
|
1625
|
+
<Dropdown.Divider />
|
|
1626
|
+
<Dropdown.Header
|
|
1627
|
+
content={this.props.intl.formatMessage(
|
|
1628
|
+
messages.selected,
|
|
1629
|
+
{ count: this.state.selected.length },
|
|
1630
|
+
)}
|
|
1631
|
+
/>
|
|
1632
|
+
<Input
|
|
1633
|
+
icon={<Icon name={zoomSVG} size="24px" />}
|
|
1634
|
+
iconPosition="left"
|
|
1635
|
+
className="search"
|
|
1636
|
+
placeholder={this.props.intl.formatMessage(
|
|
1637
|
+
messages.filter,
|
|
1638
|
+
)}
|
|
1639
|
+
onChange={this.onChangeSelected}
|
|
1640
|
+
onClick={(e) => {
|
|
1641
|
+
e.preventDefault();
|
|
1642
|
+
e.stopPropagation();
|
|
1643
|
+
}}
|
|
1644
|
+
/>
|
|
1645
|
+
<Dropdown.Menu scrolling>
|
|
1646
|
+
{map(filteredItems, (item) => (
|
|
1647
|
+
<Dropdown.Item
|
|
1648
|
+
key={item}
|
|
1649
|
+
value={item}
|
|
1650
|
+
onClick={this.onDeselect}
|
|
1651
|
+
>
|
|
1652
|
+
<Icon
|
|
1653
|
+
name={deleteSVG}
|
|
1654
|
+
color="#e40166"
|
|
1655
|
+
size="24px"
|
|
1656
|
+
/>{' '}
|
|
1657
|
+
{this.getFieldById(item, 'title')}
|
|
1658
|
+
</Dropdown.Item>
|
|
1659
|
+
))}
|
|
1660
|
+
</Dropdown.Menu>
|
|
1659
1661
|
</Dropdown.Menu>
|
|
1660
|
-
</Dropdown
|
|
1661
|
-
</
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1662
|
+
</Dropdown>
|
|
1663
|
+
</Table.HeaderCell>
|
|
1664
|
+
<Table.HeaderCell
|
|
1665
|
+
width={Math.ceil(
|
|
1666
|
+
16 / this.state.index.selectedCount,
|
|
1667
|
+
)}
|
|
1668
|
+
>
|
|
1669
|
+
<FormattedMessage
|
|
1670
|
+
id="Title"
|
|
1671
|
+
defaultMessage="Title"
|
|
1672
|
+
/>
|
|
1673
|
+
</Table.HeaderCell>
|
|
1674
|
+
{map(
|
|
1675
|
+
this.state.index.order,
|
|
1676
|
+
(index, order) =>
|
|
1677
|
+
this.state.index.values[index].selected && (
|
|
1678
|
+
<ContentsIndexHeader
|
|
1679
|
+
key={index}
|
|
1680
|
+
width={Math.ceil(
|
|
1681
|
+
16 / this.state.index.selectedCount,
|
|
1682
|
+
)}
|
|
1683
|
+
label={
|
|
1684
|
+
this.state.index.values[index].label
|
|
1685
|
+
}
|
|
1686
|
+
order={order}
|
|
1687
|
+
onOrderIndex={this.onOrderIndex}
|
|
1688
|
+
/>
|
|
1689
|
+
),
|
|
1666
1690
|
)}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1691
|
+
<Table.HeaderCell textAlign="right">
|
|
1692
|
+
<FormattedMessage
|
|
1693
|
+
id="Actions"
|
|
1694
|
+
defaultMessage="Actions"
|
|
1695
|
+
/>
|
|
1696
|
+
</Table.HeaderCell>
|
|
1697
|
+
</Table.Row>
|
|
1698
|
+
</Table.Header>
|
|
1699
|
+
<Table.Body>
|
|
1700
|
+
{this.state.items.map((item, order) => (
|
|
1701
|
+
<ContentsItem
|
|
1702
|
+
key={item['@id']}
|
|
1703
|
+
item={item}
|
|
1704
|
+
order={order}
|
|
1705
|
+
selected={
|
|
1706
|
+
indexOf(this.state.selected, item['@id']) !==
|
|
1707
|
+
-1
|
|
1708
|
+
}
|
|
1709
|
+
onClick={this.onSelect}
|
|
1710
|
+
indexes={filter(
|
|
1711
|
+
map(this.state.index.order, (index) => ({
|
|
1712
|
+
id: index,
|
|
1713
|
+
type: this.state.index.values[index].type,
|
|
1714
|
+
})),
|
|
1715
|
+
(index) =>
|
|
1716
|
+
this.state.index.values[index.id].selected,
|
|
1717
|
+
)}
|
|
1718
|
+
onCut={this.cut}
|
|
1719
|
+
onCopy={this.copy}
|
|
1720
|
+
onDelete={this.delete}
|
|
1721
|
+
onOrderItem={this.onOrderItem}
|
|
1722
|
+
onMoveToTop={this.onMoveToTop}
|
|
1723
|
+
onMoveToBottom={this.onMoveToBottom}
|
|
1692
1724
|
/>
|
|
1693
|
-
|
|
1694
|
-
</Table.
|
|
1695
|
-
</Table
|
|
1696
|
-
|
|
1697
|
-
{this.state.items.map((item, order) => (
|
|
1698
|
-
<ContentsItem
|
|
1699
|
-
key={item['@id']}
|
|
1700
|
-
item={item}
|
|
1701
|
-
order={order}
|
|
1702
|
-
selected={
|
|
1703
|
-
indexOf(this.state.selected, item['@id']) !== -1
|
|
1704
|
-
}
|
|
1705
|
-
onClick={this.onSelect}
|
|
1706
|
-
indexes={filter(
|
|
1707
|
-
map(this.state.index.order, (index) => ({
|
|
1708
|
-
id: index,
|
|
1709
|
-
type: this.state.index.values[index].type,
|
|
1710
|
-
})),
|
|
1711
|
-
(index) =>
|
|
1712
|
-
this.state.index.values[index.id].selected,
|
|
1713
|
-
)}
|
|
1714
|
-
onCut={this.cut}
|
|
1715
|
-
onCopy={this.copy}
|
|
1716
|
-
onDelete={this.delete}
|
|
1717
|
-
onOrderItem={this.onOrderItem}
|
|
1718
|
-
onMoveToTop={this.onMoveToTop}
|
|
1719
|
-
onMoveToBottom={this.onMoveToBottom}
|
|
1720
|
-
/>
|
|
1721
|
-
))}
|
|
1722
|
-
</Table.Body>
|
|
1723
|
-
</Table>
|
|
1725
|
+
))}
|
|
1726
|
+
</Table.Body>
|
|
1727
|
+
</Table>
|
|
1728
|
+
</div>
|
|
1724
1729
|
|
|
1725
1730
|
<div className="contents-pagination">
|
|
1726
1731
|
<Pagination
|
|
@@ -172,15 +172,31 @@ class Form extends Component {
|
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
|
|
176
|
+
let selectedBlock = null;
|
|
177
|
+
if (
|
|
178
|
+
formData.hasOwnProperty(blocksLayoutFieldname) &&
|
|
179
|
+
formData[blocksLayoutFieldname].items.length > 0
|
|
180
|
+
) {
|
|
181
|
+
selectedBlock = formData[blocksLayoutFieldname].items[0];
|
|
182
|
+
|
|
183
|
+
if (config.blocks?.initialBlocksFocus?.[this.props.type]) {
|
|
184
|
+
//Default selected is not the first block, but the one from config.
|
|
185
|
+
Object.keys(formData[blocksFieldname]).forEach((b_key) => {
|
|
186
|
+
if (
|
|
187
|
+
formData[blocksFieldname][b_key]['@type'] ===
|
|
188
|
+
config.blocks?.initialBlocksFocus?.[this.props.type]
|
|
189
|
+
) {
|
|
190
|
+
selectedBlock = b_key;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
175
195
|
this.state = {
|
|
176
196
|
formData,
|
|
177
197
|
initialFormData: cloneDeep(formData),
|
|
178
198
|
errors: {},
|
|
179
|
-
selected:
|
|
180
|
-
formData.hasOwnProperty(blocksLayoutFieldname) &&
|
|
181
|
-
formData[blocksLayoutFieldname].items.length > 0
|
|
182
|
-
? formData[blocksLayoutFieldname].items[0]
|
|
183
|
-
: null,
|
|
199
|
+
selected: selectedBlock,
|
|
184
200
|
multiSelected: [],
|
|
185
201
|
isClient: false,
|
|
186
202
|
};
|
|
@@ -157,6 +157,7 @@ class ArrayWidget extends Component {
|
|
|
157
157
|
),
|
|
158
158
|
onChange: PropTypes.func.isRequired,
|
|
159
159
|
wrapped: PropTypes.bool,
|
|
160
|
+
creatable: PropTypes.bool, //if widget has no vocab and you want to be creatable
|
|
160
161
|
};
|
|
161
162
|
|
|
162
163
|
/**
|
|
@@ -176,6 +177,7 @@ class ArrayWidget extends Component {
|
|
|
176
177
|
error: [],
|
|
177
178
|
choices: [],
|
|
178
179
|
value: null,
|
|
180
|
+
creatable: false,
|
|
179
181
|
};
|
|
180
182
|
|
|
181
183
|
/**
|
|
@@ -242,7 +244,23 @@ class ArrayWidget extends Component {
|
|
|
242
244
|
const { SortableContainer } = this.props.reactSortableHOC;
|
|
243
245
|
const Select = this.props.reactSelect.default;
|
|
244
246
|
const SortableSelect =
|
|
245
|
-
|
|
247
|
+
// It will be only createable if the named vocabulary is in the widget definition
|
|
248
|
+
// (hint) like:
|
|
249
|
+
// list_field_voc_unconstrained = schema.List(
|
|
250
|
+
// title=u"List field with values from vocabulary but not constrained to them.",
|
|
251
|
+
// description=u"zope.schema.List",
|
|
252
|
+
// value_type=schema.TextLine(),
|
|
253
|
+
// required=False,
|
|
254
|
+
// missing_value=[],
|
|
255
|
+
// )
|
|
256
|
+
// directives.widget(
|
|
257
|
+
// "list_field_voc_unconstrained",
|
|
258
|
+
// AjaxSelectFieldWidget,
|
|
259
|
+
// vocabulary="plone.app.vocabularies.PortalTypes",
|
|
260
|
+
// )
|
|
261
|
+
this.props?.choices &&
|
|
262
|
+
!getVocabFromHint(this.props) &&
|
|
263
|
+
!this.props.creatable
|
|
246
264
|
? SortableContainer(Select)
|
|
247
265
|
: SortableContainer(CreatableSelect);
|
|
248
266
|
|
|
@@ -119,6 +119,14 @@ Disabled.args = {
|
|
|
119
119
|
disabled: true,
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
+
export const Creatable = WidgetStory.bind({ widget: ArrayWidget });
|
|
123
|
+
Creatable.args = {
|
|
124
|
+
id: 'field-creatable',
|
|
125
|
+
title: 'Field with creatable',
|
|
126
|
+
description: 'Allows creation of new terms',
|
|
127
|
+
creatable: true,
|
|
128
|
+
};
|
|
129
|
+
|
|
122
130
|
const getOptionsGenerator = (count) => {
|
|
123
131
|
const options = [];
|
|
124
132
|
for (let i = 0; i < count; i = i + 1) {
|
|
@@ -148,8 +148,8 @@ export class DatetimeWidgetComponent extends Component {
|
|
|
148
148
|
const moment = this.props.moment.default;
|
|
149
149
|
if (time) {
|
|
150
150
|
const base = (this.getInternalValue() || moment()).set({
|
|
151
|
-
hours: time
|
|
152
|
-
minutes: time
|
|
151
|
+
hours: time?.hours() ?? 0,
|
|
152
|
+
minutes: time?.minutes() ?? 0,
|
|
153
153
|
seconds: 0,
|
|
154
154
|
});
|
|
155
155
|
const dateValue = base.toISOString();
|
|
@@ -5,7 +5,9 @@ import config from '@plone/volto/registry';
|
|
|
5
5
|
|
|
6
6
|
const NavItem = ({ item, lang }) => {
|
|
7
7
|
const { settings } = config;
|
|
8
|
-
|
|
8
|
+
// The item.url in the root is ''
|
|
9
|
+
// TODO: make more consistent it everywhere (eg. reducers to return '/' instead of '')
|
|
10
|
+
if (isInternalURL(item.url) || item.url === '') {
|
|
9
11
|
return (
|
|
10
12
|
<NavLink
|
|
11
13
|
to={item.url === '' ? '/' : item.url}
|
package/src/config/Blocks.jsx
CHANGED
|
@@ -454,5 +454,12 @@ const blocksConfig = {
|
|
|
454
454
|
const requiredBlocks = ['title'];
|
|
455
455
|
|
|
456
456
|
const initialBlocks = {};
|
|
457
|
+
const initialBlocksFocus = {}; //{Document:'title'}
|
|
457
458
|
|
|
458
|
-
export {
|
|
459
|
+
export {
|
|
460
|
+
groupBlocksOrder,
|
|
461
|
+
requiredBlocks,
|
|
462
|
+
blocksConfig,
|
|
463
|
+
initialBlocks,
|
|
464
|
+
initialBlocksFocus,
|
|
465
|
+
};
|
package/src/config/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
requiredBlocks,
|
|
27
27
|
blocksConfig,
|
|
28
28
|
initialBlocks,
|
|
29
|
+
initialBlocksFocus,
|
|
29
30
|
} from './Blocks';
|
|
30
31
|
import { loadables } from './Loadables';
|
|
31
32
|
|
|
@@ -174,6 +175,7 @@ let config = {
|
|
|
174
175
|
blocksConfig,
|
|
175
176
|
groupBlocksOrder,
|
|
176
177
|
initialBlocks,
|
|
178
|
+
initialBlocksFocus,
|
|
177
179
|
showEditBlocksInBabelView: false,
|
|
178
180
|
},
|
|
179
181
|
addonRoutes: [],
|
|
@@ -4,7 +4,6 @@ import { getAPIResourceWithAuth } from '@plone/volto/helpers';
|
|
|
4
4
|
const HEADERS = ['content-type', 'content-disposition', 'cache-control'];
|
|
5
5
|
|
|
6
6
|
function fileMiddleware(req, res, next) {
|
|
7
|
-
const { errorHandler } = req.app.locals;
|
|
8
7
|
getAPIResourceWithAuth(req)
|
|
9
8
|
.then((resource) => {
|
|
10
9
|
// Just forward the headers that we need
|
|
@@ -14,8 +13,8 @@ function fileMiddleware(req, res, next) {
|
|
|
14
13
|
}
|
|
15
14
|
});
|
|
16
15
|
res.send(resource.body);
|
|
17
|
-
}
|
|
18
|
-
.catch(
|
|
16
|
+
})
|
|
17
|
+
.catch(next);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export default function () {
|
|
@@ -4,7 +4,6 @@ import { getAPIResourceWithAuth } from '@plone/volto/helpers';
|
|
|
4
4
|
const HEADERS = ['content-type', 'content-disposition', 'cache-control'];
|
|
5
5
|
|
|
6
6
|
function imageMiddleware(req, res, next) {
|
|
7
|
-
const { errorHandler } = req.app.locals;
|
|
8
7
|
getAPIResourceWithAuth(req)
|
|
9
8
|
.then((resource) => {
|
|
10
9
|
// Just forward the headers that we need
|
|
@@ -13,10 +12,9 @@ function imageMiddleware(req, res, next) {
|
|
|
13
12
|
res.set(header, resource.headers[header]);
|
|
14
13
|
}
|
|
15
14
|
});
|
|
16
|
-
|
|
17
15
|
res.send(resource.body);
|
|
18
|
-
}
|
|
19
|
-
.catch(
|
|
16
|
+
})
|
|
17
|
+
.catch(next);
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export default function () {
|
|
@@ -34,11 +34,5 @@ export const getAPIResourceWithAuth = (req) =>
|
|
|
34
34
|
if (authToken) {
|
|
35
35
|
request.set('Authorization', `Bearer ${authToken}`);
|
|
36
36
|
}
|
|
37
|
-
request.
|
|
38
|
-
if (error) {
|
|
39
|
-
reject(error);
|
|
40
|
-
} else {
|
|
41
|
-
resolve(res);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
37
|
+
request.then(resolve).catch(reject);
|
|
44
38
|
});
|
|
@@ -61,7 +61,7 @@ const widgetValidation = {
|
|
|
61
61
|
},
|
|
62
62
|
url: {
|
|
63
63
|
isValidURL: (urlValue, urlObj, intlFunc) => {
|
|
64
|
-
const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)
|
|
64
|
+
const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?|^((http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gm;
|
|
65
65
|
const isValid = urlRegex.test(urlValue);
|
|
66
66
|
return !isValid ? intlFunc(messages.isValidURL) : null;
|
|
67
67
|
},
|
package/src/server.jsx
CHANGED
|
@@ -72,6 +72,34 @@ const middleware = (config.settings.expressMiddleware || []).filter((m) => m);
|
|
|
72
72
|
server.all('*', setupServer);
|
|
73
73
|
if (middleware.length) server.use('/', middleware);
|
|
74
74
|
|
|
75
|
+
server.use(function (err, req, res, next) {
|
|
76
|
+
if (err) {
|
|
77
|
+
const { store } = req.app.locals;
|
|
78
|
+
const errorPage = (
|
|
79
|
+
<Provider store={store} onError={reactIntlErrorHandler}>
|
|
80
|
+
<StaticRouter context={{}} location={req.url}>
|
|
81
|
+
<ErrorPage message={err.message} />
|
|
82
|
+
</StaticRouter>
|
|
83
|
+
</Provider>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
res.set({
|
|
87
|
+
'Cache-Control': 'public, max-age=60, no-transform',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/* Displays error in console
|
|
91
|
+
* TODO:
|
|
92
|
+
* - get ignored codes from Plone error_log
|
|
93
|
+
*/
|
|
94
|
+
const ignoredErrors = [301, 302, 401, 404];
|
|
95
|
+
if (!ignoredErrors.includes(err.status)) console.error(err);
|
|
96
|
+
|
|
97
|
+
res
|
|
98
|
+
.status(err.status || 500) // If error happens in Volto code itself error status is undefined
|
|
99
|
+
.send(`<!doctype html> ${renderToString(errorPage)}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
75
103
|
function setupServer(req, res, next) {
|
|
76
104
|
const api = new Api(req);
|
|
77
105
|
|
|
@@ -78,6 +78,18 @@
|
|
|
78
78
|
top: 3px;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
.contents-table-wrapper {
|
|
82
|
+
width: 100%;
|
|
83
|
+
overflow-x: auto;
|
|
84
|
+
|
|
85
|
+
.ui.attached.table {
|
|
86
|
+
width: 100%;
|
|
87
|
+
max-width: 100%;
|
|
88
|
+
|
|
89
|
+
border: none;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
.ui.table .icon-align,
|
|
82
94
|
.ui.dropdown .menu > .item.icon-align {
|
|
83
95
|
display: flex;
|