@plone/volto 17.18.2 → 17.20.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 (52) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/CHANGELOG.md +31 -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/packages/volto-slate/src/blocks/Text/SlashMenu.jsx +15 -1
  35. package/src/components/manage/Add/Add.jsx +1 -0
  36. package/src/components/manage/BlockChooser/BlockChooser.jsx +9 -1
  37. package/src/components/manage/BlockChooser/BlockChooser.test.jsx +4 -0
  38. package/src/components/manage/Blocks/Block/BlocksForm.jsx +2 -0
  39. package/src/components/manage/Blocks/Search/components/SortOn.jsx +82 -55
  40. package/src/components/manage/Contents/Contents.jsx +3 -0
  41. package/src/components/manage/Contents/ContentsPropertiesModal.jsx +85 -52
  42. package/src/components/manage/Diff/DiffField.jsx +167 -39
  43. package/src/components/manage/Form/Form.jsx +1 -0
  44. package/src/components/manage/Toolbar/Toolbar.jsx +1 -1
  45. package/src/components/manage/Widgets/InternalUrlWidget.jsx +13 -17
  46. package/src/config/index.js +1 -0
  47. package/src/hooks/index.js +1 -0
  48. package/src/hooks/user/useUser.js +23 -0
  49. package/theme/themes/pastanaga/extras/blocks.less +6 -0
  50. package/theme/themes/pastanaga/extras/toolbar.less +10 -3
  51. package/types/hooks/index.d.ts +1 -0
  52. package/types/hooks/user/useUser.d.ts +2 -0
@@ -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
  );
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import React from 'react';
7
- // import { diffWords as dWords } from 'diff';
8
7
  import { join, map } from 'lodash';
9
8
  import PropTypes from 'prop-types';
10
9
  import { Grid } from 'semantic-ui-react';
@@ -13,20 +12,128 @@ import { Provider } from 'react-intl-redux';
13
12
  import { createBrowserHistory } from 'history';
14
13
  import { ConnectedRouter } from 'connected-react-router';
15
14
  import { useSelector } from 'react-redux';
16
-
15
+ import config from '@plone/volto/registry';
17
16
  import { Api } from '@plone/volto/helpers';
18
17
  import configureStore from '@plone/volto/store';
19
- import { DefaultView } from '@plone/volto/components/';
18
+ import { RenderBlocks } from '@plone/volto/components';
20
19
  import { serializeNodes } from '@plone/volto-slate/editor/render';
21
-
22
20
  import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
23
21
 
