@plone/volto 16.0.0 → 16.2.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 (64) hide show
  1. package/CHANGELOG.md +30 -6
  2. package/cypress/support/commands.js +1 -1
  3. package/locales/ca/LC_MESSAGES/volto.po +22 -0
  4. package/locales/ca.json +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +22 -0
  6. package/locales/de.json +1 -1
  7. package/locales/en/LC_MESSAGES/volto.po +22 -0
  8. package/locales/en.json +1 -1
  9. package/locales/es/LC_MESSAGES/volto.po +22 -0
  10. package/locales/es.json +1 -1
  11. package/locales/eu/LC_MESSAGES/volto.po +22 -0
  12. package/locales/eu.json +1 -1
  13. package/locales/fr/LC_MESSAGES/volto.po +22 -0
  14. package/locales/fr.json +1 -1
  15. package/locales/it/LC_MESSAGES/volto.po +22 -0
  16. package/locales/it.json +1 -1
  17. package/locales/ja/LC_MESSAGES/volto.po +22 -0
  18. package/locales/ja.json +1 -1
  19. package/locales/nl/LC_MESSAGES/volto.po +22 -0
  20. package/locales/nl.json +1 -1
  21. package/locales/pt/LC_MESSAGES/volto.po +22 -0
  22. package/locales/pt.json +1 -1
  23. package/locales/pt_BR/LC_MESSAGES/volto.po +22 -0
  24. package/locales/pt_BR.json +1 -1
  25. package/locales/ro/LC_MESSAGES/volto.po +22 -0
  26. package/locales/ro.json +1 -1
  27. package/locales/volto.pot +23 -1
  28. package/package.json +5 -3
  29. package/packages/volto-slate/README.md +2 -233
  30. package/packages/volto-slate/src/blocks/Text/extensions/index.js +1 -0
  31. package/packages/volto-slate/src/blocks/Text/extensions/normalizeExternalData.js +7 -0
  32. package/packages/volto-slate/src/blocks/Text/index.js +2 -0
  33. package/packages/volto-slate/src/editor/config.jsx +2 -0
  34. package/packages/volto-slate/src/editor/deserialize.js +25 -55
  35. package/packages/volto-slate/src/editor/extensions/index.js +1 -0
  36. package/packages/volto-slate/src/editor/extensions/insertData.js +17 -4
  37. package/packages/volto-slate/src/editor/extensions/normalizeExternalData.js +8 -0
  38. package/packages/volto-slate/src/editor/ui/Toolbar.jsx +8 -3
  39. package/packages/volto-slate/src/editor/ui/ToolbarButton.test.js +2 -2
  40. package/packages/volto-slate/src/editor/utils.js +248 -0
  41. package/src/components/index.js +3 -0
  42. package/src/components/manage/Blocks/Block/DefaultEdit.jsx +44 -0
  43. package/src/components/manage/Blocks/Block/DefaultView.jsx +78 -0
  44. package/src/components/manage/Blocks/Block/Edit.jsx +3 -2
  45. package/src/components/manage/Blocks/HeroImageLeft/Data.jsx +2 -1
  46. package/src/components/manage/Blocks/Image/ImageSidebar.jsx +1 -0
  47. package/src/components/manage/Blocks/Listing/ListingData.jsx +1 -0
  48. package/src/components/manage/Blocks/Maps/MapsSidebar.jsx +1 -0
  49. package/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +1 -0
  50. package/src/components/manage/Blocks/Search/components/SelectFacet.jsx +2 -1
  51. package/src/components/manage/Blocks/ToC/Edit.jsx +1 -0
  52. package/src/components/manage/Blocks/Video/VideoSidebar.jsx +1 -1
  53. package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +14 -11
  54. package/src/components/manage/Widgets/NumberWidget.jsx +1 -1
  55. package/src/components/manage/Widgets/ObjectListWidget.jsx +19 -2
  56. package/src/components/theme/Error/ErrorBoundary.jsx +29 -0
  57. package/src/components/theme/Error/ErrorBoundary.test.jsx +34 -0
  58. package/src/components/theme/View/RenderBlocks.jsx +3 -1
  59. package/src/config/Style.jsx +9 -0
  60. package/src/config/index.js +2 -0
  61. package/src/helpers/Blocks/Blocks.js +37 -38
  62. package/src/helpers/Blocks/Blocks.test.js +64 -0
  63. package/src/helpers/MessageLabels/MessageLabels.js +18 -0
  64. package/test-setup-config.js +7 -0
