@plone/volto 18.0.0-alpha.31 → 18.0.0-alpha.33

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 (75) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +12 -0
  3. package/locales/ca.json +1 -1
  4. package/locales/de/LC_MESSAGES/volto.po +12 -0
  5. package/locales/de.json +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +12 -0
  7. package/locales/en.json +1 -1
  8. package/locales/es/LC_MESSAGES/volto.po +12 -0
  9. package/locales/es.json +1 -1
  10. package/locales/eu/LC_MESSAGES/volto.po +12 -0
  11. package/locales/eu.json +1 -1
  12. package/locales/fi/LC_MESSAGES/volto.po +12 -0
  13. package/locales/fi.json +1 -1
  14. package/locales/fr/LC_MESSAGES/volto.po +12 -0
  15. package/locales/fr.json +1 -1
  16. package/locales/hi/LC_MESSAGES/volto.po +4989 -0
  17. package/locales/hi.json +1 -0
  18. package/locales/it/LC_MESSAGES/volto.po +12 -0
  19. package/locales/it.json +1 -1
  20. package/locales/ja/LC_MESSAGES/volto.po +12 -0
  21. package/locales/ja.json +1 -1
  22. package/locales/nl/LC_MESSAGES/volto.po +12 -0
  23. package/locales/nl.json +1 -1
  24. package/locales/pt/LC_MESSAGES/volto.po +12 -0
  25. package/locales/pt.json +1 -1
  26. package/locales/pt_BR/LC_MESSAGES/volto.po +12 -0
  27. package/locales/pt_BR.json +1 -1
  28. package/locales/ro/LC_MESSAGES/volto.po +12 -0
  29. package/locales/ro.json +1 -1
  30. package/locales/volto.pot +13 -1
  31. package/locales/zh_CN/LC_MESSAGES/volto.po +12 -0
  32. package/locales/zh_CN.json +1 -1
  33. package/package.json +6 -4
  34. package/razzle.config.js +51 -0
  35. package/src/components/manage/BlockChooser/BlockChooserSearch.jsx +1 -0
  36. package/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +2 -0
  37. package/src/components/manage/Form/Form.jsx +1 -1
  38. package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +158 -65
  39. package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +125 -71
  40. package/src/components/manage/Widgets/IdWidget.jsx +138 -203
  41. package/src/components/manage/Widgets/TextWidget.jsx +92 -124
  42. package/src/components/manage/Widgets/TextWidget.stories.jsx +14 -3
  43. package/src/components/theme/App/App.jsx +5 -0
  44. package/src/components/theme/Footer/Footer.jsx +1 -0
  45. package/src/components/theme/Navigation/Navigation.jsx +1 -1
  46. package/src/components/theme/SlotRenderer/SlotRenderer.tsx +3 -4
  47. package/src/components/theme/View/View.jsx +5 -3
  48. package/src/components/theme/View/View.test.jsx +1 -0
  49. package/src/config/ControlPanels.js +7 -0
  50. package/src/config/config.test.js +232 -0
  51. package/src/config/server.js +0 -1
  52. package/src/constants/Languages.js +1 -0
  53. package/src/express-middleware/files.js +1 -0
  54. package/src/express-middleware/images.js +1 -0
  55. package/src/express-middleware/static.js +37 -19
  56. package/src/helpers/Api/Api.js +12 -0
  57. package/src/helpers/Api/Api.plone.rest.test.js +1 -0
  58. package/src/helpers/Api/Api.test.js +1 -0
  59. package/src/helpers/Html/Html.jsx +6 -8
  60. package/src/helpers/Slots/index.tsx +12 -5
  61. package/src/server.jsx +34 -36
  62. package/theme/themes/pastanaga/extras/blocks.less +10 -10
  63. package/theme/themes/pastanaga/extras/objectbrowser-widget.less +83 -0
  64. package/theme/themes/pastanaga/extras/widgets.less +19 -0
  65. package/types/components/manage/Sidebar/ObjectBrowserNav.d.ts +2 -1
  66. package/types/components/manage/Widgets/IdWidget.d.ts +54 -2
  67. package/types/components/manage/Widgets/TextWidget.d.ts +54 -5
  68. package/types/components/manage/Widgets/TextWidget.stories.d.ts +13 -1
  69. package/types/components/manage/Widgets/index.d.ts +2 -2
  70. package/types/config/Widgets.d.ts +1 -1
  71. package/types/config/config.test.d.ts +1 -0
  72. package/types/config/server.d.ts +0 -3
  73. package/types/constants/Languages.d.ts +1 -0
  74. package/types/helpers/Slots/index.d.ts +5 -3
  75. package/types/start-client.d.ts +1 -1
