@plone/volto 14.2.1 → 14.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/locales/ca/LC_MESSAGES/volto.po +5 -0
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +5 -0
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +5 -0
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +5 -0
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +5 -0
  12. package/locales/eu.json +1 -1
  13. package/locales/fr/LC_MESSAGES/volto.po +5 -0
  14. package/locales/fr.json +1 -1
  15. package/locales/it/LC_MESSAGES/volto.po +5 -0
  16. package/locales/it.json +1 -1
  17. package/locales/ja/LC_MESSAGES/volto.po +5 -0
  18. package/locales/ja.json +1 -1
  19. package/locales/nl/LC_MESSAGES/volto.po +5 -0
  20. package/locales/nl.json +1 -1
  21. package/locales/pt/LC_MESSAGES/volto.po +5 -0
  22. package/locales/pt.json +1 -1
  23. package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
  24. package/locales/pt_BR.json +1 -1
  25. package/locales/ro/LC_MESSAGES/volto.po +5 -0
  26. package/locales/ro.json +1 -1
  27. package/locales/volto.pot +6 -1
  28. package/package.json +3 -3
  29. package/src/components/manage/Add/Add.jsx +14 -0
  30. package/src/components/manage/Contents/Contents.jsx +225 -220
  31. package/src/components/manage/Widgets/DatetimeWidget.jsx +2 -2
  32. package/src/components/manage/Widgets/FormFieldWrapper.jsx +14 -1
  33. package/src/components/theme/Anontools/Anontools.jsx +1 -1
  34. package/src/express-middleware/files.js +2 -3
  35. package/src/express-middleware/images.js +2 -4
  36. package/src/helpers/Api/APIResourceWithAuth.js +1 -7
  37. package/src/helpers/Content/Content.js +16 -0
  38. package/src/helpers/Content/Content.test.js +20 -1
  39. package/src/helpers/FormValidation/FormValidation.js +1 -1
  40. package/src/helpers/index.js +1 -0
  41. package/src/server.jsx +28 -0
  42. package/theme/themes/pastanaga/extras/contents.less +12 -0
  43. package/theme/themes/pastanaga/extras/widgets.less +12 -0
@@ -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();
@@ -18,6 +18,10 @@ const messages = defineMessages({
18
18
  id: 'Delete',
19
19
  defaultMessage: 'Delete',
20
20
  },
21
+ language_independent: {
22
+ id: 'Language independent field.',
23
+ defaultMessage: 'Language independent field.',
24
+ },
21
25
  });
22
26
  /**
23
27
  * FormFieldWrapper component class.
@@ -85,6 +89,7 @@ class FormFieldWrapper extends Component {
85
89
  onDelete,
86
90
  intl,
87
91
  noForInFieldLabel,
92
+ multilingual_options,
88
93
  } = this.props;
89
94
  const wdg = (
90
95
  <>
@@ -107,6 +112,9 @@ class FormFieldWrapper extends Component {
107
112
  description ? 'help' : '',
108
113
  className,
109
114
  `field-wrapper-${id}`,
115
+ multilingual_options?.language_independent
116
+ ? 'language-independent-field'
117
+ : null,
110
118
  )}
111
119
  >
112
120
  <Grid>
@@ -160,7 +168,12 @@ class FormFieldWrapper extends Component {
160
168
  {description && (
161
169
  <Grid.Row stretched>
162
170
  <Grid.Column stretched width="12">
163
- <p className="help">{description}</p>
171
+ <p className="help">
172
+ {this.props.multilingual_options
173
+ ? `${intl.formatMessage(messages.language_independent)} `
174
+ : null}
175
+ {description}
176
+ </p>
164
177
  </Grid.Column>
165
178
  </Grid.Row>
166
179
  )}
@@ -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
  '',
@@ -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
  });
@@ -63,3 +63,19 @@ export function getContentIcon(type, isFolderish) {
63
63
  if (type in contentIcons) return contentIcons[type];
64
64
  return isFolderish ? contentIcons.Folder : contentIcons.File;
65
65
  }
66
+
67
+ /**
68
+ * Get the language independent fields presents in a schema.
69
+ * @description Configurable in config
70
+ * @function getLanguageIndependentFields
71
+ * @param {string} schema content type JSON Schema serialization
72
+ * @returns {array} List of language independent fields
73
+ */
74
+ export function getLanguageIndependentFields(schema) {
75
+ const { properties } = schema;
76
+ return Object.keys(properties).filter(
77
+ (field) =>
78
+ Object.keys(properties[field]).includes('multilingual_options') &&
79
+ properties[field]['multilingual_options']?.['language_independent'],
80
+ );
81
+ }
@@ -1,4 +1,8 @@
1
- import { nestContent, getContentIcon } from './Content';
1
+ import {
2
+ nestContent,
3
+ getContentIcon,
4
+ getLanguageIndependentFields,
5
+ } from './Content';
2
6
  import contentExistingSVG from '@plone/volto/icons/content-existing.svg';
3
7
  import linkSVG from '@plone/volto/icons/link.svg';
4
8
  import calendarSVG from '@plone/volto/icons/calendar.svg';
@@ -77,4 +81,19 @@ describe('Content', () => {
77
81
  expect(getContentIcon('Custom', false)).toBe(fileSVG);
78
82
  });
79
83
  });
84
+
85
+ describe('getLanguageIndependentFields', () => {
86
+ it('returns the language independenr field', () => {
87
+ const schema = {
88
+ properties: {
89
+ lif: {
90
+ multilingual_options: {
91
+ language_independent: true,
92
+ },
93
+ },
94
+ },
95
+ };
96
+ expect(getLanguageIndependentFields(schema)).toStrictEqual(['lif']);
97
+ });
98
+ });
80
99
  });
@@ -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
  },
@@ -33,6 +33,7 @@ export {
33
33
  nestContent,
34
34
  getLayoutFieldname,
35
35
  getContentIcon,
36
+ getLanguageIndependentFields,
36
37
  } from '@plone/volto/helpers/Content/Content';
37
38
  export {
38
39
  addBlock,