@plone/volto 14.2.0 → 14.3.0

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,42 @@
1
1
  # Change Log
2
2
 
3
+ ## 14.3.0 (2022-01-20)
4
+
5
+ ### Feature
6
+
7
+ - Bump semantic-ui-react to v2.0.3 @nileshgulia1
8
+
9
+ ## 14.2.3 (2022-01-20)
10
+
11
+ ### Bugfix
12
+
13
+ - Fix overflow table in Content view @giuliaghisini
14
+ - Fixed url validation in FormValidation to admit ip addresses. @giuliaghisini
15
+
16
+ ## 14.2.2 (2022-01-13)
17
+
18
+ ### Bugfix
19
+
20
+ - Fix home URL item in Navigation, which was evaluating as non-internal @sneridagh
21
+ - Improve the request handling in `getAPIResourceWithAuth` and in `Api` helper. This fixes the "Cannot set headers once the content has being sent" @sneridagh
22
+ - Fix when you remove the time from DatetimeWidget @iRohitSingh
23
+
24
+ ### Internal
25
+
26
+ - Fix URL for Climate-Energy, a Volto website @tiberiuichim
27
+ - Fix quirky Cypress test in "DX control panel schema" (see https://github.com/plone/volto/runs/4803206906?check_suite_focus=true) @sneridagh
28
+
29
+ ## 14.2.1 (2022-01-12)
30
+
31
+ ### Bugfix
32
+
33
+ - Fix home URL item in Navigation, which was evaluating as non-internal
34
+
35
+ ### Internal
36
+
37
+ - Use plone-backend docker images for Cypress tests @sneridagh
38
+ - Upgrade `query-string` library so it supports Plone `:list` qs marker @sneridagh
39
+
3
40
  ## 14.2.0 (2022-01-04)
4
41
 
5
42
  ### Feature
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.2.0",
12
+ "version": "14.3.0",
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",
@@ -387,7 +387,7 @@
387
387
  "release-it": "14.2.1",
388
388
  "rrule": "2.6.4",
389
389
  "semantic-ui-less": "2.4.1",
390
- "semantic-ui-react": "0.88.1",
390
+ "semantic-ui-react": "2.0.3",
391
391
  "semver": "5.6.0",
392
392
  "serialize-javascript": "3.1.0",
393
393
  "start-server-and-test": "1.10.6",
@@ -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
@@ -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}
@@ -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;