@eeacms/volto-clms-theme 1.0.76 → 1.0.79

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.
@@ -12,17 +12,39 @@ import { Icon } from '@plone/volto/components';
12
12
 
13
13
  import PlaceHolder from '@eeacms/volto-clms-theme/../theme/clms/img/ccl-thumbnail-placeholder.jpg';
14
14
 
15
- const CardImage = ({ card, size = 'preview' }) => {
15
+ const CardImage = ({ card, size = 'preview', isCustomCard }) => {
16
16
  return card?.image_field ? (
17
17
  <img
18
18
  src={`${card.getURL}/@@images/${card?.image_field}/${size}`}
19
19
  alt={card?.image?.alt || 'Placeholder'}
20
20
  />
21
+ ) : isCustomCard && card?.image?.url ? (
22
+ <img src={`${card.image.url}/@@images/image`} alt={card.image.alt} />
21
23
  ) : (
22
24
  <img src={PlaceHolder} alt={card?.image?.alt || 'Placeholder'} />
23
25
  );
24
26
  };
25
27
 
28
+ const CardLink = ({ url, children, className, condition = true }) => {
29
+ function hasProtocol(protocolUrl) {
30
+ return (
31
+ protocolUrl.startsWith('https://') || protocolUrl.startsWith('http://')
32
+ );
33
+ }
34
+ const RenderElement = hasProtocol(url) ? 'a' : Link;
35
+ return !condition ? (
36
+ children
37
+ ) : hasProtocol(url) ? (
38
+ <RenderElement className={className} href={url}>
39
+ {children}
40
+ </RenderElement>
41
+ ) : (
42
+ <RenderElement className={className} to={url}>
43
+ {children}
44
+ </RenderElement>
45
+ );
46
+ };
47
+
26
48
  const DocCard = ({ card, url, showEditor, children }) => {
27
49
  return (
28
50
  <>
@@ -54,12 +76,26 @@ const DocCard = ({ card, url, showEditor, children }) => {
54
76
  );
55
77
  };
56
78
  function CclCard(props) {
57
- const { type, children, card, showEditor = false } = props;
79
+ const {
80
+ type,
81
+ children,
82
+ card,
83
+ showEditor = false,
84
+ CclImageEditor = null,
85
+ onClickImage = () => {
86
+ return '';
87
+ },
88
+ isCustomCard = false,
89
+ } = props;
58
90
  let url = '/';
59
91
  let content_type = '';
60
- if (card) {
61
- url = card['@id'] || card.hrerf || '/';
92
+ if (card && !isCustomCard) {
93
+ url = card['@id'] || '/';
62
94
  content_type = portal_types_labels[card['@type']] || card['@type'];
95
+ } else {
96
+ if (card?.url?.length > 0) {
97
+ url = card.url[0]['@id'] || '/';
98
+ }
63
99
  }
64
100
  const conditional_types = [
65
101
  'doc',
@@ -69,107 +105,146 @@ function CclCard(props) {
69
105
  'threeColumns',
70
106
  'globalSearch',
71
107
  ];
108
+ const wrapperClass =
109
+ 'card-' + (type === 'globalSearch' ? 'doc' : type || 'line');
72
110
  return (
73
- <div
74
- className={'card-' + (type === 'globalSearch' ? 'doc' : type || 'line')}
111
+ <CardLink
112
+ url={url}
113
+ className={wrapperClass}
114
+ condition={type === 'block' || type === 'threeColumns'}
75
115
  >
76
- {conditional_types.includes(type) ? (
77
- <>
78
- {type === 'doc' && (
79
- <>
80
- <DocCard card={card} url={url} showEditor={showEditor}>
81
- {children}
82
- </DocCard>
83
- </>
84
- )}
85
- {type === 'globalSearch' && (
86
- <>
87
- <Label ribbon="right" color="olive">
88
- {content_type}
89
- </Label>
90
- <DocCard card={card} url={url} showEditor={showEditor}>
91
- {children}
92
- </DocCard>
93
- </>
94
- )}
95
- {(type === 'block' || type === 'threeColumns') && (
96
- <>
97
- <div className={`card-${type}-image`}>
98
- <CardImage card={card} size={'preview'} />
99
- </div>
100
- <div className="card-text">
101
- <div className="card-title">
102
- <Link to={url}>{card?.title || 'Card default title'}</Link>
103
- </div>
104
- <div className="card-description">{card?.description}</div>
105
- {children}
106
- </div>
107
- </>
108
- )}
109
- {type === 'news' && (
110
- <>
111
- <div className="card-news-image">
112
- <CardImage card={card} size={'mini'} />
113
- </div>
114
- <div className="card-news-text">
115
- <div className="card-news-title">
116
- <Link to={url}>{card?.title || 'Card default title'}</Link>
117
- </div>
118
- <div className="card-news-date">
119
- {new Date(card?.effective).toLocaleString()}
116
+ <div
117
+ tabIndex="0"
118
+ role="button"
119
+ onClick={() => onClickImage()}
120
+ onKeyDown={() => onClickImage()}
121
+ className={
122
+ !(type === 'block' || type === 'threeColumns') && wrapperClass
123
+ }
124
+ >
125
+ {conditional_types.includes(type) ? (
126
+ <>
127
+ {type === 'doc' && (
128
+ <>
129
+ <DocCard card={card} url={url} showEditor={showEditor}>
130
+ {children}
131
+ </DocCard>
132
+ </>
133
+ )}
134
+ {type === 'globalSearch' && (
135
+ <>
136
+ <Label ribbon="right" color="olive">
137
+ {content_type}
138
+ </Label>
139
+ <DocCard card={card} url={url} showEditor={showEditor}>
140
+ {children}
141
+ </DocCard>
142
+ </>
143
+ )}
144
+ {(type === 'block' || type === 'threeColumns') && (
145
+ <>
146
+ <div className={`card-${type}-image`}>
147
+ {isCustomCard && CclImageEditor ? (
148
+ CclImageEditor
149
+ ) : (
150
+ <CardImage
151
+ isCustomCard={isCustomCard}
152
+ card={card}
153
+ size={'preview'}
154
+ />
155
+ )}
120
156
  </div>
121
- <p className="card-news-description">{card?.description}</p>
122
- </div>
123
- </>
124
- )}
125
- {type === 'event' && (
126
- <>
127
- <div className={'card-event-text'}>
128
- <div className="card-event-title">
129
- <Link to={url}>{card?.title || 'Event default title'}</Link>
157
+ <div className="card-text">
158
+ <div className="card-title">{card?.title}</div>
159
+ <div className="card-description">{card?.description}</div>
160
+ {children}
130
161
  </div>
131
- <div className="card-event-when">
132
- <FontAwesomeIcon icon={['far', 'calendar-alt']} />
133
- <div className="card-event-when-text">
134
- <When
135
- start={card.start}
136
- end={card.whole_day ? card.start : card.end}
137
- whole_day={card.whole_day}
162
+ </>
163
+ )}
164
+ {type === 'news' && (
165
+ <>
166
+ <div className="card-news-image">
167
+ {isCustomCard && CclImageEditor ? (
168
+ CclImageEditor
169
+ ) : (
170
+ <CardImage
171
+ isCustomCard={isCustomCard}
172
+ card={card}
173
+ size={'mini'}
138
174
  />
175
+ )}
176
+ </div>
177
+ <div className="card-news-text">
178
+ <div className="card-news-title">
179
+ <CardLink url={url}>{card?.title}</CardLink>
180
+ {/* <CardLink url={url} title={card?.title} /> */}
139
181
  </div>
182
+ <div className="card-news-date">
183
+ {new Date(card?.effective).toLocaleString()}
184
+ </div>
185
+ <p className="card-news-description">{card?.description}</p>
140
186
  </div>
141
- {card?.location ? (
142
- <div className="card-event-where">
143
- <>
144
- <FontAwesomeIcon icon={['fas', 'map-marker-alt']} />
145
- <div className="card-event-where-text">
146
- {card?.location}
147
- </div>
148
- </>
187
+ </>
188
+ )}
189
+ {type === 'event' && (
190
+ <>
191
+ <div className={'card-event-text'}>
192
+ <div className="card-event-title">
193
+ <CardLink url={url}>{card?.title}</CardLink>
194
+ {/* <CardLink url={url} title={card?.title} /> */}
195
+ </div>
196
+ <div className="card-event-when">
197
+ <FontAwesomeIcon icon={['far', 'calendar-alt']} />
198
+ <div className="card-event-when-text">
199
+ <When
200
+ start={card.start}
201
+ end={card.whole_day ? card.start : card.end}
202
+ whole_day={card.whole_day}
203
+ />
204
+ </div>
149
205
  </div>
150
- ) : (
151
- ''
152
- )}
153
- <p className="card-event-description">{card?.description}</p>
206
+ {card?.location ? (
207
+ <div className="card-event-where">
208
+ <>
209
+ <FontAwesomeIcon icon={['fas', 'map-marker-alt']} />
210
+ <div className="card-event-where-text">
211
+ {card?.location}
212
+ </div>
213
+ </>
214
+ </div>
215
+ ) : (
216
+ ''
217
+ )}
218
+ <p className="card-event-description">{card?.description}</p>
219
+ </div>
220
+ </>
221
+ )}
222
+ </>
223
+ ) : (
224
+ <>
225
+ <div className="card-image">
226
+ {isCustomCard && CclImageEditor ? (
227
+ CclImageEditor
228
+ ) : (
229
+ <CardImage
230
+ isCustomCard={isCustomCard}
231
+ card={card}
232
+ size={'mini'}
233
+ />
234
+ )}
235
+ </div>
236
+ <div className={'card-text'}>
237
+ <div className="card-title">
238
+ <CardLink url={url}>{card?.title}</CardLink>
239
+ {/* <CardLink url={url} title={card?.title} /> */}
154
240
  </div>
155
- </>
156
- )}
157
- </>
158
- ) : (
159
- <>
160
- <div className="card-image">
161
- <CardImage card={card} size={'mini'} />
162
- </div>
163
- <div className={'card-text'}>
164
- <div className="card-title">
165
- <Link to={url}>{card?.title || 'Card default title'}</Link>
241
+ <div className="card-description">{card?.description}</div>
242
+ {children}
166
243
  </div>
167
- <div className="card-description">{card?.description}</div>
168
- {children}
169
- </div>
170
- </>
171
- )}
172
- </div>
244
+ </>
245
+ )}
246
+ </div>
247
+ </CardLink>
173
248
  );
174
249
  }
175
250
 
@@ -14,6 +14,7 @@
14
14
  }
15
15
 
16
16
  .card-line .card-image {
17
+ border: solid 1px #a0b12833;
17
18
  margin-right: 1rem;
18
19
  }
19
20
 
@@ -59,6 +60,7 @@
59
60
  .card-line-color .card-image {
60
61
  width: 12rem;
61
62
  height: 12rem;
63
+ border: solid 1px #a0b12833;
62
64
  margin: 0;
63
65
  }
64
66
 
@@ -101,6 +103,7 @@
101
103
 
102
104
  .card-block .card-block-image {
103
105
  height: 12rem;
106
+ border: solid 1px #a0b12833;
104
107
  }
105
108
 
106
109
  .card-block img {
@@ -137,6 +140,7 @@
137
140
 
138
141
  .card-threeColumns .card-threeColumns-image {
139
142
  height: 12rem;
143
+ border: solid 1px #a0b12833;
140
144
  }
141
145
 
142
146
  .card-threeColumns img {
@@ -186,13 +190,16 @@
186
190
  }
187
191
 
188
192
  .card-news .card-news-image {
193
+ width: 10rem;
194
+ height: 10rem;
189
195
  flex-shrink: 0;
196
+ border: solid 1px #a0b12833;
190
197
  margin-right: 1.5rem;
191
198
  }
192
199
 
193
200
  .card-news .card-news-image img {
194
- width: 10rem;
195
- height: 10rem;
201
+ width: 100%;
202
+ height: 100%;
196
203
  object-fit: cover;
197
204
  }
198
205
 
@@ -249,13 +256,16 @@
249
256
  }
250
257
 
251
258
  .card-event .card-event-image {
259
+ width: 10rem;
260
+ height: 10rem;
252
261
  flex-shrink: 0;
262
+ border: solid 1px #a0b12833;
253
263
  margin-right: 1.5rem;
254
264
  }
255
265
 
256
266
  .card-event .card-event-image img {
257
- width: 10rem;
258
- height: 10rem;
267
+ width: 100%;
268
+ height: 100%;
259
269
  object-fit: cover;
260
270
  }
261
271
 
@@ -0,0 +1,316 @@
1
+ import './styles.less';
2
+
3
+ // import { CardBlockSchema, HomeUsersSchema } from './HomeUsersSchema';
4
+ /** upload image */
5
+ import { Dimmer, Image, Label, Loader, Message } from 'semantic-ui-react';
6
+ import { Icon } from '@plone/volto/components';
7
+ import React, { useState } from 'react';
8
+ import { useDispatch, useSelector } from 'react-redux';
9
+ import { defineMessages, useIntl } from 'react-intl';
10
+ import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers';
11
+
12
+ import { createContent, getContent } from '@plone/volto/actions';
13
+ import editingSVG from '@plone/volto/icons/editing.svg';
14
+ import navSVG from '@plone/volto/icons/nav.svg';
15
+ import { readAsDataURL } from 'promise-file-reader';
16
+ import uploadSVG from '@plone/volto/icons/upload.svg';
17
+
18
+ const messages = defineMessages({
19
+ uploadImage: {
20
+ id: 'Upload image',
21
+ defaultMessage: 'Upload or select an image',
22
+ },
23
+ uploadingFile: {
24
+ id: 'Uploading file',
25
+ defaultMessage: 'Uploading file',
26
+ },
27
+ removeImage: {
28
+ id: 'Remove image',
29
+ defaultMessage: 'Remove image',
30
+ },
31
+ editImage: {
32
+ id: 'Edit image',
33
+ defaultMessage: 'Edit',
34
+ },
35
+ });
36
+
37
+ const onChangeCardBlockImage = (
38
+ onChangeBlock,
39
+ block,
40
+ data,
41
+ selectedCardBlock,
42
+ imageValue,
43
+ ) => {
44
+ onChangeBlock(block, {
45
+ ...data,
46
+ customCards: {
47
+ ...data.customCards,
48
+ blocks: {
49
+ ...data.customCards.blocks,
50
+ [selectedCardBlock]: {
51
+ ...data.customCards.blocks[selectedCardBlock],
52
+ image: imageValue,
53
+ },
54
+ },
55
+ },
56
+ });
57
+ };
58
+
59
+ const onUploadImage = (pathname, e, setUploading, dispatch) => {
60
+ e.stopPropagation();
61
+ e.preventDefault();
62
+ const target = e.target;
63
+ const file = target.files[0];
64
+ readAsDataURL(file).then((data) => {
65
+ setUploading(true);
66
+ const fields = data.match(/^data:(.*);(.*),(.*)$/);
67
+ dispatch(
68
+ createContent(getBaseUrl(pathname), {
69
+ '@type': 'Image',
70
+ image: {
71
+ data: fields[3],
72
+ encoding: fields[2],
73
+ 'content-type': fields[1],
74
+ filename: file.name,
75
+ },
76
+ }),
77
+ ).then((response) => {
78
+ dispatch(getContent(getBaseUrl(pathname)));
79
+ });
80
+ });
81
+ };
82
+
83
+ const ImageEditor = ({
84
+ editable,
85
+ pathname,
86
+ setUploading,
87
+ uploading,
88
+ dispatch,
89
+ onChangeBlock,
90
+ block,
91
+ data,
92
+ selected,
93
+ selectedCardBlock,
94
+ setSelectedCardBlock,
95
+ openObjectBrowser,
96
+ request,
97
+ content,
98
+ }) => {
99
+ React.useEffect(() => {
100
+ if (!__SERVER__) {
101
+ setUploading(false);
102
+ }
103
+ if (!selected) {
104
+ setSelectedCardBlock(-1);
105
+ }
106
+ // eslint-disable-next-line react-hooks/exhaustive-deps
107
+ }, [selected]);
108
+ React.useEffect(() => {
109
+ if (request?.loaded && !request?.loading && uploading) {
110
+ setUploading(false);
111
+ onChangeCardBlockImage(onChangeBlock, block, data, selectedCardBlock, {
112
+ url: flattenToAppURL(content['@id']),
113
+ alt: content?.image?.filename,
114
+ });
115
+ }
116
+ /* eslint-disable-next-line */
117
+ }, [request]);
118
+ return (
119
+ editable && (
120
+ <p>
121
+ <label className="file">
122
+ <Icon className="ui button" name={uploadSVG} size={35} />
123
+ <input
124
+ type="file"
125
+ onChange={(e) => onUploadImage(pathname, e, setUploading, dispatch)}
126
+ style={{ display: 'none' }}
127
+ />
128
+ </label>
129
+ <label className="file">
130
+ <Icon className="ui button" name={navSVG} size={35} />
131
+ <input
132
+ type="button"
133
+ onClick={(e) => {
134
+ e.stopPropagation();
135
+ e.preventDefault();
136
+ openObjectBrowser({
137
+ onSelectItem: (url, element) =>
138
+ onChangeCardBlockImage(
139
+ onChangeBlock,
140
+ block,
141
+ data,
142
+ selectedCardBlock,
143
+ {
144
+ url: element['@id'],
145
+ alt: element.title,
146
+ },
147
+ ),
148
+ });
149
+ }}
150
+ style={{ display: 'none' }}
151
+ />
152
+ </label>
153
+ </p>
154
+ )
155
+ );
156
+ };
157
+
158
+ function isSelected(selected, uid, selectedCardBlock) {
159
+ return selected && uid === selectedCardBlock;
160
+ }
161
+
162
+ const UploadingDimmer = ({ uid, selectedCardBlock, uploading, intl }) => {
163
+ return uid === selectedCardBlock && uploading ? (
164
+ <Dimmer active>
165
+ <Loader indeterminate>
166
+ {intl.formatMessage(messages.uploadingFile)}
167
+ </Loader>
168
+ </Dimmer>
169
+ ) : (
170
+ <></>
171
+ );
172
+ };
173
+
174
+ const handleEdit = (handleEditProps) => {
175
+ const {
176
+ selected,
177
+ uid,
178
+ selectedCardBlock,
179
+ setSelectedCardBlock,
180
+ openObjectBrowser,
181
+ onChangeBlock,
182
+ block,
183
+ data,
184
+ } = handleEditProps;
185
+ if (isSelected(selected, uid, selectedCardBlock)) {
186
+ openObjectBrowser({
187
+ onSelectItem: (url, element) =>
188
+ onChangeCardBlockImage(onChangeBlock, block, data, selectedCardBlock, {
189
+ url: element['@id'],
190
+ alt: element.title,
191
+ }),
192
+ });
193
+ } else {
194
+ setSelectedCardBlock(uid);
195
+ }
196
+ };
197
+
198
+ const CclImageEditor = ({
199
+ block,
200
+ data,
201
+ editable,
202
+ imageUrl,
203
+ onChangeBlock,
204
+ openObjectBrowser,
205
+ pathname,
206
+ selected,
207
+ selectedCardBlock,
208
+ setSelectedCardBlock,
209
+ uid,
210
+ }) => {
211
+ const intl = useIntl();
212
+
213
+ const [uploading, setUploading] = useState(false);
214
+ const dispatch = useDispatch();
215
+ const request = useSelector((state) => state.content.create);
216
+ const content = useSelector((state) => state.content.data);
217
+
218
+ return imageUrl ? (
219
+ <>
220
+ {isSelected(selected, uid, selectedCardBlock) && (
221
+ <Label
222
+ as="a"
223
+ color="red"
224
+ pointing="below"
225
+ onClick={() =>
226
+ onChangeCardBlockImage(
227
+ onChangeBlock,
228
+ block,
229
+ data,
230
+ selectedCardBlock,
231
+ {},
232
+ )
233
+ }
234
+ >
235
+ {intl.formatMessage(messages.removeImage)}
236
+ </Label>
237
+ )}
238
+
239
+ <div
240
+ role="button"
241
+ tabindex="0"
242
+ className="ui image edit-image-container"
243
+ onClick={(e) => {
244
+ e.stopPropagation();
245
+ e.preventDefault();
246
+ const handleEditPC = {
247
+ selected: selected,
248
+ uid: uid,
249
+ selectedCardBlock: selectedCardBlock,
250
+ setSelectedCardBlock: setSelectedCardBlock,
251
+ openObjectBrowser: openObjectBrowser,
252
+ onChangeBlock: onChangeBlock,
253
+ block: block,
254
+ data: data,
255
+ };
256
+ handleEdit(handleEditPC);
257
+ }}
258
+ onKeyDown={(e) => {
259
+ e.stopPropagation();
260
+ e.preventDefault();
261
+ const handleEditPK = {
262
+ selected: selected,
263
+ uid: uid,
264
+ selectedCardBlock: selectedCardBlock,
265
+ setSelectedCardBlock: setSelectedCardBlock,
266
+ openObjectBrowser: openObjectBrowser,
267
+ onChangeBlock: onChangeBlock,
268
+ block: block,
269
+ data: data,
270
+ };
271
+ handleEdit(handleEditPK);
272
+ }}
273
+ >
274
+ <Image size="small" src={`${imageUrl}/@@images/image/preview`} />
275
+ {isSelected(selected, uid, selectedCardBlock) && (
276
+ <div className="edit-image">
277
+ <Icon className="ui" name={editingSVG} size={35} />
278
+ {intl.formatMessage(messages.editImage)}
279
+ </div>
280
+ )}
281
+ </div>
282
+ </>
283
+ ) : (
284
+ <div className="image-add">
285
+ <Message className="image-message">
286
+ <UploadingDimmer
287
+ uid={uid}
288
+ selectedCardBlock={selectedCardBlock}
289
+ uploading={uploading}
290
+ intl={intl}
291
+ />
292
+ <center>
293
+ <h4>{intl.formatMessage(messages.uploadImage)}</h4>
294
+ <ImageEditor
295
+ editable={editable}
296
+ pathname={pathname}
297
+ setUploading={setUploading}
298
+ uploading={uploading}
299
+ dispatch={dispatch}
300
+ onChangeBlock={onChangeBlock}
301
+ block={block}
302
+ data={data}
303
+ selected={selected}
304
+ selectedCardBlock={selectedCardBlock}
305
+ setSelectedCardBlock={setSelectedCardBlock}
306
+ openObjectBrowser={openObjectBrowser}
307
+ request={request}
308
+ content={content}
309
+ />
310
+ </center>
311
+ </Message>
312
+ </div>
313
+ );
314
+ };
315
+
316
+ export default CclImageEditor;