24
- /**
25
- * Enhanced diff words utility
26
- * @function diffWords
27
- * @param oneStr Field one
28
- * @param twoStr Field two
29
- */
22
+ const isHtmlTag = (str) => {
23
+ // Match complete HTML tags, including:
24
+ // 1. Opening tags like <div>, <img src="example" />, <svg>...</svg>
25
+ // 2. Self-closing tags like <img />, <br />
26
+ // 3. Closing tags like </div>
27
+ return /^<([a-zA-Z]+[0-9]*)\b[^>]*>|^<\/([a-zA-Z]+[0-9]*)\b[^>]*>$|^<([a-zA-Z]+[0-9]*)\b[^>]*\/>$/.test(
28
+ str,
29
+ );
30
+ };
31
+
32
+ const splitWords = (str) => {
33
+ if (typeof str !== 'string') return str;
34
+ if (!str) return [];
35
+
36
+ const result = [];
37
+ let currentWord = '';
38
+ let insideTag = false;
39
+ let insideSpecialTag = false;
40
+ let tagBuffer = '';
41
+
42
+ // Special tags that should not be split (e.g., <img />, <svg> ... </svg>)
43
+ const specialTags = ['img', 'svg'];
44
+
45
+ for (let i = 0; i < str.length; i++) {
46
+ const char = str[i];
47
+
48
+ // Start of an HTML tag
49
+ if (char === '<') {
50
+ if (currentWord) {
51
+ result.push(currentWord); // Push text before the tag
52
+ currentWord = '';
53
+ }
54
+ insideTag = true;
55
+ tagBuffer += char;
56
+ }
57
+ // End of an HTML tag
58
+ else if (char === '>') {
59
+ tagBuffer += char;
60
+ insideTag = false;
61
+
62
+ // Check if the tagBuffer contains a special tag
63
+ const tagNameMatch = tagBuffer.match(/^<\/?([a-zA-Z]+[0-9]*)\b/);
64
+ if (tagNameMatch && specialTags.includes(tagNameMatch[1])) {
65
+ insideSpecialTag =
66
+ tagNameMatch[0].startsWith('<') && !tagNameMatch[0].startsWith('</');
67
+ result.push(tagBuffer); // Push the complete special tag as one unit
68
+ tagBuffer = '';
69
+ continue;
70
+ }
71
+
72
+ result.push(tagBuffer); // Push the complete tag
73
+ tagBuffer = '';
74
+ }
75
+ // Inside the tag or special tag
76
+ else if (insideTag || insideSpecialTag) {
77
+ tagBuffer += char;
78
+ }
79
+ // Space outside of tags - push current word
80
+ else if (char === ' ' && !insideTag && !insideSpecialTag) {
81
+ if (currentWord) {
82
+ result.push(currentWord);
83
+ currentWord = '';
84
+ }
85
+ result.push(' ');
86
+ } else if (
87
+ char === ',' &&
88
+ i < str.length - 1 &&
89
+ str[i + 1] !== ' ' &&
90
+ !insideTag &&
91
+ !insideSpecialTag
92
+ ) {
93
+ if (currentWord) {
94
+ result.push(currentWord + char);
95
+ currentWord = '';
96
+ }
97
+ result.push(' ');
98
+ }
99
+ // Accumulate characters outside of tags
100
+ else {
101
+ currentWord += char;
102
+ }
103
+ }
104
+
105
+ // Push any remaining text
106
+ if (currentWord) {
107
+ result.push(currentWord);
108
+ }
109
+ if (tagBuffer) {
110
+ result.push(tagBuffer); // Push remaining tagBuffer
111
+ }
112
+
113
+ return result;
114
+ };
115
+
116
+ const formatDiffPart = (part, value, side) => {
117
+ if (!isHtmlTag(value)) {
118
+ if (part.removed && (side === 'left' || side === 'unified')) {
119
+ return `<span class="deletion">${value}</span>`;
120
+ } else if (part.removed) return '';
121
+ else if (part.added && (side === 'right' || side === 'unified')) {
122
+ return `<span class="addition">${value}</span>`;
123
+ } else if (part.added) return '';
124
+ return value;
125
+ } else {
126
+ if (side === 'unified' && part.added) return value;
127
+ else if (side === 'unified' && part.removed) return '';
128
+ if (part.removed && side === 'left') {
129
+ return value;
130
+ } else if (part.removed) return '';
131
+ else if (part.added && side === 'right') {
132
+ return value;
133
+ } else if (part.added) return '';
134
+ return value;
135
+ }
136
+ };
30
137
 
31
138
  /**
32
139
  * Diff field component.
@@ -36,6 +143,7 @@ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
36
143
  * @param {Object} schema Field schema
37
144
  * @returns {string} Markup of the component.
38
145
  */
