@plone/volto 17.18.2 → 17.19.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 (44) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +20 -0
  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/fi/LC_MESSAGES/volto.po +5 -0
  14. package/locales/fi.json +1 -1
  15. package/locales/fr/LC_MESSAGES/volto.po +5 -0
  16. package/locales/fr.json +1 -1
  17. package/locales/it/LC_MESSAGES/volto.po +5 -0
  18. package/locales/it.json +1 -1
  19. package/locales/ja/LC_MESSAGES/volto.po +5 -0
  20. package/locales/ja.json +1 -1
  21. package/locales/nl/LC_MESSAGES/volto.po +5 -0
  22. package/locales/nl.json +1 -1
  23. package/locales/pt/LC_MESSAGES/volto.po +5 -0
  24. package/locales/pt.json +1 -1
  25. package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
  26. package/locales/pt_BR.json +1 -1
  27. package/locales/ro/LC_MESSAGES/volto.po +5 -0
  28. package/locales/ro.json +1 -1
  29. package/locales/volto.pot +6 -1
  30. package/locales/zh_CN/LC_MESSAGES/volto.po +5 -0
  31. package/locales/zh_CN.json +1 -1
  32. package/package.json +1 -1
  33. package/packages/volto-slate/package.json +1 -1
  34. package/src/components/manage/Add/Add.jsx +1 -0
  35. package/src/components/manage/Blocks/Block/BlocksForm.jsx +2 -0
  36. package/src/components/manage/Blocks/Search/components/SortOn.jsx +82 -55
  37. package/src/components/manage/Contents/Contents.jsx +3 -0
  38. package/src/components/manage/Contents/ContentsPropertiesModal.jsx +85 -52
  39. package/src/components/manage/Form/Form.jsx +1 -0
  40. package/src/components/manage/Toolbar/Toolbar.jsx +1 -1
  41. package/src/components/manage/Widgets/InternalUrlWidget.jsx +13 -17
  42. package/src/config/index.js +1 -0
  43. package/theme/themes/pastanaga/extras/blocks.less +6 -0
  44. package/theme/themes/pastanaga/extras/toolbar.less +10 -3
@@ -1,12 +1,14 @@
1
- import React, { useEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useDispatch, useSelector } from 'react-redux';
4
4
  import { isEmpty, map } from 'lodash';
5
5
  import { defineMessages, useIntl } from 'react-intl';
6
6
 
7
7
  import { usePrevious } from '@plone/volto/helpers';
8
+ import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils';
8
9
  import { updateContent } from '@plone/volto/actions';
9
10
  import { ModalForm } from '@plone/volto/components';
11
+ import config from '@plone/volto/registry';
10
12
 
