@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.
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +31 -0
- package/locales/ca/LC_MESSAGES/volto.po +5 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +5 -0
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +5 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +5 -0
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +5 -0
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +5 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +5 -0
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +5 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +5 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +5 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +5 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +5 -0
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +5 -0
- package/locales/ro.json +1 -1
- package/locales/volto.pot +6 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +5 -0
- package/locales/zh_CN.json +1 -1
- package/package.json +1 -1
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/blocks/Text/SlashMenu.jsx +15 -1
- package/src/components/manage/Add/Add.jsx +1 -0
- package/src/components/manage/BlockChooser/BlockChooser.jsx +9 -1
- package/src/components/manage/BlockChooser/BlockChooser.test.jsx +4 -0
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +2 -0
- package/src/components/manage/Blocks/Search/components/SortOn.jsx +82 -55
- package/src/components/manage/Contents/Contents.jsx +3 -0
- package/src/components/manage/Contents/ContentsPropertiesModal.jsx +85 -52
- package/src/components/manage/Diff/DiffField.jsx +167 -39
- package/src/components/manage/Form/Form.jsx +1 -0
- package/src/components/manage/Toolbar/Toolbar.jsx +1 -1
- package/src/components/manage/Widgets/InternalUrlWidget.jsx +13 -17
- package/src/config/index.js +1 -0
- package/src/hooks/index.js +1 -0
- package/src/hooks/user/useUser.js +23 -0
- package/theme/themes/pastanaga/extras/blocks.less +6 -0
- package/theme/themes/pastanaga/extras/toolbar.less +10 -3
- package/types/hooks/index.d.ts +1 -0
- package/types/hooks/user/useUser.d.ts +2 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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, () =>
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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.
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
}}
|
|
@@ -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 {
|
|
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
|
|
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: '
|
|
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 {
|
|
48
|
+
* @returns {string} Empty string
|
|
48
49
|
*/
|
|
49
50
|
const clear = () => {
|
|
50
|
-
|
|
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
|
|
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
|
|
97
|
-
maxLength={maxLength
|
|
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
|
|
176
|
+
export default withObjectBrowser(InternalUrlWidget);
|
package/src/config/index.js
CHANGED
|
@@ -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,
|
package/src/hooks/index.js
CHANGED
|
@@ -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
|
+
}
|
package/types/hooks/index.d.ts
CHANGED