@@ -1,70 +1,37 @@
1
1
  import { jsx } from 'slate-hyperscript';
2
2
  import { Text } from 'slate';
3
- import { isWhitespace } from '@plone/volto-slate/utils';
4
- import {
5
- TD,
6
- TH,
7
- COMMENT,
8
- ELEMENT_NODE,
9
- INLINE_ELEMENTS,
10
- TEXT_NODE,
11
- } from '../constants';
12
-
13
- const isInline = (node) =>
14
- node &&
15
- (node.nodeType === TEXT_NODE || INLINE_ELEMENTS.includes(node.nodeName));
3
+ // import { isWhitespace } from '@plone/volto-slate/utils';
4
+ import { TD, TH, COMMENT, ELEMENT_NODE, TEXT_NODE } from '../constants';
5
+
6
+ import { collapseInlineSpace } from './utils';
16
7
 
17
8
  /**
18
- * Deserialize to an object or an Array.
9
+ * Deserialize to a Slate Node, an Array of Slate Nodes or null
10
+ *
11
+ * One particularity of this function is that it tries to do
12
+ * a "perception-based" conversion. For example, in html, multiple whitespaces
13
+ * display as a single space. A new line character in text is actually rendered
14
+ * as a space, etc. So we try to meet user's expectations that when they
15
+ * copy/paste content, we'll preserve the aspect of their text.
19
16
  *
20
- * This returns a Slate Node or null.
17
+ * See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
21
18
  */
22
19
  export const deserialize = (editor, el) => {
23
- // console.log('deserialize el:', el);
24
20
  const { htmlTagsToSlate } = editor;
25
21
 
26
- // console.log('des:', el.nodeType, el);
27
22
  if (el.nodeType === COMMENT) {
28
23
  return null;
29
24
  } else if (el.nodeType === TEXT_NODE) {
30
- // instead of === '\n' we use isWhitespace for when deserializing tables
31
- // from Calc and other similar cases
32
-
33
- // console.log('maybe whitespace', {
34
- // text: `-${el.textContent}-`,
35
- // prev: el.previousSibling,
36
- // next: el.nextSibling,
37
- // isPrev: isInline(el.previousSibling),
38
- // isNext: isInline(el.nextSibling),
39
- // prevName: el.previousSibling && el.previousSibling.nodeName,
40
- // nextName: el.nextSibling && el.nextSibling.nodeName,
41
- // });
42
-
43
- if (isWhitespace(el.textContent)) {
44
- // console.log({
45
- // text: `-${el.textContent}-`,
46
- // prev: el.previousSibling,
47
- // next: el.nextSibling,
48
- // isPrev: isInline(el.previousSibling),
49
- // isNext: isInline(el.nextSibling),
50
- // prevName: el.previousSibling && el.previousSibling.nodeName,
51
- // nextName: el.nextSibling && el.nextSibling.nodeName,
52
- // });
53
- // if it's empty text between 2 tags, it should be ignored
54
- return isInline(el.previousSibling) || isInline(el.nextSibling)
55
- ? { text: el.textContent } // perceptually multiple whitespace render as a single space
56
- : null;
57
- }
58
- return {
59
- text: el.textContent
60
- .replace(/\n$/g, ' ')
61
- .replace(/\n/g, ' ')
62
- .replace(/\t/g, ''),
63
- };
25
+ const text = collapseInlineSpace(el);
26
+ return text
27
+ ? {
28
+ text,
29
+ }
30
+ : null;
64
31
  } else if (el.nodeType !== ELEMENT_NODE) {
65
32
  return null;
66
33
  } else if (el.nodeName === 'BR') {
67
- // TODO: is handling <br> like this ok in all cases ?
34
+ // gets merged with sibling text nodes by Slate normalization in insertData
68
35
  return { text: '\n' };
69
36
  }
70
37
 
@@ -78,7 +45,8 @@ export const deserialize = (editor, el) => {
78
45
  return htmlTagsToSlate[nodeName](editor, el);
79
46
  }
80
47
 
81
- return deserializeChildren(el, editor); // fallback deserializer
48
+ // fallback deserializer, all unknown elements are "stripped"
49
+ return deserializeChildren(el, editor);
82
50
  };
