@eeacms/volto-clms-theme 1.0.105 → 1.0.108

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
@@ -4,8 +4,39 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [1.0.108](https://github.com/eea/volto-clms-theme/compare/1.0.107...1.0.108)
8
+
9
+ - EmailWidget fix [`cd56839`](https://github.com/eea/volto-clms-theme/commit/cd5683951ae27f7ceb51703593e898e33928bf0e)
10
+
11
+ #### [1.0.107](https://github.com/eea/volto-clms-theme/compare/1.0.106...1.0.107)
12
+
13
+ > 14 July 2022
14
+
15
+ - Develop [`#278`](https://github.com/eea/volto-clms-theme/pull/278)
16
+ - Minor fixes [`#277`](https://github.com/eea/volto-clms-theme/pull/277)
17
+ - remove unused import [`dc57cf5`](https://github.com/eea/volto-clms-theme/commit/dc57cf5d5a3c1537edd762854be35ae076fc0359)
18
+ - unify related items in one component [`87fa84f`](https://github.com/eea/volto-clms-theme/commit/87fa84f78c06ec666b2a5ae720e6faa76df9ed1d)
19
+ - show related products/datasets always [`a35faad`](https://github.com/eea/volto-clms-theme/commit/a35faad04dc54e78afa9fc47fe9545ff7fe36303)
20
+ - show only File items [`abb4859`](https://github.com/eea/volto-clms-theme/commit/abb48592819a4bf338fc34aebdc1f1ecd920a226)
21
+
22
+ #### [1.0.106](https://github.com/eea/volto-clms-theme/compare/1.0.105...1.0.106)
23
+
24
+ > 13 July 2022
25
+
26
+ - Develop [`#276`](https://github.com/eea/volto-clms-theme/pull/276)
27
+ - new field types for form [`a452429`](https://github.com/eea/volto-clms-theme/commit/a452429eff799806d58e05e3e4164bc885d6eeb9)
28
+ - handle SelectWidget choices until the vocabulary is loaded [`0614fb5`](https://github.com/eea/volto-clms-theme/commit/0614fb584f52571d15e37fbe921e0694594a01f4)
29
+ - EmailWidget fix and ImageWidget created for form block [`fc1d16b`](https://github.com/eea/volto-clms-theme/commit/fc1d16baf2466dec09d835147fc3df839811e409)
30
+ - lint fix [`af5f960`](https://github.com/eea/volto-clms-theme/commit/af5f9605ec147cb010245a58696087abaed55c35)
31
+ - EmailWidget override for correct anguage and size limitation [`8a6c253`](https://github.com/eea/volto-clms-theme/commit/8a6c253f590fcf753d1a274e8ab7767f366d8c1e)
32
+ - number of current filters [`195cc43`](https://github.com/eea/volto-clms-theme/commit/195cc43d351d0e9793a0f462ecf8bdf9433a1def)
33
+ - subscription modal block [`30db876`](https://github.com/eea/volto-clms-theme/commit/30db876826dd53db6bbb11507de11af10a71a4eb)
34
+
7
35
  #### [1.0.105](https://github.com/eea/volto-clms-theme/compare/1.0.104...1.0.105)
8
36
 
37
+ > 11 July 2022
38
+
39
+ - Develop [`#275`](https://github.com/eea/volto-clms-theme/pull/275)
9
40
  - File card [`#274`](https://github.com/eea/volto-clms-theme/pull/274)
10
41
  - sort items taxonomy items [`#272`](https://github.com/eea/volto-clms-theme/pull/272)
11
42
  - Changes in download cart [`#271`](https://github.com/eea/volto-clms-theme/pull/271)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-clms-theme",
3
- "version": "1.0.105",
3
+ "version": "1.0.108",
4
4
  "description": "volto-clms-theme: Volto theme for CLMS site",
5
5
  "main": "src/index.js",
6
6
  "author": "CodeSyntax for the European Environment Agency",
@@ -1,4 +1,4 @@
1
- import { SubscriptionView } from '@eeacms/volto-clms-theme/components/CLMSSubscriptionView';
1
+ import CclButton from '@eeacms/volto-clms-theme/components/CclButton/CclButton';
2
2
  import { SidebarPortal } from '@plone/volto/components';
3
3
  import { SubscriptionSchema } from './schema';
4
4
  import InlineForm from '@plone/volto/components/manage/Form/InlineForm';
@@ -16,7 +16,7 @@ const SubscriptionBlockEdit = (props) => {
16
16
  aria-hidden="true"
17
17
  />
18
18
  </div>
19
- <SubscriptionView type={data.type || 'events'} />
19
+ <CclButton mode={'filled'}>{data.title || 'Text example...'}</CclButton>
20
20
  <SidebarPortal selected={selected}>
21
21
  <InlineForm
22
22
  schema={SubscriptionSchema()}
@@ -1,9 +1,20 @@
1
1
  import { SubscriptionView } from '@eeacms/volto-clms-theme/components/CLMSSubscriptionView';
2
+ import CclModal from '@eeacms/volto-clms-theme/components/CclModal/CclModal';
3
+ import CclButton from '@eeacms/volto-clms-theme/components/CclButton/CclButton';
2
4
 
3
5
  const SubscriptionBlockView = (props) => {
4
6
  const { data } = props;
5
7
 
6
- return <SubscriptionView type={data.type || 'events'} />;
8
+ return (
9
+ <CclModal
10
+ trigger={
11
+ <CclButton mode={'filled'}>{data.title || 'Text example...'}</CclButton>
12
+ }
13
+ size="small"
14
+ >
15
+ <SubscriptionView type={data.type || 'events'} />
16
+ </CclModal>
17
+ );
7
18
  };
8
19
 
9
20
  export default SubscriptionBlockView;
@@ -4,7 +4,7 @@ export const SubscriptionSchema = () => ({
4
4
  {
5
5
  id: 'default',
6
6
  title: 'Default',
7
- fields: ['type'],
7
+ fields: ['type', 'title'],
8
8
  },
9
9
  ],
10
10
  properties: {
@@ -16,6 +16,10 @@ export const SubscriptionSchema = () => ({
16
16
  ['newsletter', 'newsletter'],
17
17
  ],
18
18
  },
19
+ title: {
20
+ title: 'Title',
21
+ type: 'string',
22
+ },
19
23
  },
20
24
  required: ['product'],
21
25
  });
@@ -45,11 +45,13 @@ const VocabularyWidget = ({
45
45
  getVocabulary={() => {}}
46
46
  getVocabularyTokenTitle={() => {}}
47
47
  choices={
48
- vocabItems?.loaded && [
49
- ...vocabItems.items.map((item) => {
50
- return [item.label, item.label];
51
- }),
52
- ]
48
+ vocabItems?.loaded
49
+ ? [
50
+ ...vocabItems.items.map((item) => {
51
+ return [item.label, item.label];
52
+ }),
53
+ ]
54
+ : []
53
55
  }
54
56
  value={value}
55
57
  onChange={onChange}
@@ -25,7 +25,7 @@ const FilterList = (props) => {
25
25
  (v) =>
26
26
  v[1] &&
27
27
  baseFacets.length > 0 &&
28
- !baseFacets.map((bf) => bf.field?.value).includes(v[0]),
28
+ baseFacets.map((bf) => bf.field?.value).includes(v[0]),
29
29
  ),
30
30
  );
31
31
 
@@ -65,6 +65,7 @@ import homeBand from '@plone/volto/icons/image-wide.svg';
65
65
  import linkSVG from '@plone/volto/icons/link.svg';
66
66
  import navSVG from '@plone/volto/icons/nav.svg';
67
67
  import upSVG from '@plone/volto/icons/up-key.svg';
68
+ import ImageWidget from '@eeacms/volto-clms-theme/components/Widgets/ImageWidget';
68
69
 
69
70
  export const customGroupBlocksOrder = [
70
71
  {
@@ -474,6 +475,31 @@ const customBlocks = (config) => ({
474
475
  isMulti: true,
475
476
  }),
476
477
  },
478
+ {
479
+ id: 'topic_vocabulary',
480
+ label: 'Topics Vocabulary',
481
+ component: (props) =>
482
+ VocabularyWidget({
483
+ ...props,
484
+ vocabulary: 'clms.types.TopicsVocabulary',
485
+ isMulti: true,
486
+ }),
487
+ },
488
+ {
489
+ id: 'spatial_coverage_vocabulary',
490
+ label: 'Spatial Coverage Vocabulary',
491
+ component: (props) =>
492
+ VocabularyWidget({
493
+ ...props,
494
+ vocabulary: 'clms.types.UseCaseSpatialCoverageVocabulary',
495
+ isMulti: true,
496
+ }),
497
+ },
498
+ {
499
+ id: 'image_field_widget',
500
+ label: 'Image Field Widget',
501
+ component: ImageWidget,
502
+ },
477
503
  ],
478
504
  },
479
505
  });
@@ -1,7 +1,7 @@
1
1
  import './meetingstyles.less';
2
2
 
3
3
  import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
4
- import { Header, Image, Message, Segment, Grid } from 'semantic-ui-react';
4
+ import { Header, Image, Message, Segment } from 'semantic-ui-react';
5
5
  import { Icon, Toast } from '@plone/volto/components';
6
6
  import {
7
7
  Recurrence,
@@ -23,6 +23,7 @@ import CclListingCards from '@eeacms/volto-clms-theme/components/Blocks/CustomTe
23
23
  import config from '@plone/volto/registry';
24
24
  import AnimateHeight from 'react-animate-height';
25
25
  import { Accordion } from 'semantic-ui-react';
26
+ import { CLMSRelatedItems } from '../CLMSRelatedItems';
26
27
 
27
28
  export const CLMSMeetingView = (props) => {
28
29
  const { content, intl } = props;
@@ -112,15 +113,9 @@ export const CLMSMeetingView = (props) => {
112
113
  );
113
114
  history.push(props.location.pathname + '/form');
114
115
  }
115
-
116
- const files =
117
- content.items !== undefined
118
- ? content.items.map((item) => (item['@type'] === 'File' ? item : null))
119
- : [];
120
- const index = files.indexOf(null);
121
- if (index > -1) {
122
- files.splice(index, 1);
123
- }
116
+ const files = content.items
117
+ ? content.items.filter((item) => item['@type'] === 'File')
118
+ : [];
124
119
  const RegistrationButton = ({ rContent, rMeeting_register, rIsLoggedIn }) => {
125
120
  return (
126
121
  <>
@@ -425,6 +420,7 @@ export const CLMSMeetingView = (props) => {
425
420
  )}
426
421
  <StringToHTML string={content.text?.data || ''} />
427
422
  <LightGalleryListing />
423
+
428
424
  {files.length > 0 && (
429
425
  <CclListingCards
430
426
  variation="file"
@@ -456,36 +452,7 @@ export const CLMSMeetingView = (props) => {
456
452
  </Accordion.Title>
457
453
  <Accordion.Content active={activeIndex.includes(0)}>
458
454
  <AnimateHeight animateOpacity duration={500} height={'auto'}>
459
- <ul>
460
- {content.products.map((product, key) => (
461
- <li key={key}>
462
- <Grid columns={2}>
463
- <Grid.Column width={2}>
464
- {product.image_field && (
465
- <img
466
- src={
467
- product.image_field
468
- ? `${product['@id']}/@@images/image`
469
- : 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg'
470
- }
471
- alt={
472
- product.image_field
473
- ? product.image?.filename
474
- : 'Placeholder'
475
- }
476
- />
477
- )}
478
- </Grid.Column>
479
- <Grid.Column width={10}>
480
- <a href={product['@id']}>
481
- <strong>{product.title}</strong>
482
- </a>
483
- <p>{product.description}</p>
484
- </Grid.Column>
485
- </Grid>
486
- </li>
487
- ))}
488
- </ul>
455
+ <CLMSRelatedItems items={content.products} />
489
456
  </AnimateHeight>
490
457
  </Accordion.Content>
491
458
  </Accordion>
@@ -512,36 +479,7 @@ export const CLMSMeetingView = (props) => {
512
479
  </Accordion.Title>
513
480
  <Accordion.Content active={activeDatasetIndex.includes(0)}>
514
481
  <AnimateHeight animateOpacity duration={500} height={'auto'}>
515
- <ul>
516
- {content.datasets.map((dataset, key) => (
517
- <li key={key}>
518
- <Grid columns={2}>
519
- <Grid.Column width={2}>
520
- {dataset.image_field && (
521
- <img
522
- src={
523
- dataset.image_field
524
- ? `${dataset['@id']}/@@images/image`
525
- : 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg'
526
- }
527
- alt={
528
- dataset.image_field
529
- ? dataset.image?.filename
530
- : 'Placeholder'
531
- }
532
- />
533
- )}
534
- </Grid.Column>
535
- <Grid.Column width={10}>
536
- <a href={dataset['@id']}>
537
- <strong>{dataset.title}</strong>
538
- </a>
539
- <p>{dataset.description}</p>
540
- </Grid.Column>
541
- </Grid>
542
- </li>
543
- ))}
544
- </ul>
482
+ <CLMSRelatedItems items={content.datasets} />
545
483
  </AnimateHeight>
546
484
  </Accordion.Content>
547
485
  </Accordion>
@@ -8,18 +8,14 @@ import CclListingCards from '@eeacms/volto-clms-theme/components/Blocks/CustomTe
8
8
  import config from '@plone/volto/registry';
9
9
  import { Icon } from '@plone/volto/components';
10
10
  import AnimateHeight from 'react-animate-height';
11
- import { Accordion, Grid } from 'semantic-ui-react';
11
+ import { Accordion } from 'semantic-ui-react';
12
+ import { CLMSRelatedItems } from '../CLMSRelatedItems';
12
13
 
13
14
  const CLMSNewsItemView = (props) => {
14
15
  const { content } = props;
15
- const files =
16
- content.items !== undefined
17
- ? content.items.map((item) => (item['@type'] === 'File' ? item : null))
18
- : [];
19
- const index = files.indexOf(null);
20
- if (index > -1) {
21
- files.splice(index, 1);
22
- }
16
+ const files = content.items
17
+ ? content.items.filter((item) => item['@type'] === 'File')
18
+ : [];
23
19
  const [activeIndex, setActiveIndex] = React.useState([99]);
24
20
 
25
21
  const handleClick = ({ index }) => {
@@ -80,128 +76,58 @@ const CLMSNewsItemView = (props) => {
80
76
  <StringToHTML string={content.text?.data || ''} />
81
77
  </div>
82
78
  <LightGalleryListing />
83
- {files.length > 0 && (
84
- <CclListingCards
85
- variation="file"
86
- items={files}
87
- linkHref={`${files['@id']}/@@download/file`}
88
- />
89
- )}
90
- {content?.products?.length > 0 && (
91
- <Accordion fluid styled>
92
- <Accordion.Title
93
- as={'h2'}
94
- onClick={() => handleClick({ index: 0 })}
95
- className={'accordion-title align-arrow-right'}
96
- >
97
- {activeIndex.includes(0) ? (
98
- <Icon
99
- name={iconName(content, titleIcons.opened)}
100
- size="24px"
101
- />
102
- ) : (
103
- <Icon
104
- name={iconName(content, titleIcons.closed)}
105
- size="24px"
106
- />
107
- )}
108
- <span>Related products</span>
109
- </Accordion.Title>
110
- <Accordion.Content active={activeIndex.includes(0)}>
111
- <AnimateHeight animateOpacity duration={500} height={'auto'}>
112
- <ul>
113
- {content.products.map((product, key) => (
114
- <li key={key}>
115
- <Grid columns={2}>
116
- <Grid.Column width={2}>
117
- {product.image_field && (
118
- <img
119
- src={
120
- product.image_field
121
- ? `${product['@id']}/@@images/image`
122
- : 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg'
123
- }
124
- alt={
125
- product.image_field
126
- ? product.image?.filename
127
- : 'Placeholder'
128
- }
129
- />
130
- )}
131
- </Grid.Column>
132
- <Grid.Column width={10}>
133
- <a href={product['@id']}>
134
- <strong>{product.title}</strong>
135
- </a>
136
- <p>{product.description}</p>
137
- </Grid.Column>
138
- </Grid>
139
- </li>
140
- ))}
141
- </ul>
142
- </AnimateHeight>
143
- </Accordion.Content>
144
- </Accordion>
145
- )}
146
- {content?.datasets?.length > 0 && (
147
- <Accordion fluid styled>
148
- <Accordion.Title
149
- as={'h2'}
150
- onClick={() => handleDatasetClick({ datasetindex: 0 })}
151
- className={'accordion-title align-arrow-right'}
152
- >
153
- {activeDatasetIndex.includes(0) ? (
154
- <Icon
155
- name={iconName(content, titleIcons.opened)}
156
- size="24px"
157
- />
158
- ) : (
159
- <Icon
160
- name={iconName(content, titleIcons.closed)}
161
- size="24px"
162
- />
163
- )}
164
- <span>Related datasets</span>
165
- </Accordion.Title>
166
- <Accordion.Content active={activeDatasetIndex.includes(0)}>
167
- <AnimateHeight animateOpacity duration={500} height={'auto'}>
168
- <ul>
169
- {content.datasets.map((dataset, key) => (
170
- <li key={key}>
171
- <Grid columns={2}>
172
- <Grid.Column width={2}>
173
- {dataset.image_field && (
174
- <img
175
- src={
176
- dataset.image_field
177
- ? `${dataset['@id']}/@@images/image`
178
- : 'https://eu-copernicus.github.io/copernicus-component-library/assets/images/image_placeholder.jpg'
179
- }
180
- alt={
181
- dataset.image_field
182
- ? dataset.image?.filename
183
- : 'Placeholder'
184
- }
185
- />
186
- )}
187
- </Grid.Column>
188
- <Grid.Column width={10}>
189
- <a href={dataset['@id']}>
190
- <strong>{dataset.title}</strong>
191
- </a>
192
- <p>{dataset.description}</p>
193
- </Grid.Column>
194
- </Grid>
195
- </li>
196
- ))}
197
- </ul>
198
- </AnimateHeight>
199
- </Accordion.Content>
200
- </Accordion>
201
- )}
202
79
  </div>
203
80
  </>
204
81
  )}
82
+ {files.length > 0 && (
83
+ <CclListingCards
84
+ variation="file"
85
+ items={files}
86
+ linkHref={`${files['@id']}/@@download/file`}
87
+ />
88
+ )}
89
+ {content?.products?.length > 0 && (
90
+ <Accordion fluid styled>
91
+ <Accordion.Title
92
+ as={'h2'}
93
+ onClick={() => handleClick({ index: 0 })}
94
+ className={'accordion-title align-arrow-right'}
95
+ >
96
+ {activeIndex.includes(0) ? (
97
+ <Icon name={iconName(content, titleIcons.opened)} size="24px" />
98
+ ) : (
99
+ <Icon name={iconName(content, titleIcons.closed)} size="24px" />
100
+ )}
101
+ <span>Related products</span>
102
+ </Accordion.Title>
103
+ <Accordion.Content active={activeIndex.includes(0)}>
104
+ <AnimateHeight animateOpacity duration={500} height={'auto'}>
105
+ <CLMSRelatedItems items={content.products} />
106
+ </AnimateHeight>
107
+ </Accordion.Content>
108
+ </Accordion>
109
+ )}
110
+ {content?.datasets?.length > 0 && (
111
+ <Accordion fluid styled>
112
+ <Accordion.Title
113
+ as={'h2'}
114
+ onClick={() => handleDatasetClick({ datasetindex: 0 })}
115
+ className={'accordion-title align-arrow-right'}
116
+ >
117
+ {activeDatasetIndex.includes(0) ? (
118
+ <Icon name={iconName(content, titleIcons.opened)} size="24px" />
119
+ ) : (
120
+ <Icon name={iconName(content, titleIcons.closed)} size="24px" />
121
+ )}
122
+ <span>Related datasets</span>
123
+ </Accordion.Title>
124
+ <Accordion.Content active={activeDatasetIndex.includes(0)}>
125
+ <AnimateHeight animateOpacity duration={500} height={'auto'}>
126
+ <CLMSRelatedItems items={content.datasets} />
127
+ </AnimateHeight>
128
+ </Accordion.Content>
129
+ </Accordion>
130
+ )}
205
131
  </div>
206
132
  );
207
133
  };
@@ -0,0 +1,39 @@
1
+ import { Grid, Image } from 'semantic-ui-react';
2
+ import PlaceHolder from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-thumbnail-placeholder.jpg';
3
+
4
+ const CLMSRelatedItems = (props) => {
5
+ const { items } = props;
6
+
7
+ return (
8
+ <>
9
+ {items.map((item, key) => (
10
+ <>
11
+ <Grid columns="2">
12
+ <Grid.Column width={3} key={key}>
13
+ {item.mage_field ? (
14
+ <Image
15
+ src={`${item['@id']}/@@images/image/mini`}
16
+ alt={item?.title || 'Placeholder'}
17
+ />
18
+ ) : (
19
+ <Image
20
+ fluid="true"
21
+ src={PlaceHolder}
22
+ alt={item?.title || 'Placeholder'}
23
+ />
24
+ )}
25
+ </Grid.Column>
26
+ <Grid.Column width={9}>
27
+ <a href={item['@id']}>
28
+ <strong>{item.title}</strong>
29
+ </a>
30
+ {item?.description && <p>{item.description}</p>}
31
+ </Grid.Column>
32
+ </Grid>
33
+ </>
34
+ ))}
35
+ </>
36
+ );
37
+ };
38
+
39
+ export { CLMSRelatedItems };
@@ -0,0 +1,3 @@
1
+ import { CLMSRelatedItems } from './CLMSRelatedItems';
2
+
3
+ export { CLMSRelatedItems };
@@ -27,7 +27,7 @@ function CclModal(props) {
27
27
  onOpen={() => openModal()}
28
28
  open={open}
29
29
  trigger={trigger}
30
- className={'modal-clms'}
30
+ className={'modal-clms-container'}
31
31
  size={size}
32
32
  >
33
33
  <div className={'modal-close modal-clms-close'}>
@@ -0,0 +1,239 @@
1
+ /**
2
+ * FileWidget component.
3
+ * @module components/manage/Widgets/FileWidget
4
+ */
5
+
6
+ import React from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import { Button, Image, Dimmer } from 'semantic-ui-react';
9
+ import { readAsDataURL } from 'promise-file-reader';
10
+ import { injectIntl } from 'react-intl';
11
+ import deleteSVG from '@plone/volto/icons/delete.svg';
12
+ import { Icon, FormFieldWrapper } from '@plone/volto/components';
13
+ import loadable from '@loadable/component';
14
+ import { flattenToAppURL } from '@plone/volto/helpers';
15
+ import { defineMessages, useIntl } from 'react-intl';
16
+
17
+ const imageMimetypes = [
18
+ 'image/png',
19
+ 'image/jpeg',
20
+ 'image/webp',
21
+ 'image/jpg',
22
+ 'image/gif',
23
+ 'image/svg+xml',
24
+ ];
25
+ const Dropzone = loadable(() => import('react-dropzone'));
26
+
27
+ const messages = defineMessages({
28
+ releaseDrag: {
29
+ id: 'Drop images here ...',
30
+ defaultMessage: 'Drop images here ...',
31
+ },
32
+ editFile: {
33
+ id: 'Drop image here to replace the existing image',
34
+ defaultMessage: 'Drop image here to replace the existing image',
35
+ },
36
+ fileDrag: {
37
+ id: 'Drop image here to upload a new image',
38
+ defaultMessage: 'Drop image here to upload a new image',
39
+ },
40
+ replaceFile: {
41
+ id: 'Replace existing image',
42
+ defaultMessage: 'Replace existing image',
43
+ },
44
+ addNewFile: {
45
+ id: 'Choose a image',
46
+ defaultMessage: 'Choose a image',
47
+ },
48
+ invalid_file: {
49
+ id: 'The uploaded file is not valid',
50
+ defaultMessage: 'The uploaded file must be an image',
51
+ },
52
+ });
53
+
54
+ /**
55
+ * FileWidget component class.
56
+ * @function FileWidget
57
+ * @returns {string} Markup of the component.
58
+ *
59
+ * To use it, in schema properties, declare a field like:
60
+ *
61
+ * ```jsx
62
+ * {
63
+ * title: "File",
64
+ * widget: 'file',
65
+ * }
66
+ * ```
67
+ * or:
68
+ *
69
+ * ```jsx
70
+ * {
71
+ * title: "File",
72
+ * type: 'object',
73
+ * }
74
+ * ```
75
+ *
76
+ */
77
+ const FileWidget = (props) => {
78
+ const { id, value, onChange } = props;
79
+ const [fileType, setFileType] = React.useState(false);
80
+ const intl = useIntl();
81
+
82
+ React.useEffect(() => {
83
+ if (value && imageMimetypes.includes(value['content-type'])) {
84
+ setFileType(true);
85
+ }
86
+ }, [value]);
87
+
88
+ const imgsrc = value?.download
89
+ ? `${flattenToAppURL(value?.download)}?id=${Date.now()}`
90
+ : null || value?.data
91
+ ? `data:${value['content-type']};${value.encoding},${value.data}`
92
+ : null;
93
+
94
+ /**
95
+ * Drop handler
96
+ * @method onDrop
97
+ * @param {array} files File objects
98
+ * @returns {undefined}
99
+ */
100
+ const onDrop = (files) => {
101
+ const file = files[0];
102
+ readAsDataURL(file).then((data) => {
103
+ const fields = data.match(/^data:(.*);(.*),(.*)$/);
104
+ if (
105
+ fields[0] !== 'image/png' ||
106
+ 'image/jpeg' ||
107
+ 'image/webp' ||
108
+ 'image/jpg' ||
109
+ 'image/gif' ||
110
+ 'image/svg+xml'
111
+ ) {
112
+ // eslint-disable-next-line no-alert
113
+ alert(intl.formatMessage(messages.invalid_file));
114
+ return;
115
+ } else {
116
+ onChange(id, {
117
+ data: fields[3],
118
+ encoding: fields[2],
119
+ 'content-type': fields[1],
120
+ filename: file.name,
121
+ });
122
+ }
123
+ });
124
+
125
+ let reader = new FileReader();
126
+ reader.onload = function () {
127
+ const fields = reader.result.match(/^data:(.*);(.*),(.*)$/);
128
+ if (imageMimetypes.includes(fields[1])) {
129
+ setFileType(true);
130
+ let imagePreview = document.getElementById(`field-${id}-image`);
131
+ imagePreview.src = reader.result;
132
+ } else {
133
+ setFileType(false);
134
+ }
135
+ };
136
+ reader.readAsDataURL(files[0]);
137
+ };
138
+
139
+ return (
140
+ <FormFieldWrapper {...props}>
141
+ <Dropzone onDrop={onDrop}>
142
+ {({ getRootProps, getInputProps, isDragActive }) => (
143
+ <div className="file-widget-dropzone" {...getRootProps()}>
144
+ {isDragActive && <Dimmer active></Dimmer>}
145
+ {fileType ? (
146
+ <Image
147
+ className="image-preview"
148
+ id={`field-${id}-image`}
149
+ size="small"
150
+ src={imgsrc}
151
+ />
152
+ ) : (
153
+ <div className="dropzone-placeholder">
154
+ {isDragActive ? (
155
+ <p className="dropzone-text">
156
+ {intl.formatMessage(messages.releaseDrag)}
157
+ </p>
158
+ ) : value ? (
159
+ <p className="dropzone-text">
160
+ {intl.formatMessage(messages.editFile)}
161
+ </p>
162
+ ) : (
163
+ <p className="dropzone-text">
164
+ {intl.formatMessage(messages.fileDrag)}
165
+ </p>
166
+ )}
167
+ </div>
168
+ )}
169
+
170
+ <label className="label-file-widget-input">
171
+ {value
172
+ ? intl.formatMessage(messages.replaceFile)
173
+ : intl.formatMessage(messages.addNewFile)}
174
+ </label>
175
+ <input
176
+ {...getInputProps({
177
+ type: 'file',
178
+ style: { display: 'none' },
179
+ })}
180
+ id={`field-${id}`}
181
+ name={id}
182
+ type="file"
183
+ />
184
+ </div>
185
+ )}
186
+ </Dropzone>
187
+ <div className="field-file-name">
188
+ {value && value.filename}
189
+ {value && (
190
+ <Button
191
+ icon
192
+ basic
193
+ className="delete-button"
194
+ aria-label="delete file"
195
+ onClick={() => {
196
+ onChange(id, null);
197
+ setFileType(false);
198
+ }}
199
+ >
200
+ <Icon name={deleteSVG} size="20px" />
201
+ </Button>
202
+ )}
203
+ </div>
204
+ </FormFieldWrapper>
205
+ );
206
+ };
207
+
208
+ /**
209
+ * Property types.
210
+ * @property {Object} propTypes Property types.
211
+ * @static
212
+ */
213
+ FileWidget.propTypes = {
214
+ id: PropTypes.string.isRequired,
215
+ title: PropTypes.string.isRequired,
216
+ description: PropTypes.string,
217
+ required: PropTypes.bool,
218
+ error: PropTypes.arrayOf(PropTypes.string),
219
+ value: PropTypes.shape({
220
+ '@type': PropTypes.string,
221
+ title: PropTypes.string,
222
+ }),
223
+ onChange: PropTypes.func.isRequired,
224
+ wrapped: PropTypes.bool,
225
+ };
226
+
227
+ /**
228
+ * Default properties.
229
+ * @property {Object} defaultProps Default properties.
230
+ * @static
231
+ */
232
+ FileWidget.defaultProps = {
233
+ description: null,
234
+ required: false,
235
+ error: [],
236
+ value: null,
237
+ };
238
+
239
+ export default injectIntl(FileWidget);
@@ -0,0 +1,126 @@
1
+ /**
2
+ * EmailWidget component.
3
+ * @module components/manage/Widgets/EmailWidget
4
+ */
5
+
6
+ import { FormFieldWrapper } from '@plone/volto/components';
7
+ import PropTypes from 'prop-types';
8
+ import React from 'react';
9
+ import { defineMessages, useIntl } from 'react-intl';
10
+
11
+ /** EmailWidget, a widget for email addresses
12
+ *
13
+ * To use it, in schema properties, declare a field like:
14
+ *
15
+ * ```jsx
16
+ * {
17
+ * title: "Email",
18
+ * widget: 'email',
19
+ * }
20
+ * ```
21
+ */
22
+ const EmailWidget = (props) => {
23
+ const {
24
+ id,
25
+ value = '',
26
+ onChange,
27
+ onBlur,
28
+ onClick,
29
+ minLength,
30
+ placeholder,
31
+ isDisabled,
32
+ } = props;
33
+ const inputId = `field-${id}`;
34
+ const intl = useIntl();
35
+
36
+ const messages = defineMessages({
37
+ invalid_email: {
38
+ id: 'The entered email address is not valid',
39
+ defaultMessage: 'The entered email address is not valid',
40
+ },
41
+ });
42
+
43
+ const email =
44
+ typeof window !== 'undefined' && document.getElementById(inputId) !== null
45
+ ? document.getElementById(inputId).value
46
+ : '';
47
+ // email.addEventListener('input', function (e) {
48
+ React.useEffect(() => {
49
+ if (email !== '') {
50
+ if (email.validity !== undefined) {
51
+ if (email.validity.typeMismatch) {
52
+ email.setCustomValidity(intl.formatMessage(messages.invalid_email));
53
+ email.reportValidity();
54
+ } else {
55
+ email.setCustomValidity('');
56
+ }
57
+ }
58
+ } else {
59
+ return;
60
+ }
61
+ // eslint-disable-next-line react-hooks/exhaustive-deps
62
+ }, [email.value]);
63
+ // });
64
+
65
+ return (
66
+ <FormFieldWrapper {...props} className="email">
67
+ <input
68
+ className="ui input"
69
+ id={inputId}
70
+ name={id}
71
+ type="email"
72
+ value={value || ''}
73
+ disabled={isDisabled}
74
+ placeholder={placeholder}
75
+ onChange={({ target }) =>
76
+ onChange(id, target.value === '' ? undefined : target.value)
77
+ }
78
+ onBlur={({ target }) =>
79
+ onBlur(id, target.value === '' ? undefined : target.value)
80
+ }
81
+ onClick={() => onClick()}
82
+ minLength={minLength || null}
83
+ maxLength={200}
84
+ />
85
+ </FormFieldWrapper>
86
+ );
87
+ };
88
+
89
+ /**
90
+ * Property types
91
+ * @property {Object} propTypes Property types.
92
+ * @static
93
+ */
94
+ EmailWidget.propTypes = {
95
+ id: PropTypes.string.isRequired,
96
+ title: PropTypes.string.isRequired,
97
+ description: PropTypes.string,
98
+ required: PropTypes.bool,
99
+ error: PropTypes.arrayOf(PropTypes.string),
100
+ value: PropTypes.string,
101
+ onChange: PropTypes.func.isRequired,
102
+ onBlur: PropTypes.func,
103
+ onClick: PropTypes.func,
104
+ minLength: PropTypes.number,
105
+ maxLength: PropTypes.number,
106
+ placeholder: PropTypes.string,
107
+ };
108
+
109
+ /**
110
+ * Default properties.
111
+ * @property {Object} defaultProps Default properties.
112
+ * @static
113
+ */
114
+ EmailWidget.defaultProps = {
115
+ description: null,
116
+ required: false,
117
+ error: [],
118
+ value: null,
119
+ onChange: () => {},
120
+ onBlur: () => {},
121
+ onClick: () => {},
122
+ minLength: null,
123
+ maxLength: null,
124
+ };
125
+
126
+ export default EmailWidget;