@@ -21,6 +21,8 @@ import clearSVG from '@plone/volto/icons/clear.svg';
21
21
  import searchSVG from '@plone/volto/icons/zoom.svg';
22
22
  import linkSVG from '@plone/volto/icons/link.svg';
23
23
  import homeSVG from '@plone/volto/icons/home.svg';
24
+ import iconsSVG from '@plone/volto/icons/apps.svg';
25
+ import listSVG from '@plone/volto/icons/list-bullet.svg';
24
26
 
25
27
  import ObjectBrowserNav from '@plone/volto/components/manage/Sidebar/ObjectBrowserNav';
26
28
 
@@ -41,6 +43,18 @@ const messages = defineMessages({
41
43
  id: 'Search SVG',
42
44
  defaultMessage: 'Search SVG',
43
45
  },
46
+ iconView: {
47
+ id: 'Icon View',
48
+ defaultMessage: 'Icon View',
49
+ },
50
+ listView: {
51
+ id: 'List View',
52
+ defaultMessage: 'List View',
53
+ },
54
+ home: {
55
+ id: 'Home',
56
+ defaultMessage: 'Home',
57
+ },
44
58
  of: { id: 'Selected items - x of y', defaultMessage: 'of' },
45
59
  });
46
60
 
@@ -132,6 +146,7 @@ class ObjectBrowserBody extends Component {
132
146
  this.props.mode === 'image'
133
147
  ? this.props.searchableTypes || config.settings.imageObjects
134
148
  : this.props.searchableTypes,
149
+ view: this.props.mode === 'image' ? 'icons' : 'list',
135
150
  };
136
151
  this.searchInputRef = React.createRef();
137
152
  }
@@ -201,10 +216,28 @@ class ObjectBrowserBody extends Component {
201
216
  showSearchInput: !prevState.showSearchInput,
202
217
  }),
203
218
  () => {
204
- if (this.searchInputRef?.current) this.searchInputRef.current.focus();
219
+ if (this.searchInputRef?.current) {
220
+ this.searchInputRef.current.focus();
221
+ } else {
222
+ this.props.searchContent(
223
+ this.state.currentFolder,
224
+ {
225
+ 'path.depth': 1,
226
+ sort_on: 'getObjPositionInParent',
227
+ metadata_fields: '_all',
228
+ b_size: 1000,
229
+ },
230
+ `${this.props.block}-${this.props.mode}`,
231
+ );
232
+ }
205
233
  },
206
234
  );
207
235
 