83
51
 
84
52
  export const typeDeserialize = (editor, el) => {
@@ -138,10 +106,11 @@ export const inlineTagDeserializer = (attrs) => (editor, el) => {
138
106
  export const spanTagDeserializer = (editor, el) => {
139
107
  const style = el.getAttribute('style') || '';
140
108
  let children = el.childNodes;
109
+
141
110
  if (
142
111
  // handle formatting from OpenOffice
143
112
  children.length === 1 &&
144
- children[0].nodeType === 3 &&
113
+ children[0].nodeType === TEXT_NODE &&
145
114
  children[0].textContent === '\n'
146
115
  ) {
147
116
  return jsx('text', {}, ' ');
@@ -149,7 +118,7 @@ export const spanTagDeserializer = (editor, el) => {
149
118
  children = deserializeChildren(el, editor);
150
119
 
151
120
  // whitespace is replaced by deserialize() with null;
152
- children = children.map((c) => (c === null ? ' ' : c));
121
+ children = children.map((c) => (c === null ? '' : c));
153
122
 
154
123
  // TODO: handle sub/sup as <sub> and <sup>
155
124
  // Handle Google Docs' <sub> formatting
@@ -171,6 +140,7 @@ export const spanTagDeserializer = (editor, el) => {
171
140
  const res = children.find((c) => typeof c !== 'string')
172
141
  ? children
173
142
  : jsx('text', {}, children);
143
+
174
144
  return res;
175
145
  };
176
146
 
@@ -3,3 +3,4 @@ export * from './insertData';
3
3
  export * from './isInline';
4
4
  export * from './withDeserializers';
5
5
  export * from './normalizeNode';
6
+ export * from './normalizeExternalData';
@@ -29,14 +29,27 @@ export const insertData = (editor) => {
29
29
 
30
30
  let fragment;
31
31
 
32
+ // eslint-disable-next-line no-console
33
+ console.debug('clipboard operation', {
34
+ clipboard: dt,
35
+ parsedBody: body,
36
+ });
37
+
32
38
  const val = deserialize(editor, body);
33
39
  fragment = Array.isArray(val) ? val : [val];
34
-
35
- // external normalization
36
- fragment = normalizeExternalData(editor, fragment);
40
+ fragment = editor.normalizeExternalData(fragment);
37
41
 
38
42
  editor.insertFragment(fragment);
39
43
 
44
+ // eslint-disable-next-line no-console
45
+ console.debug('result clipboard operation', {
46
+ clipboard: dt,
47
+ parsedBody: body,
48
+ deserializedValue: val,
49
+ normalizedFragment: fragment,
50
+ editorChildren: editor.children,
51
+ });
52
+
40
53
  return true;
41
54
  },
42
55
  'text/plain': (dt, fullMime) => {
@@ -94,6 +107,7 @@ export const insertData = (editor) => {
94
107
  }
95
108
  }
96
109
 
110
+ // always normalize when dealing with plain text
97
111
  const nodes = normalizeExternalData(editor, fragment);
98
112
  if (!containsText) {
99
113
  Transforms.insertNodes(editor, nodes);
@@ -116,7 +130,6 @@ export const insertData = (editor) => {
116
130
  editor.beforeInsertData(data);
117
131
  }
118
132
 
119
- // debugger;
120
133
  for (let i = 0; i < editor.dataTransferFormatsOrder.length; ++i) {
121
134
  const dt = editor.dataTransferFormatsOrder[i];
122
135
  if (dt === 'files') {
@@ -0,0 +1,8 @@
1
+ import { normalizeExternalData as normalize } from '@plone/volto-slate/utils';
2
+
3
+ export function normalizeExternalData(editor) {
4
+ editor.normalizeExternalData = (fragment) => {
5
+ return normalize(fragment);
6
+ };
7
+ return editor;
8
+ }
@@ -20,7 +20,7 @@ const Toolbar = ({
20
20
 
21
21
  useEffect(() => {
22
22
  const domNode = ref.current;
23
- let rect;
23
+ let rect = { width: 1, top: 0, left: 0 };
24
24
 
25
25
  if ((children || []).length === 0) {
26
26
  domNode.removeAttribute('style');
@@ -63,8 +63,13 @@ const Toolbar = ({
63
63
  // TODO: should we fallback to editor.getSelection()?
64
64
  // TODO: test with third party plugins
65
65
  const slateNode = Node.get(editor, selection.anchor.path);
66
- const domEl = ReactEditor.toDOMNode(editor, slateNode);
67
- rect = domEl.getBoundingClientRect();
66
+ try {
67
+ const domEl = ReactEditor.toDOMNode(editor, slateNode);
68
+ rect = domEl.getBoundingClientRect();
69
+ } catch {
70
+ // ignoring error here is safe, editor is out of sync and the selection
71
+ // is actually none, so no toolbar should be shown
72
+ }
68
73
  }
69
74
 
70
75
  domNode.style.opacity = 1;
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import configureStore from 'redux-mock-store';
3
3
  import { Provider } from 'react-intl-redux';
4
- import { wait, render } from '@testing-library/react';
4
+ import { waitFor, render } from '@testing-library/react';
5
5
 
6
6
  import ToolbarButton from './ToolbarButton';
7
7
 
@@ -20,6 +20,6 @@ describe('ToolbarButton', () => {
20
20
  <ToolbarButton />
21
21
  </Provider>,
22
22
  );
23
- await wait(() => expect(asFragment()).toMatchSnapshot());
23
+ await waitFor(() => expect(asFragment()).toMatchSnapshot());
24
24
  });
25
25
  });
@@ -0,0 +1,248 @@
1
+ import { INLINE_ELEMENTS, TEXT_NODE } from '../constants';
2
+
3
+ // Original at https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
4
+ /**
5
+ * Throughout, whitespace is defined as one of the characters
6
+ * "\t" TAB \u0009
7
+ * "\n" LF \u000A
8
+ * "\r" CR \u000D
9
+ * " " SPC \u0020
10
+ *
11
+ * This does not use JavaScript's "\s" because that includes non-breaking
12
+ * spaces (and also some other characters).
13
+ */
14
+
15
+ /**
16
+ * Determine whether a node's text content is entirely whitespace.
17
+ *
18
+ * @param nod A node implementing the |CharacterData| interface (i.e.,
19
+ * a |Text|, |Comment|, or |CDATASection| node
20
+ * @return True if all of the text content of |nod| is whitespace,
21
+ * otherwise false.
22
+ */
23
+ export function is_all_ws(text) {
24
+ return !/[^\t\n\r ]/.test(text);
25
+ }
26
+
27
+ /**
28
+ * Version of |data| that doesn't include whitespace at the beginning
29
+ * and end and normalizes all whitespace to a single space. (Normally
30
+ * |data| is a property of text nodes that gives the text of the node.)
31
+ *
32
+ * @param txt The text node whose data should be returned
33
+ * @return A string giving the contents of the text node with
34
+ * whitespace collapsed.
35
+ */
36
+ export function data_of(txt) {
37
+ let data = txt.textContent;
38
+ data = data.replace(/[\t\n\r ]+/g, ' ');
39
+ if (data[0] === ' ') {
40
+ data = data.substring(1, data.length);
41
+ }
42
+ if (data[data.length - 1] === ' ') {
43
+ data = data.substring(0, data.length - 1);
44
+ }
45
+ return data;
46
+ }
47
+
48
+ /**
49
+ * Determine if a node should be ignored by the iterator functions.
50
+ *
51
+ * @param nod An object implementing the DOM1 |Node| interface.
52
+ * @return true if the node is:
53
+ * 1) A |Text| node that is all whitespace
54
+ * 2) A |Comment| node
55
+ * and otherwise false.
56
+ */
57
+
58
+ export function is_ignorable(nod) {
59
+ return (
60
+ nod.nodeType === 8 || // A comment node
61
+ (nod.nodeType === 3 && is_all_ws(nod.textContent))
62
+ ); // a text node, all ws
63
+ }
64
+
65
+ /**
66
+ * Version of |previousSibling| that skips nodes that are entirely
67
+ * whitespace or comments. (Normally |previousSibling| is a property
68
+ * of all DOM nodes that gives the sibling node, the node that is
69
+ * a child of the same parent, that occurs immediately before the
70
+ * reference node.)
71
+ *
72
+ * @param sib The reference node.
73
+ * @return Either:
74
+ * 1) The closest previous sibling to |sib| that is not
75
+ * ignorable according to |is_ignorable|, or
76
+ * 2) null if no such node exists.
77
+ */
78
+ export function node_before(sib) {
79
+ while ((sib = sib.previousSibling)) {
80
+ if (!is_ignorable(sib)) {
81
+ return sib;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Version of |nextSibling| that skips nodes that are entirely
89
+ * whitespace or comments.
90
+ *
91
+ * @param sib The reference node.
92
+ * @return Either:
93
+ * 1) The closest next sibling to |sib| that is not
94
+ * ignorable according to |is_ignorable|, or
95
+ * 2) null if no such node exists.
96
+ */
97
+ export function node_after(sib) {
98
+ while ((sib = sib.nextSibling)) {
99
+ if (!is_ignorable(sib)) {
100
+ return sib;
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+
106
+ /**
107
+ * Version of |lastChild| that skips nodes that are entirely
108
+ * whitespace or comments. (Normally |lastChild| is a property
109
+ * of all DOM nodes that gives the last of the nodes contained
110
+ * directly in the reference node.)
111
+ *
112
+ * @param sib The reference node.
113
+ * @return Either:
114
+ * 1) The last child of |sib| that is not
115
+ * ignorable according to |is_ignorable|, or
116
+ * 2) null if no such node exists.
117
+ */
118
+ export function last_child(par) {
119
+ let res = par.lastChild;
120
+ while (res) {
121
+ if (!is_ignorable(res)) {
122
+ return res;
123
+ }
124
+ res = res.previousSibling;
125
+ }
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * Version of |firstChild| that skips nodes that are entirely
131
+ * whitespace and comments.
132
+ *
133
+ * @param sib The reference node.
134
+ * @return Either:
135
+ * 1) The first child of |sib| that is not
136
+ * ignorable according to |is_ignorable|, or
137
+ * 2) null if no such node exists.
138
+ */
139
+ export function first_child(par) {
140
+ let res = par.firstChild;
141
+ while (res) {
142
+ if (!is_ignorable(res)) {
143
+ return res;
144
+ }
145
+ res = res.nextSibling;
146
+ }
147
+ return null;
148
+ }
149
+
150
+ export const removeSpaceBeforeAfterEndLine = (text) => {
151
+ text = text.replace(/\s+\n/gm, '\n'); // space before endline
152
+ text = text.replace(/\n\s+/gm, '\n'); // space after endline
153
+ return text;
154
+ };
155
+
156
+ export const convertTabsToSpaces = (text) => text.replace(/\t/gm, ' ');
157
+ export const convertLineBreaksToSpaces = (text) => text.replace(/\n/gm, ' ');
158
+
159
+ export const isInline = (node) =>
160
+ node &&
161
+ (node.nodeType === TEXT_NODE || INLINE_ELEMENTS.includes(node.nodeName));
162
+
163
+ export const removeSpaceFollowSpace = (text, node) => {
164
+ // Any space immediately following another space (even across two separate
165
+ // inline elements) is ignored (rule 4)
166
+ text = text.replace(/ ( +)/gm, ' ');
167
+ if (!text.startsWith(' ')) return text;
168
+
169
+ if (node.previousSibling) {
170
+ if (node.previousSibling.nodeType === TEXT_NODE) {
171
+ if (node.previousSibling.textContent.endsWith(' ')) {
172
+ return text.replace(/^ /, '');
173
+ }
174
+ } else if (isInline(node.previousSibling)) {
175
+ const prevText = collapseInlineSpace(node.previousSibling);
176
+ if (prevText.endsWith(' ')) {
177
+ return text.replace(/^ /, '');
178
+ }
179
+ }
180
+ } else {
181
+ const parent = node.parentNode;
182
+ if (parent.previousSibling) {
183
+ // && isInline(parent.previousSibling)
184
+ const prevText = collapseInlineSpace(parent.previousSibling);
185
+ if (prevText && prevText.endsWith(' ')) {
186
+ return text.replace(/^ /, '');
187
+ }
188
+ }
189
+ }
190
+
191
+ return text;
192
+ };
193
+
194
+ export const removeElementEdges = (text, node) => {
195
+ if (
196
+ !isInline(node.parentNode) &&
197
+ !node.previousSibling &&
198
+ text.match(/^\s/)
199
+ ) {
200
+ text = text.replace(/^\s+/, '');
201
+ }
202
+
203
+ if (text.match(/\s$/) && !node.nextSibling && !isInline(node.parentNode)) {
204
+ text = text.replace(/\s$/, '');
205
+ }
206
+
207
+ return text;
208
+ };
209
+
210
+ export const collapseInlineSpace = (node) => {
211
+ let text = node.textContent;
212
+
213
+ // See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
214
+
215
+ // 1. all spaces and tabs immediately before and after a line break are ignored
216
+
217
+ text = removeSpaceBeforeAfterEndLine(text);
218
+
219
+ // 2. Next, all tab characters are handled as space characters
220
+ text = convertTabsToSpaces(text);
221
+
222
+ // 3. Convert all line breaks to spaces
223
+ text = convertLineBreaksToSpaces(text);
224
+
225
+ // 4. Any space immediately following another space
226
+ // (even across two separate inline elements) is ignored
227
+ text = removeSpaceFollowSpace(text, node);
228
+
229
+ // 5. Sequences of spaces at the beginning and end of an element are removed
230
+ text = removeElementEdges(text, node);
231
+
232
+ // (volto) Return null if the element is not adjacent to an inline node
233
+ // This will cause the element to be ignored in the deserialization
234
+ // TODO: use the node traverse functions defined here
235
+ if (
236
+ is_all_ws(text) &&
237
+ !(
238
+ isInline(node.previousSibling) ||
239
+ isInline(node.nextSibling) ||
240
+ isInline(node.parentNode.nextSibling) ||
241
+ isInline(node.parentNode.previousSibling)
242
+ )
243
+ ) {
244
+ return null;
245
+ }
246
+
247
+ return text;
248
+ };
@@ -30,6 +30,7 @@ export EventDetails from '@plone/volto/components/theme/EventDetails/EventDetail
30
30
  export PreviewImage from '@plone/volto/components/theme/PreviewImage/PreviewImage';
31
31
 
32
32
  export Error from '@plone/volto/components/theme/Error/Error';
33
+ export ErrorBoundary from '@plone/volto/components/theme/Error/ErrorBoundary';
33
34
  export NotFound from '@plone/volto/components/theme/NotFound/NotFound';
34
35
  export Forbidden from '@plone/volto/components/theme/Forbidden/Forbidden';
35
36
  export Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorized';
@@ -172,6 +173,7 @@ export ObjectBrowserWidgetMode from '@plone/volto/components/manage/Widgets/Obje
172
173
  export ObjectWidget from '@plone/volto/components/manage/Widgets/ObjectWidget';
173
174
  export ObjectListWidget from '@plone/volto/components/manage/Widgets/ObjectListWidget';
174
175
 
176
+ export EditDefaultBlock from '@plone/volto/components/manage/Blocks/Block/DefaultEdit';
175
177
  export EditDescriptionBlock from '@plone/volto/components/manage/Blocks/Description/Edit';
176
178
  export EditTitleBlock from '@plone/volto/components/manage/Blocks/Title/Edit';
177
179
  export EditToCBlock from '@plone/volto/components/manage/Blocks/ToC/Edit';
@@ -185,6 +187,7 @@ export ViewHeroImageLeftBlock from '@plone/volto/components/manage/Blocks/HeroIm
185
187
  export EditMapBlock from '@plone/volto/components/manage/Blocks/Maps/Edit';
186
188
  export EditHTMLBlock from '@plone/volto/components/manage/Blocks/HTML/Edit';
187
189
 
190
+ export ViewDefaultBlock from '@plone/volto/components/manage/Blocks/Block/DefaultView';
188
191
  export ViewDescriptionBlock from '@plone/volto/components/manage/Blocks/Description/View';
189
192
  export ViewTitleBlock from '@plone/volto/components/manage/Blocks/Title/View';
190
193
  export ViewToCBlock from '@plone/volto/components/manage/Blocks/ToC/View';
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import config from '@plone/volto/registry';
3
+ import { useIntl } from 'react-intl';
4
+ import { SidebarPortal } from '@plone/volto/components';
5
+ import { BlockDataForm } from '@plone/volto/components';
6
+ import DefaultBlockView from './DefaultView';
7
+
8
+ const DefaultBlockEdit = (props) => {
9
+ const { blocksConfig = config.blocks.blocksConfig } = props;
10
+ const { data, onChangeBlock, block, selected } = props;
11
+ const intl = useIntl();
12
+ const blockSchema = blocksConfig?.[data['@type']]?.blockSchema;
13
+ const schema =
14
+ typeof blockSchema === 'function'
15
+ ? blockSchema({ ...props, intl })
16
+ : blockSchema;
17
+
18
+ const BlockView = blocksConfig?.[data['@type']]?.['view'] || DefaultBlockView;
19
+ return (
20
+ <>
21
+ <BlockView {...props} />
22
+ {schema ? (
23
+ <SidebarPortal selected={selected}>
24
+ <BlockDataForm
25
+ block={block}
26
+ schema={schema}
27
+ title={schema.title}
28
+ onChangeField={(id, value) => {
29
+ onChangeBlock(block, {
30
+ ...data,
31
+ [id]: value,
32
+ });
33
+ }}
34
+ formData={data}
35
+ />
36
+ </SidebarPortal>
37
+ ) : (
38
+ ''
39
+ )}
40
+ </>
41
+ );
42
+ };
43
+
44
+ export default DefaultBlockEdit;
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { Container, Segment, Grid, Label } from 'semantic-ui-react';
4
+ import { ErrorBoundary } from '@plone/volto/components';
5
+ import { getWidget } from '@plone/volto/helpers/Widget/utils';
6
+ import config from '@plone/volto/registry';
7
+
8
+ const messages = defineMessages({
9
+ unknownBlock: {
10
+ id: 'Unknown Block',
11
+ defaultMessage: 'Unknown Block {block}',
12
+ },
13
+ invalidBlock: {
14
+ id: 'Invalid Block',
15
+ defaultMessage: 'Invalid block - Will be removed on saving',
16
+ },
17
+ });
18
+
19
+ const DefaultBlockView = (props) => {
20
+ const { data, block } = props;
21
+ const intl = useIntl();
22
+ const { views } = config.widgets;
23
+ const { blocksConfig = config.blocks.blocksConfig } = props;
24
+ if (!data)
25
+ return <div key={block}>{intl.formatMessage(messages.invalidBlock)}</div>;
26
+ // Compatibility with RenderBlocks non-view
27
+
28
+ const blockSchema = blocksConfig?.[data['@type']]?.blockSchema;
29
+ const schema =
30
+ typeof blockSchema === 'function'
31
+ ? blockSchema({ ...props, intl })
32
+ : blockSchema;
33
+ const fieldsets = schema.fieldsets || [];
34
+
35
+ return schema ? (
36
+ <Container className="page-block">
37
+ {fieldsets?.map((fs) => {
38
+ return (
39
+ <div className="fieldset" key={fs.id}>
40
+ {fs.id !== 'default' && <h2>{fs.title}</h2>}
41
+ {fs.fields?.map((f, key) => {
42
+ let field = {
43
+ ...schema?.properties[f],
44
+ id: f,
45
+ widget: getWidget(f, schema?.properties[f]),
46
+ };
47
+ let Widget = views?.getWidget(field);
48
+ return f !== 'title' ? (
49
+ <Grid celled="internally" key={key}>
50
+ <Grid.Row>
51
+ <Label>{field.title}:</Label>
52
+ </Grid.Row>
53
+ <Grid.Row>
54
+ <Segment basic>
55
+ <ErrorBoundary name={f}>
56
+ <Widget value={data[f]} />
57
+ </ErrorBoundary>
58
+ </Segment>
59
+ </Grid.Row>
60
+ </Grid>
61
+ ) : (
62
+ <Widget key={key} value={data[f]} />
63
+ );
64
+ })}
65
+ </div>
66
+ );
67
+ })}
68
+ </Container>
69
+ ) : (
70
+ <div key={block}>
71
+ {intl.formatMessage(messages.unknownBlock, {
72
+ block: data['@type'],
73
+ })}
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default DefaultBlockView;
@@ -13,6 +13,7 @@ import { setSidebarTab } from '@plone/volto/actions';
13
13
  import config from '@plone/volto/registry';
14
14
  import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
15
15
  import { applyBlockDefaults } from '@plone/volto/helpers';
16
+ import { ViewDefaultBlock, EditDefaultBlock } from '@plone/volto/components';
16
17
 
17
18
  import {
18
19
  SidebarPortal,
@@ -120,12 +121,12 @@ export class Edit extends Component {
120
121
 
121
122
  const disableNewBlocks = this.props.data?.disableNewBlocks;
122
123
 
123
- let Block = blocksConfig?.[type]?.['edit'] || null;
124
+ let Block = blocksConfig?.[type]?.['edit'] || EditDefaultBlock;
124
125
  if (
125
126
  this.props.data?.readOnly ||
126
127
  (!editable && !config.blocks.showEditBlocksInBabelView)
127
128
  ) {
128
- Block = blocksConfig?.[type]?.['view'] || null;
129
+ Block = blocksConfig?.[type]?.['view'] || ViewDefaultBlock;
129
130
  }
130
131
  const schema = blocksConfig?.[type]?.['schema'] || BlockSettingsSchema;
131
132
  const blockHasOwnFocusManagement =
@@ -9,7 +9,6 @@ const HeroImageLeftBlockData = (props) => {
9
9
  const schema = schemaHero({ ...props, intl });
10
10
  return (
11
11
  <BlockDataForm
12
- block={block}
13
12
  schema={schema}
14
13
  title={schema.title}
15
14
  onChangeField={(id, value) => {
@@ -18,7 +17,9 @@ const HeroImageLeftBlockData = (props) => {
18
17
  [id]: value,
19
18
  });
20
19
  }}
20
+ onChangeBlock={onChangeBlock}
21
21
  formData={data}
22
+ block={block}
22
23
  />
23
24
  );
24
25
  };
@@ -52,6 +52,7 @@ const ImageSidebar = (props) => {
52
52
  [id]: value,
53
53
  });
54
54
  }}
55
+ onChangeBlock={onChangeBlock}
55
56
  formData={data}
56
57
  block={block}
57
58
  />