11
13
  const messages = defineMessages({
12
14
  properties: {
@@ -65,12 +67,83 @@ const messages = defineMessages({
65
67
  });
66
68
 
67
69
  const ContentsPropertiesModal = (props) => {
68
- const { onCancel, onOk, open, items } = props;
70
+ const { onCancel, onOk, open, items, values } = props;
69
71
  const intl = useIntl();
70
72
  const dispatch = useDispatch();
71
73
  const request = useSelector((state) => state.content.update);
72
74
  const prevrequestloading = usePrevious(request.loading);
73
75
 
76
+ const baseSchema = {
77
+ fieldsets: [
78
+ {
79
+ id: 'default',
80
+ title: intl.formatMessage(messages.default),
81
+ fields: [
82
+ 'effective',
83
+ 'expires',
84
+ 'rights',
85
+ 'creators',
86
+ 'exclude_from_nav',
87
+ ],
88
+ },
89
+ ],
90
+ properties: {
91
+ effective: {
92
+ description: intl.formatMessage(messages.effectiveDescription),
93
+ title: intl.formatMessage(messages.effectiveTitle),
94
+ type: 'string',
95
+ widget: 'datetime',
96
+ },
97
+ expires: {
98
+ description: intl.formatMessage(messages.expiresDescription),
99
+ title: intl.formatMessage(messages.expiresTitle),
100
+ type: 'string',
101
+ widget: 'datetime',
102
+ },
103
+ rights: {
104
+ description: intl.formatMessage(messages.rightsDescription),
105
+ title: intl.formatMessage(messages.rightsTitle),
106
+ type: 'string',
107
+ widget: 'textarea',
108
+ },
109
+ creators: {
110
+ description: intl.formatMessage(messages.creatorsDescription),
111
+ title: intl.formatMessage(messages.creatorsTitle),
112
+ type: 'array',
113
+ },
114
+ exclude_from_nav: {
115
+ description: intl.formatMessage(messages.excludeFromNavDescription),
116
+ title: intl.formatMessage(messages.excludeFromNavTitle),
117
+ type: 'boolean',
118
+ },
119
+ },
120
+ required: [],
121
+ };
122
+ const schemaEnhancer = config.settings.contentPropertiesSchemaEnhancer;
123
+ let schema = schemaEnhancer
124
+ ? schemaEnhancer({
125
+ schema: cloneDeepSchema(baseSchema),
126
+ intl,
127
+ })
128
+ : baseSchema;
129
+
130
+ const initialData = {};
131
+ if (values?.length) {
132
+ for (const name of Object.keys(schema.properties)) {
133
+ const firstValue = values[0][name];
134
+ // should not show floor or ceiling dates
135
+ if (
136
+ (name === 'effective' && firstValue && firstValue <= '1970') ||
137
+ (name === 'expires' && firstValue && firstValue >= '2499')
138
+ ) {
139
+ continue;
140
+ }
141
+ if (values.every((item) => item[name] === firstValue)) {
142
+ initialData[name] = firstValue;
143
+ }
144
+ }
145
+ }
146
+
74
147
  useEffect(() => {
75
148
  if (prevrequestloading && request.loaded) {
76
149
  onOk();
@@ -78,13 +151,19 @@ const ContentsPropertiesModal = (props) => {
78
151
  }, [onOk, prevrequestloading, request.loaded]);
79
152
 
80
153
  const onSubmit = (data) => {
81
- if (isEmpty(data)) {
154
+ let changes = {};
155
+ for (const name of Object.keys(data)) {
156
+ if (data[name] !== initialData[name]) {
157
+ changes[name] = data[name];
158
+ }
159
+ }
160
+ if (isEmpty(changes)) {
82
161
  onOk();
83
162
  } else {
84
163
  dispatch(
85
164
  updateContent(
86
165
  items,
87
- map(items, () => data),
166
+ map(items, () => changes),
88
167
  ),
89
168
  );
90
169
  }
@@ -97,54 +176,8 @@ const ContentsPropertiesModal = (props) => {
97
176
  onSubmit={onSubmit}
98
177
  onCancel={onCancel}
99
178
  title={intl.formatMessage(messages.properties)}
100
- schema={{
101
- fieldsets: [
102
- {
103
- id: 'default',
104
- title: intl.formatMessage(messages.default),
105
- fields: [
106
- 'effective',
107
- 'expires',
108
- 'rights',
109
- 'creators',
110
- 'exclude_from_nav',
111
- ],
112
- },
113
- ],
114
- properties: {
115
- effective: {
116
- description: intl.formatMessage(messages.effectiveDescription),
117
- title: intl.formatMessage(messages.effectiveTitle),
118
- type: 'string',
119
- widget: 'datetime',
120
- },
121
- expires: {
122
- description: intl.formatMessage(messages.expiresDescription),
123
- title: intl.formatMessage(messages.expiresTitle),
124
- type: 'string',
125
- widget: 'datetime',
126
- },
127
- rights: {
128
- description: intl.formatMessage(messages.rightsDescription),
129
- title: intl.formatMessage(messages.rightsTitle),
130
- type: 'string',
131
- widget: 'textarea',
132
- },
133
- creators: {
134
- description: intl.formatMessage(messages.creatorsDescription),
135
- title: intl.formatMessage(messages.creatorsTitle),
136
- type: 'array',
137
- },
138
- exclude_from_nav: {
139
- description: intl.formatMessage(
140
- messages.excludeFromNavDescription,
141
- ),
142
- title: intl.formatMessage(messages.excludeFromNavTitle),
143
- type: 'boolean',
144
- },
145
- },
146
- required: [],
147
- }}
179
+ schema={schema}
180
+ formData={initialData}
148
181
  />
149
182
  )
150
183
  );
@@ -695,6 +695,7 @@ class Form extends Component {
695
695
  properties={formData}
696
696
  navRoot={navRoot}
697
697
  type={type}
698
+ errors={this.state.errors}
698
699
  pathname={this.props.pathname}
699
700
  selectedBlock={this.state.selected}
700
701
  multiSelected={this.state.multiSelected}
@@ -590,7 +590,7 @@ class Toolbar extends Component {
590
590
  aria-label={this.props.intl.formatMessage(
591
591
  messages.shrinkToolbar,
592
592
  )}
593
- className={cx({
593
+ className={cx('toolbar-handler-button', {
594
594
  [this.props.content?.review_state]:
595
595
  this.props.content?.review_state,
596
596
  })}
@@ -4,10 +4,10 @@
4
4
  */
5
5
 
6
6
  import React, { useState } from 'react';
7
- import { compose } from 'redux';
8
7
  import PropTypes from 'prop-types';
9
8
  import { Input, Button } from 'semantic-ui-react';
10
- import { FormFieldWrapper, Icon } from '@plone/volto/components';
9
+ import { Icon } from '@plone/volto/components';
10
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
11
11
  import { isInternalURL, flattenToAppURL, URLUtils } from '@plone/volto/helpers';
12
12
  import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
13
13
  import clearSVG from '@plone/volto/icons/clear.svg';
@@ -15,13 +15,13 @@ import navTreeSVG from '@plone/volto/icons/nav.svg';
15
15
 
16
16
  /** Widget to edit urls
17
17
  *
18
- * This is the default widget used for the `remoteUrl` field. You can also use
18
+ * This is a widget used for getting and setting an internal url. You can also use
19
19
  * it by declaring a field like:
20
20
  *
21
21
  * ```jsx
22
22
  * {
23
23
  * title: "URL",
24
- * widget: 'url',
24
+ * widget: 'internal_url',
25
25
  * }
26
26
  * ```
27
27
  */
@@ -35,20 +35,20 @@ export const InternalUrlWidget = (props) => {
35
35
  maxLength,
36
36
  placeholder,
37
37
  isDisabled,
38
+ value,
38
39
  } = props;
39
40
  const inputId = `field-${id}`;
40
41
 
41
- const [value, setValue] = useState(flattenToAppURL(props.value));
42
42
  const [isInvalid, setIsInvalid] = useState(false);
43
+
43
44
  /**
44
45
  * Clear handler
45
46
  * @method clear
46
47
  * @param {Object} value Value
47
- * @returns {undefined}
48
+ * @returns {string} Empty string
48
49
  */
49
50
  const clear = () => {
50
- setValue('');
51
- onChange(id, undefined);
51
+ onChange(id, '');
52
52
  };
53
53
 
54
54
  const onChangeValue = (_value) => {
@@ -63,8 +63,6 @@ export const InternalUrlWidget = (props) => {
63
63
  }
64
64
  }
65
65
 
66
- setValue(newValue);
67
-
68
66
  newValue = isInternalURL(newValue) ? flattenToAppURL(newValue) : newValue;
69
67
 
70
68
  if (!isInternalURL(newValue) && newValue.length > 0) {
@@ -75,7 +73,7 @@ export const InternalUrlWidget = (props) => {
75
73
  }
76
74
  }
77
75
 
78
- onChange(id, newValue === '' ? undefined : newValue);
76
+ onChange(id, newValue);
79
77
  };
80
78
 
81
79
  return (
@@ -89,12 +87,10 @@ export const InternalUrlWidget = (props) => {
89
87
  disabled={isDisabled}
90
88
  placeholder={placeholder}
91
89
  onChange={({ target }) => onChangeValue(target.value)}
92
- onBlur={({ target }) =>
93
- onBlur(id, target.value === '' ? undefined : target.value)
94
- }
90
+ onBlur={({ target }) => onBlur(id, target.value)}
95
91
  onClick={() => onClick()}
96
- minLength={minLength || null}
97
- maxLength={maxLength || null}
92
+ minLength={minLength}
93
+ maxLength={maxLength}
98
94
  error={isInvalid}
99
95
  />
100
96
  {value?.length > 0 ? (
@@ -177,4 +173,4 @@ InternalUrlWidget.defaultProps = {
177
173
  maxLength: null,
178
174
  };
179
175
 
180
- export default compose(withObjectBrowser)(InternalUrlWidget);
176
+ export default withObjectBrowser(InternalUrlWidget);
@@ -185,6 +185,7 @@ let config = {
185
185
  ],
186
186
  showSelfRegistration: false,
187
187
  contentMetadataTagsImageField: 'image',
188
+ contentPropertiesSchemaEnhancer: null,
188
189
  hasWorkingCopySupport: false,
189
190
  maxUndoLevels: 200, // undo history size for the main form
190
191
  addonsInfo: addonsInfo,
@@ -1119,6 +1119,12 @@ div.image-upload-widget-image {
1119
1119
  flex-direction: row;
1120
1120
  align-items: center;
1121
1121
  margin-right: 0.5em;
1122
+
1123
+ .sorted-label-value {
1124
+ margin-right: 0.5em;
1125
+ margin-left: 0.7em;
1126
+ color: @lightGrey;
1127
+ }
1122
1128
  }
1123
1129
 
1124
1130
  .sort-label {
@@ -129,7 +129,7 @@ body:not(.has-sidebar):not(.has-sidebar-collapsed) {
129
129
  }
130
130
 
131
131
  .toolbar-handler {
132
- button {
132
+ .toolbar-handler-button {
133
133
  opacity: 0.3;
134
134
  }
135
135
 
@@ -243,7 +243,7 @@ body:not(.has-sidebar):not(.has-sidebar-collapsed) {
243
243
  width: 100%;
244
244
  justify-content: center;
245
245
 
246
- button {
246
+ .toolbar-handler-button {
247
247
  width: @toolbarWidth;
248
248
  height: 20px;
249
249
  padding: 0;
@@ -428,7 +428,7 @@ body:not(.has-sidebar):not(.has-sidebar-collapsed) {
428
428
  flex-direction: column;
429
429
  justify-content: center;
430
430
 
431
- button {
431
+ .toolbar-handler-button {
432
432
  width: @toolbarWidthMin;
433
433
  height: @toolbarWidth;
434
434
 
@@ -752,3 +752,10 @@ body:not(.has-sidebar):not(.has-sidebar-collapsed) {
752
752
  }
753
753
  }
754
754
  // End Orphaned CSS
755
+
756
+ // Toolbar handler color in homepage
757
+ .contenttype-plone-site {
758
+ #toolbar .toolbar-handler .toolbar-handler-button:before {
759
+ background: @teal-blue;
760
+ }
761
+ }