236
+ toggleView = () =>
237
+ this.setState((prevState) => ({
238
+ view: prevState.view === 'icons' ? 'list' : 'icons',
239
+ }));
240
+
208
241
  onSearch = (e) => {
209
242
  const text = flattenToAppURL(e.target.value);
210
243
  if (text.startsWith('/')) {
@@ -386,25 +419,9 @@ class ObjectBrowserBody extends Component {
386
419
  */
387
420
  render() {
388
421
  return (
389
- <Segment.Group raised>
422
+ <Segment.Group raised className="object-browser">
390
423
  <header className="header pulled">
391
424
  <div className="vertical divider" />
392
- {this.state.currentFolder === '/' ? (
393
- <>
394
- {this.props.mode === 'image' ? (
395
- <Icon name={folderSVG} size="24px" />
396
- ) : (
397
- <Icon name={linkSVG} size="24px" />
398
- )}
399
- </>
400
- ) : (
401
- <button
402
- aria-label={this.props.intl.formatMessage(messages.back)}
403
- onClick={() => this.navigateTo(this.state.parentFolder)}
404
- >
405
- <Icon name={backSVG} size="24px" />
406
- </button>
407
- )}
408
425
  {this.state.showSearchInput ? (
409
426
  <Input
410
427
  className="search"
@@ -414,20 +431,40 @@ class ObjectBrowserBody extends Component {
414
431
  messages.SearchInputPlaceholder,
415
432
  )}
416
433
  />
417
- ) : this.props.mode === 'image' ? (
418
- <h2>
419
- <FormattedMessage
420
- id="Choose Image"
421
- defaultMessage="Choose Image"
422
- />
423
- </h2>
424
434
  ) : (
425
- <h2>
426
- <FormattedMessage
427
- id="Choose Target"
428
- defaultMessage="Choose Target"
429
- />
430
- </h2>
435
+ <>
436
+ {this.state.currentFolder === '/' ? (
437
+ <>
438
+ {this.props.mode === 'image' ? (
439
+ <Icon name={folderSVG} size="24px" />
440
+ ) : (
441
+ <Icon name={linkSVG} size="24px" />
442
+ )}
443
+ </>
444
+ ) : (
445
+ <button
446
+ aria-label={this.props.intl.formatMessage(messages.back)}
447
+ onClick={() => this.navigateTo(this.state.parentFolder)}
448
+ >
449
+ <Icon name={backSVG} size="24px" />
450
+ </button>
451
+ )}
452
+ {this.props.mode === 'image' ? (
453
+ <h2>
454
+ <FormattedMessage
455
+ id="Choose Image"
456
+ defaultMessage="Choose Image"
457
+ />
458
+ </h2>
459
+ ) : (
460
+ <h2>
461
+ <FormattedMessage
462
+ id="Choose Target"
463
+ defaultMessage="Choose Target"
464
+ />
465
+ </h2>
466
+ )}
467
+ </>
431
468
  )}
432
469
 
433
470
  <button
@@ -441,40 +478,94 @@ class ObjectBrowserBody extends Component {
441
478
  </button>
442
479
  </header>
443
480
  <Segment secondary className="breadcrumbs" vertical>
444
- <Breadcrumb>
445
- {this.state.currentFolder !== '/' ? (
446
- this.state.currentFolder.split('/').map((item, index, items) => {
447
- return (
448
- <React.Fragment key={`divider-${item}-${index}`}>
449
- {index === 0 ? (
450
- <Breadcrumb.Section onClick={() => this.navigateTo('/')}>
451
- <Icon
452
- className="home-icon"
453
- name={homeSVG}
454
- size="18px"
455
- />
456
- </Breadcrumb.Section>
457
- ) : (
458
- <>
459
- <Breadcrumb.Divider key={`divider-${item.url}`} />
460
- <Breadcrumb.Section
461
- onClick={() =>
462
- this.navigateTo(items.slice(0, index + 1).join('/'))
463
- }
464
- >
465
- {item}
466
- </Breadcrumb.Section>
467
- </>
468
- )}
469
- </React.Fragment>
470
- );
471
- })
472
- ) : (
473
- <Breadcrumb.Section onClick={() => this.navigateTo('/')}>
474
- <Icon className="home-icon" name={homeSVG} size="18px" />
475
- </Breadcrumb.Section>
476
- )}
477
- </Breadcrumb>
481
+ {this.props.mode === 'image' && (
482
+ <button
483
+ onClick={this.toggleView}
484
+ className="mode-switch"
485
+ aria-label={this.props.intl.formatMessage(
486
+ this.state.view === 'list'
487
+ ? messages.iconView
488
+ : messages.listView,
489
+ )}
490
+ >
491
+ <Icon
492
+ name={this.state.view === 'list' ? iconsSVG : listSVG}
493
+ size="24px"
494
+ className="mode-switch"
495
+ title={this.props.intl.formatMessage(
496
+ this.state.view === 'list'
497
+ ? messages.iconView
498
+ : messages.listView,
499
+ )}
500
+ />
501
+ </button>
502
+ )}
503
+ {!this.state.showSearchInput ? (
504
+ <Breadcrumb>
505
+ {this.state.currentFolder !== '/' ? (
506
+ this.state.currentFolder
507
+ .split('/')
508
+ .map((item, index, items) => {
509
+ return (
510
+ <React.Fragment key={`divider-${item}-${index}`}>
511
+ {index === 0 ? (
512
+ <Breadcrumb.Section
513
+ onClick={() => this.navigateTo('/')}
514
+ role="button"
515
+ aria-label={this.props.intl.formatMessage(
516
+ messages.home,
517
+ )}
518
+ >
519
+ <Icon
520
+ className="home-icon"
521
+ name={homeSVG}
522
+ size="18px"
523
+ title={this.props.intl.formatMessage(
524
+ messages.home,
525
+ )}
526
+ />
527
+ </Breadcrumb.Section>
528
+ ) : (
529
+ <>
530
+ <Breadcrumb.Divider key={`divider-${item.url}`} />
531
+ <Breadcrumb.Section
532
+ role="button"
533
+ onClick={() =>
534
+ this.navigateTo(
535
+ items.slice(0, index + 1).join('/'),
536
+ )
537
+ }
538
+ >
539
+ {item}
540
+ </Breadcrumb.Section>
541
+ </>
542
+ )}
543
+ </React.Fragment>
544
+ );
545
+ })
546
+ ) : (
547
+ <Breadcrumb.Section
548
+ onClick={() => this.navigateTo('/')}
549
+ aria-label={this.props.intl.formatMessage(messages.home)}
550
+ >
551
+ <Icon
552
+ className="home-icon"
553
+ name={homeSVG}
554
+ role="button"
555
+ size="18px"
556
+ title={this.props.intl.formatMessage(messages.home)}
557
+ />
558
+ </Breadcrumb.Section>
559
+ )}
560
+ </Breadcrumb>
561
+ ) : (
562
+ <div className="searchResults">
563
+ <FormattedMessage
564
+ id="Search results"
565
+ defaultMessage="Search results"
566
+ />
567
+ </div>
568
+ )}
478
569
  </Segment>
479
570
  {this.props.mode === 'multiple' && (
480
571
  <Segment className="infos">
@@ -510,6 +601,7 @@ class ObjectBrowserBody extends Component {
510
601
  handleClickOnItem={this.handleClickOnItem}
511
602
  handleDoubleClickOnItem={this.handleDoubleClickOnItem}
512
603
  mode={this.props.mode}
604
+ view={this.state.view}
513
605
  navigateTo={this.navigateTo}
514
606
  isSelectable={this.isSelectable}
515
607
  />
@@ -523,6 +615,7 @@ export default compose(
523
615
  connect(
524
616
  (state) => ({
525
617
  searchSubrequests: state.search.subrequests,
618
+ lang: state.intl.locale,
526
619
  }),
527
620
  { searchContent },
528
621
  ),
@@ -26,6 +26,7 @@ const ObjectBrowserNav = ({
26
26
  handleClickOnItem,
27
27
  handleDoubleClickOnItem,
28
28
  mode,
29
+ view,
29
30
  navigateTo,
30
31
  isSelectable,
31
32
  }) => {
@@ -47,83 +48,136 @@ const ObjectBrowserNav = ({
47
48
  return (
48
49
  <Segment as="ul" className="object-listing">
49
50
  {currentSearchResults &&
50
- currentSearchResults.items.map((item) => (
51
- <li
52
- role="presentation"
53
- aria-label={
54
- item.is_folderish && mode === 'image'
55
- ? `${intl.formatMessage(messages.browse)} ${item.title}`
56
- : `${intl.formatMessage(messages.select)} ${item.title}`
57
- }
58
- key={item['@id']}
59
- className={cx('', {
60
- 'selected-item': isSelected(item),
61
-
62
- disabled:
63
- mode === 'image'
64
- ? !config.settings.imageObjects.includes(item['@type']) &&
65
- !item.is_folderish
66
- : !isSelectable(item),
67
- })}
68
- onClick={() => handleClickOnItem(item)}
69
- onDoubleClick={() => handleDoubleClickOnItem(item)}
70
- >
71
- <span title={`${item['@id']} (${item['@type']})`}>
72
- <Popup
73
- key={item['@id']}
74
- content={
75
- <>
76
- <Icon name={homeSVG} size="18px" />{' '}
77
- {flattenToAppURL(item['@id'])} ( {item['@type']})
78
- </>
79
- }
80
- trigger={
81
- <span>
82
- <Icon
83
- name={getContentIcon(item['@type'], item.is_folderish)}
84
- size="24px"
85
- />
86
- </span>
87
- }
88
- />
89
-
90
- {item.title}
91
- </span>
92
- {item.is_folderish && mode === 'image' && (
93
- <Icon
94
- className="right-arrow-icon"
95
- name={rightArrowSVG}
96
- size="24px"
97
- />
98
- )}
99
- {item.is_folderish && (mode === 'link' || mode === 'multiple') && (
51
+ currentSearchResults.items.map((item) =>
52
+ view === 'icons' ? (
53
+ <li
54
+ className="image-wrapper"
55
+ title={`${item['@id']} (${item['@type']})`}
56
+ >
100
57
  <div
58
+ basic
101
59
  role="presentation"
102
- className="right-arrow-link-mode"
103
- onClick={(e) => {
104
- e.stopPropagation();
105
- navigateTo(item['@id']);
106
- }}
60
+ onClick={(e) => handleClickOnItem(item)}
61
+ onDoubleClick={() => handleDoubleClickOnItem(item)}
62
+ className="image-preview"
63
+ aria-label={`${intl.formatMessage(messages.select)} ${
64
+ item.title
65
+ }`}
107
66
  >
108
- <Button.Group>
109
- <Button
110
- basic
111
- icon
112
- aria-label={`${intl.formatMessage(messages.browse)} ${
113
- item.title
114
- }`}
115
- >
67
+ {item['@type'] === 'Image' ? (
68
+ <img
69
+ src={`${item['@id']}/@@images/image/preview`}
70
+ alt={item.title}
71
+ style={{
72
+ width: 143,
73
+ height: 143,
74
+ }}
75
+ className={isSelected(item) ? 'selected' : ''}
76
+ />
77
+ ) : (
78
+ <div className="icon-wrapper">
116
79
  <Icon
117
- className="right-arrow-icon"
118
- name={rightArrowSVG}
119
- size="24px"
80
+ name={getContentIcon(item['@type'], item.is_folderish)}
81
+ size="45px"
120
82
  />
121
- </Button>
122
- </Button.Group>
83
+ </div>
84
+ )}
85
+ </div>
86
+ <div className="image-title">
87
+ <div
88
+ className="icon-align-name"
89
+ onClick={(e) => handleClickOnItem(item)}
90
+ aria-hidden="true"
91
+ >
92
+ <div
93
+ title={item.title}
94
+ style={{ width: 143 }}
95
+ className="image-title-content"
96
+ >
97
+ {item.title}
98
+ </div>
99
+ </div>
123
100
  </div>
124
- )}
125
- </li>
126
- ))}
101
+ </li>
102
+ ) : (
103
+ <li
104
+ role="presentation"
105
+ aria-label={
106
+ item.is_folderish && mode === 'image'
107
+ ? `${intl.formatMessage(messages.browse)} ${item.title}`
108
+ : `${intl.formatMessage(messages.select)} ${item.title}`
109
+ }
110
+ key={item['@id']}
111
+ className={cx('', {
112
+ 'selected-item': isSelected(item),
113
+
114
+ disabled:
115
+ mode === 'image'
116
+ ? !config.settings.imageObjects.includes(item['@type']) &&
117
+ !item.is_folderish
118
+ : !isSelectable(item),
119
+ })}
120
+ onClick={() => handleClickOnItem(item)}
121
+ onDoubleClick={() => handleDoubleClickOnItem(item)}
122
+ >
123
+ <span title={`${item['@id']} (${item['@type']})`}>
124
+ <Popup
125
+ key={item['@id']}
126
+ content={
127
+ <>
128
+ <Icon name={homeSVG} size="18px" />{' '}
129
+ {flattenToAppURL(item['@id'])} ( {item['@type']})
130
+ </>
131
+ }
132
+ trigger={
133
+ <span>
134
+ <Icon
135
+ name={getContentIcon(item['@type'], item.is_folderish)}
136
+ size="24px"
137
+ />
138
+ </span>
139
+ }
140
+ />
141
+
142
+ {item.title}
143
+ </span>
144
+ {item.is_folderish && mode === 'image' && (
145
+ <Icon
146
+ className="right-arrow-icon"
147
+ name={rightArrowSVG}
148
+ size="24px"
149
+ />
150
+ )}
151
+ {item.is_folderish &&
152
+ (mode === 'link' || mode === 'multiple') && (
153
+ <div
154
+ role="presentation"
155
+ className="right-arrow-link-mode"
156
+ onClick={(e) => {
157
+ e.stopPropagation();
158
+ navigateTo(item['@id']);
159
+ }}
160
+ >
161
+ <Button.Group>
162
+ <Button
163
+ basic
164
+ icon
165
+ aria-label={`${intl.formatMessage(messages.browse)} ${
166
+ item.title
167
+ }`}
168
+ >
169
+ <Icon
170
+ className="right-arrow-icon"
171
+ name={rightArrowSVG}
172
+ size="24px"
173
+ />
174
+ </Button>
175
+ </Button.Group>
176
+ </div>
177
+ )}
178
+ </li>
179
+ ),
180
+ )}
127
181
  </Segment>
128
182
  );
129
183
  };