@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 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 VERSIONS="plone.restapi=8.16.2 plone.app.iterate=4.0.2 plone.rest=2.0.0a1 plone.app.vocabularies=4.3.0" -e ADDONS="plone.volto" -e ZCML="plone.volto.cors" -e PROFILES="plone.volto:default-homepage" plone
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://demo-energy-union.eea.europa.eu) (Thematic website focusing on European strides towards mitigating climate change, developed by [Eau de Web](https://www.eaudeweb.ro), 2020)
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.1.1",
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.0.0",
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",
@@ -308,6 +308,7 @@ class Add extends Component {
308
308
  ref={this.form}
309
309
  key="translated-or-new-content-form"
310
310
  schema={this.props.schema}
311
+ type={this.props.type}
311
312
  formData={{
312
313
  ...(blocksFieldname && {
313
314
  [blocksFieldname]:
@@ -1491,236 +1491,241 @@ class Contents extends Component {
1491
1491
  </Dropdown.Menu>
1492
1492
  </Dropdown>
1493
1493
  </Segment>
1494
- <Table selectable compact singleLine attached>
1495
- <Table.Header>
1496
- <Table.Row>
1497
- <Table.HeaderCell>
1498
- <Dropdown
1499
- item
1500
- upward={false}
1501
- className="sort-icon"
1502
- aria-label={this.props.intl.formatMessage(
1503
- messages.sort,
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
- </Dropdown.Menu>
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={checkboxCheckedSVG}
1606
- color="#007eb1"
1508
+ name={configurationSVG}
1607
1509
  size="24px"
1608
- />{' '}
1609
- <FormattedMessage
1610
- id="All"
1611
- defaultMessage="All"
1510
+ color="#826a6a"
1511
+ className="configuration-svg"
1612
1512
  />
1613
- </Dropdown.Item>
1614
- <Dropdown.Item onClick={this.onSelectNone}>
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={checkboxUncheckedSVG}
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
- </Dropdown.Item>
1624
- <Dropdown.Divider />
1625
- <Dropdown.Header
1626
- content={this.props.intl.formatMessage(
1627
- messages.selected,
1628
- { count: this.state.selected.length },
1629
- )}
1630
- />
1631
- <Input
1632
- icon={<Icon name={zoomSVG} size="24px" />}
1633
- iconPosition="left"
1634
- className="search"
1635
- placeholder={this.props.intl.formatMessage(
1636
- messages.filter,
1637
- )}
1638
- onChange={this.onChangeSelected}
1639
- onClick={(e) => {
1640
- e.preventDefault();
1641
- e.stopPropagation();
1642
- }}
1643
- />
1644
- <Dropdown.Menu scrolling>
1645
- {map(filteredItems, (item) => (
1646
- <Dropdown.Item
1647
- key={item}
1648
- value={item}
1649
- onClick={this.onDeselect}
1650
- >
1651
- <Icon
1652
- name={deleteSVG}
1653
- color="#e40166"
1654
- size="24px"
1655
- />{' '}
1656
- {this.getFieldById(item, 'title')}
1657
- </Dropdown.Item>
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.Menu>
1661
- </Dropdown>
1662
- </Table.HeaderCell>
1663
- <Table.HeaderCell
1664
- width={Math.ceil(
1665
- 16 / this.state.index.selectedCount,
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
- <FormattedMessage
1669
- id="Title"
1670
- defaultMessage="Title"
1671
- />
1672
- </Table.HeaderCell>
1673
- {map(
1674
- this.state.index.order,
1675
- (index, order) =>
1676
- this.state.index.values[index].selected && (
1677
- <ContentsIndexHeader
1678
- key={index}
1679
- width={Math.ceil(
1680
- 16 / this.state.index.selectedCount,
1681
- )}
1682
- label={this.state.index.values[index].label}
1683
- order={order}
1684
- onOrderIndex={this.onOrderIndex}
1685
- />
1686
- ),
1687
- )}
1688
- <Table.HeaderCell textAlign="right">
1689
- <FormattedMessage
1690
- id="Actions"
1691
- defaultMessage="Actions"
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
- </Table.HeaderCell>
1694
- </Table.Row>
1695
- </Table.Header>
1696
- <Table.Body>
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
@@ -259,6 +259,7 @@ class Edit extends Component {
259
259
  isEditForm
260
260
  ref={this.form}
261
261
  schema={this.props.schema}
262
+ type={this.props.content?.['@type']}
262
263
  formData={this.props.content}
263
264
  requestError={this.state.error}
264
265
  onSubmit={this.onSubmit}
@@ -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
  };
@@ -106,6 +106,7 @@ const TranslationObject = ({
106
106
  key="translation-object-form"
107
107
  schema={schema}
108
108
  formData={translationObject}
109
+ type={translationObject['@type']}
109
110
  onSubmit={() => {
110
111
  /*do nothing*/
111
112
  }}
@@ -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
- this.props?.choices && !getVocabFromHint(this.props)
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.hours(),
152
- minutes: time.minutes(),
151
+ hours: time?.hours() ?? 0,
152
+ minutes: time?.minutes() ?? 0,
153
153
  seconds: 0,
154
154
  });
155
155
  const dateValue = base.toISOString();
@@ -53,7 +53,7 @@ export class Anontools extends Component {
53
53
  <Link
54
54
  aria-label="login"
55
55
  to={`/login${
56
- this.props.content
56
+ this.props.content?.['@id']
57
57
  ? `?return_url=${this.props.content['@id'].replace(
58
58
  settings.apiPath,
59
59
  '',
@@ -5,7 +5,9 @@ import config from '@plone/volto/registry';
5
5
 
6
6
  const NavItem = ({ item, lang }) => {
7
7
  const { settings } = config;
8
- if (isInternalURL(item.url)) {
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}
@@ -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 { groupBlocksOrder, requiredBlocks, blocksConfig, initialBlocks };
459
+ export {
460
+ groupBlocksOrder,
461
+ requiredBlocks,
462
+ blocksConfig,
463
+ initialBlocks,
464
+ initialBlocksFocus,
465
+ };
@@ -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
- }, errorHandler)
18
- .catch(errorHandler);
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
- }, errorHandler)
19
- .catch(errorHandler);
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.end((error, res = {}) => {
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})?(\/.*)?$/gm;
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;