146
+
39
147
  const DiffField = ({
40
148
  one,
41
149
  two,
@@ -51,7 +159,10 @@ const DiffField = ({
51
159
  timeStyle: 'short',
52
160
  };
53
161
  const diffWords = (oneStr, twoStr) => {
54
- return diffLib.diffWords(String(oneStr), String(twoStr));
162
+ return diffLib.diffArrays(
163
+ splitWords(String(oneStr)),
164
+ splitWords(String(twoStr)),
165
+ );
55
166
  };
56
167
 
57
168
  let parts, oneArray, twoArray;
@@ -78,14 +189,14 @@ const DiffField = ({
78
189
  ReactDOMServer.renderToStaticMarkup(
79
190
  <Provider store={store}>
80
191
  <ConnectedRouter history={history}>
81
- <DefaultView content={contentOne} />
192
+ <RenderBlocks content={contentOne} />
82
193
  </ConnectedRouter>
83
194
  </Provider>,
84
195
  ),
85
196
  ReactDOMServer.renderToStaticMarkup(
86
197
  <Provider store={store}>
87
198
  <ConnectedRouter history={history}>
88
- <DefaultView content={contentTwo} />
199
+ <RenderBlocks content={contentTwo} />
89
200
  </ConnectedRouter>
90
201
  </Provider>,
91
202
  ),
@@ -116,7 +227,30 @@ const DiffField = ({
116
227
  }
117
228
  case 'textarea':
118
229
  default:
119
- parts = diffWords(one, two);
230
+ const Widget = config.widgets?.views?.widget?.[schema.widget];
231
+
232
+ if (Widget) {
233
+ const api = new Api();
234
+ const history = createBrowserHistory();
235
+ const store = configureStore(window.__data, history, api);
236
+ parts = diffWords(
237
+ ReactDOMServer.renderToStaticMarkup(
238
+ <Provider store={store}>
239
+ <ConnectedRouter history={history}>
240
+ <Widget value={one} />
241
+ </ConnectedRouter>
242
+ </Provider>,
243
+ ),
244
+ ReactDOMServer.renderToStaticMarkup(
245
+ <Provider store={store}>
246
+ <ConnectedRouter history={history}>
247
+ <Widget value={two} />
248
+ </ConnectedRouter>
249
+ </Provider>,
250
+ ),
251
+ );
252
+ } else parts = diffWords(one, two);
253
+
120
254
  break;
121
255
  }
122
256
  } else if (schema.type === 'object') {
@@ -128,6 +262,7 @@ const DiffField = ({
128
262
  } else {
129
263
  parts = diffWords(one?.title || one, two?.title || two);
130
264
  }
265
+
131
266
  return (
132
267
  <Grid data-testid="DiffField">
133
268
  <Grid.Row>
@@ -140,14 +275,12 @@ const DiffField = ({
140
275
  <span
141
276
  dangerouslySetInnerHTML={{
142
277
  __html: join(
143
- map(
144
- parts,
145
- (part) =>
146
- (part.removed &&
147
- `<span class="deletion">${part.value}</span>`) ||
148
- (!part.added && `<span>${part.value}</span>`) ||
149
- '',
150
- ),
278
+ map(parts, (part) => {
279
+ let combined = (part.value || []).reduce((acc, value) => {
280
+ return acc + formatDiffPart(part, value, 'left');
281
+ }, '');
282
+ return combined;
283
+ }),
151
284
  '',
152
285
  ),
153
286
  }}
@@ -157,14 +290,12 @@ const DiffField = ({
157
290
  <span
158
291
  dangerouslySetInnerHTML={{
159
292
  __html: join(
160
- map(
161
- parts,
162
- (part) =>
163
- (part.added &&
164
- `<span class="addition">${part.value}</span>`) ||
165
- (!part.removed && `<span>${part.value}</span>`) ||
166
- '',
167
- ),
293
+ map(parts, (part) => {
294
+ let combined = (part.value || []).reduce((acc, value) => {
295
+ return acc + formatDiffPart(part, value, 'right');
296
+ }, '');
297
+ return combined;
298
+ }),
168
299
  '',
169
300
  ),
170
301
  }}
@@ -178,15 +309,12 @@ const DiffField = ({
178
309
  <span
179
310
  dangerouslySetInnerHTML={{
180
311
  __html: join(
181
- map(
182
- parts,
183
- (part) =>
184
- (part.removed &&
185
- `<span class="deletion">${part.value}</span>`) ||
186
- (part.added &&
187
- `<span class="addition">${part.value}</span>`) ||
188
- (!part.added && `<span>${part.value}</span>`),
189
- ),
312
+ map(parts, (part) => {
313
+ let combined = (part.value || []).reduce((acc, value) => {
314
+ return acc + formatDiffPart(part, value, 'unified');
315
+ }, '');
316
+ return combined;
317
+ }),
190
318
  '',
191
319
  ),
192
320
  }}
@@ -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,
@@ -1,2 +1,3 @@
1
1
  export { default as useClipboard } from '@plone/volto/hooks/clipboard/useClipboard';
2
2
  export { useClient } from '@plone/volto/hooks/client/useClient';
3
+ export { default as useUser } from '@plone/volto/hooks/user/useUser';
@@ -0,0 +1,23 @@
1
+ import { useEffect } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import jwtDecode from 'jwt-decode';
4
+ import { getUser } from '@plone/volto/actions';
5
+
6
+ const useUser = () => {
7
+ const users = useSelector((state) => state.users);
8
+ const user = users?.user;
9
+ const userId = useSelector((state) =>
10
+ state.userSession.token ? jwtDecode(state.userSession.token).sub : '',
11
+ );
12
+ const dispatch = useDispatch();
13
+
14
+ useEffect(() => {
15
+ if (!user?.id && users?.get.loading === false) {
16
+ dispatch(getUser(userId));
17
+ }
18
+ }, [dispatch, userId, user, users?.get.loading]);
19
+
20
+ return user;
21
+ };
22
+
23
+ export default useUser;
@@ -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
+ }
@@ -1,2 +1,3 @@
1
1
  export { default as useClipboard } from "@plone/volto/hooks/clipboard/useClipboard";
2
2
  export { useClient } from "@plone/volto/hooks/client/useClient";
3
+ export { default as useUser } from "@plone/volto/hooks/user/useUser";
@@ -0,0 +1,2 @@
1
+ export default useUser;
2
+ declare function useUser(): any;