@plone/volto 16.0.0-alpha.35 → 16.0.0-alpha.37

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 (36) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +1 -0
  3. package/locales/de/LC_MESSAGES/volto.po +1 -0
  4. package/locales/en/LC_MESSAGES/volto.po +1 -0
  5. package/locales/es/LC_MESSAGES/volto.po +1 -0
  6. package/locales/eu/LC_MESSAGES/volto.po +1 -0
  7. package/locales/fr/LC_MESSAGES/volto.po +1 -0
  8. package/locales/it/LC_MESSAGES/volto.po +1 -0
  9. package/locales/ja/LC_MESSAGES/volto.po +1 -0
  10. package/locales/nl/LC_MESSAGES/volto.po +1 -0
  11. package/locales/pt/LC_MESSAGES/volto.po +1 -0
  12. package/locales/pt_BR/LC_MESSAGES/volto.po +1 -0
  13. package/locales/ro/LC_MESSAGES/volto.po +1 -0
  14. package/locales/volto.pot +2 -1
  15. package/package.json +1 -1
  16. package/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +141 -0
  17. package/packages/volto-slate/src/editor/plugins/StyleMenu/index.js +19 -0
  18. package/packages/volto-slate/src/editor/plugins/StyleMenu/style.less +29 -0
  19. package/packages/volto-slate/src/editor/plugins/StyleMenu/utils.js +159 -0
  20. package/packages/volto-slate/src/editor/plugins/index.js +2 -0
  21. package/packages/volto-slate/src/index.js +1 -0
  22. package/packages/volto-slate/src/utils/blocks.js +1 -2
  23. package/src/components/manage/Blocks/Block/BlocksForm.jsx +1 -1
  24. package/src/components/manage/Blocks/Listing/getAsyncData.js +5 -2
  25. package/src/components/manage/Controlpanels/Controlpanels.jsx +6 -2
  26. package/src/components/manage/Diff/Diff.jsx +8 -3
  27. package/src/components/manage/Form/BlockDataForm.jsx +1 -1
  28. package/src/components/manage/Form/BlockDataForm.test.jsx +74 -0
  29. package/src/components/manage/Form/Form.jsx +3 -1
  30. package/src/components/manage/Widgets/AlignWidget.stories.jsx +6 -1
  31. package/src/components/manage/Widgets/NumberWidget.jsx +1 -1
  32. package/src/components/theme/App/App.jsx +1 -0
  33. package/src/helpers/Extensions/index.js +4 -1
  34. package/src/helpers/FormValidation/FormValidation.js +3 -1
  35. package/src/helpers/FormValidation/FormValidation.test.js +11 -0
  36. package/src/helpers/index.js +1 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Change Log
2
2
 
3
+ ## 16.0.0-alpha.37 (2022-09-27)
4
+
5
+ ### Feature
6
+
7
+ - Added resetOnCancel functionality in Form component @MdSahil-oss
8
+ - volto-slate: introduce style-menu @nileshgulia1
9
+
10
+ ### Bugfix
11
+
12
+ - Fix `listing` block in SSR, now that it is fully variations aware and the configuration is passed to the SSR `querystring` action. @sneridagh
13
+ - Remove wrapping ul or ol when deselecting list style @robgietema
14
+
15
+ ## 16.0.0-alpha.36 (2022-09-26)
16
+
17
+ ### Bugfix
18
+
19
+ - Fix number widget when the value is 0 @iRohitSingh
20
+ - Fix the typo in change workflow status dialog in "de" @iRohitSingh
21
+ - Show unauthorized message when accessing the diff view without permission @robgietema
22
+ - Fix i18n in title of Aliases control panel @sneridagh
23
+ - The styling schema is now applied before the block variations schema enhancers, to allow those enhancers a chance to tweak the styling schema @tiberiuichim
24
+
25
+ ### Documentation
26
+
27
+ - Added controls for the `actions` property of the `AlignWidget` storybook @JeffersonBledsoe #3671
28
+ - Generic Setup -> `GenericSetup`. @stevepiercy
29
+
3
30
  ## 16.0.0-alpha.35 (2022-09-21)
4
31
 
5
32
  ### Breaking
@@ -13,6 +40,7 @@
13
40
  - Fix selection error when pressing backspace @robgietema
14
41
  - Fix sidebarTab in Toc Block @iRohitSingh
15
42
  - Fix virtualization (windowing) when displaying options with long titles for select widgets. (The virtualization happen when the number of options is greater than 25). Add dynamic height aware options using `react-virtualized`. @sneridagh
43
+ - Fix email validation to ensure all addresses are correctly validated @instification
16
44
 
17
45
  ### Documentation
18
46
 
@@ -30,6 +58,7 @@
30
58
 
31
59
  ### Bugfix
32
60
 
61
+ - Fix Press Enter in some blocks does not focus on the text block below #3647 @dobri1408
33
62
  - Add `matchAllRoutes` to AsyncConnect so that it matches all configured `asyncPropsExtenders` @tiberiuichim
34
63
  - Fix acceptence test groups controlpanel @ksuess
35
64
 
@@ -3092,6 +3092,7 @@ msgstr "UID"
3092
3092
 
3093
3093
  #: components/manage/Aliases/Aliases
3094
3094
  #: components/manage/Controlpanels/Aliases
3095
+ #: components/manage/Controlpanels/Controlpanels
3095
3096
  #: components/manage/Toolbar/More
3096
3097
  # defaultMessage: URL Management
3097
3098
  msgid "URL Management"
@@ -3089,6 +3089,7 @@ msgstr "UID"
3089
3089
 
3090
3090
  #: components/manage/Aliases/Aliases
3091
3091
  #: components/manage/Controlpanels/Aliases
3092
+ #: components/manage/Controlpanels/Controlpanels
3092
3093
  #: components/manage/Toolbar/More
3093
3094
  # defaultMessage: URL Management
3094
3095
  msgid "URL Management"
@@ -3083,6 +3083,7 @@ msgstr ""
3083
3083
 
3084
3084
  #: components/manage/Aliases/Aliases
3085
3085
  #: components/manage/Controlpanels/Aliases
3086
+ #: components/manage/Controlpanels/Controlpanels
3086
3087
  #: components/manage/Toolbar/More
3087
3088
  # defaultMessage: URL Management
3088
3089
  msgid "URL Management"
@@ -3093,6 +3093,7 @@ msgstr "UID"
3093
3093
 
3094
3094
  #: components/manage/Aliases/Aliases
3095
3095
  #: components/manage/Controlpanels/Aliases
3096
+ #: components/manage/Controlpanels/Controlpanels
3096
3097
  #: components/manage/Toolbar/More
3097
3098
  # defaultMessage: URL Management
3098
3099
  msgid "URL Management"
@@ -3090,6 +3090,7 @@ msgstr "UID"
3090
3090
 
3091
3091
  #: components/manage/Aliases/Aliases
3092
3092
  #: components/manage/Controlpanels/Aliases
3093
+ #: components/manage/Controlpanels/Controlpanels
3093
3094
  #: components/manage/Toolbar/More
3094
3095
  # defaultMessage: URL Management
3095
3096
  msgid "URL Management"
@@ -3100,6 +3100,7 @@ msgstr "UID"
3100
3100
 
3101
3101
  #: components/manage/Aliases/Aliases
3102
3102
  #: components/manage/Controlpanels/Aliases
3103
+ #: components/manage/Controlpanels/Controlpanels
3103
3104
  #: components/manage/Toolbar/More
3104
3105
  # defaultMessage: URL Management
3105
3106
  msgid "URL Management"
@@ -3083,6 +3083,7 @@ msgstr "UID"
3083
3083
 
3084
3084
  #: components/manage/Aliases/Aliases
3085
3085
  #: components/manage/Controlpanels/Aliases
3086
+ #: components/manage/Controlpanels/Controlpanels
3086
3087
  #: components/manage/Toolbar/More
3087
3088
  # defaultMessage: URL Management
3088
3089
  msgid "URL Management"
@@ -3091,6 +3091,7 @@ msgstr "UID"
3091
3091
 
3092
3092
  #: components/manage/Aliases/Aliases
3093
3093
  #: components/manage/Controlpanels/Aliases
3094
+ #: components/manage/Controlpanels/Controlpanels
3094
3095
  #: components/manage/Toolbar/More
3095
3096
  # defaultMessage: URL Management
3096
3097
  msgid "URL Management"
@@ -3102,6 +3102,7 @@ msgstr ""
3102
3102
 
3103
3103
  #: components/manage/Aliases/Aliases
3104
3104
  #: components/manage/Controlpanels/Aliases
3105
+ #: components/manage/Controlpanels/Controlpanels
3105
3106
  #: components/manage/Toolbar/More
3106
3107
  # defaultMessage: URL Management
3107
3108
  msgid "URL Management"
@@ -3091,6 +3091,7 @@ msgstr ""
3091
3091
 
3092
3092
  #: components/manage/Aliases/Aliases
3093
3093
  #: components/manage/Controlpanels/Aliases
3094
+ #: components/manage/Controlpanels/Controlpanels
3094
3095
  #: components/manage/Toolbar/More
3095
3096
  # defaultMessage: URL Management
3096
3097
  msgid "URL Management"
@@ -3093,6 +3093,7 @@ msgstr "UID"
3093
3093
 
3094
3094
  #: components/manage/Aliases/Aliases
3095
3095
  #: components/manage/Controlpanels/Aliases
3096
+ #: components/manage/Controlpanels/Controlpanels
3096
3097
  #: components/manage/Toolbar/More
3097
3098
  # defaultMessage: URL Management
3098
3099
  msgid "URL Management"
@@ -3083,6 +3083,7 @@ msgstr "UID"
3083
3083
 
3084
3084
  #: components/manage/Aliases/Aliases
3085
3085
  #: components/manage/Controlpanels/Aliases
3086
+ #: components/manage/Controlpanels/Controlpanels
3086
3087
  #: components/manage/Toolbar/More
3087
3088
  # defaultMessage: URL Management
3088
3089
  msgid "URL Management"
package/locales/volto.pot CHANGED
@@ -1,7 +1,7 @@
1
1
  msgid ""
2
2
  msgstr ""
3
3
  "Project-Id-Version: Plone\n"
4
- "POT-Creation-Date: 2022-09-16T10:33:59.942Z\n"
4
+ "POT-Creation-Date: 2022-09-23T09:41:15.663Z\n"
5
5
  "Last-Translator: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
6
6
  "Language-Team: Plone i18n <plone-i18n@lists.sourceforge.net>\n"
7
7
  "MIME-Version: 1.0\n"
@@ -3085,6 +3085,7 @@ msgstr ""
3085
3085
 
3086
3086
  #: components/manage/Aliases/Aliases
3087
3087
  #: components/manage/Controlpanels/Aliases
3088
+ #: components/manage/Controlpanels/Controlpanels
3088
3089
  #: components/manage/Toolbar/More
3089
3090
  # defaultMessage: URL Management
3090
3091
  msgid "URL Management"
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "16.0.0-alpha.35",
12
+ "version": "16.0.0-alpha.37",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -0,0 +1,141 @@
1
+ import React from 'react';
2
+ import { useSlate } from 'slate-react';
3
+ import { Dropdown } from 'semantic-ui-react';
4
+ import { useIntl, defineMessages } from 'react-intl';
5
+ import cx from 'classnames';
6
+ import { isBlockStyleActive, isInlineStyleActive, toggleStyle } from './utils';
7
+ import config from '@plone/volto/registry';
8
+ import { ToolbarButton } from '@plone/volto-slate/editor/ui';
9
+ import paintSVG from '@plone/volto/icons/paint.svg';
10
+
11
+ const messages = defineMessages({
12
+ inlineStyle: {
13
+ id: 'Inline Style',
14
+ defaultMessage: 'Inline Style',
15
+ },
16
+ paragraphStyle: {
17
+ id: 'Paragraph Style',
18
+ defaultMessage: 'Paragraph Style',
19
+ },
20
+ additionalStyles: {
21
+ id: 'Additional Styles',
22
+ defaultMessage: 'Additional Styles',
23
+ },
24
+ });
25
+
26
+ const StyleMenuButton = ({ icon, active, ...props }) => (
27
+ <ToolbarButton {...props} icon={icon} active={active} />
28
+ );
29
+
30
+ const MenuOpts = ({ editor, toSelect, option }) => {
31
+ const isActive = toSelect.includes(option);
32
+ return (
33
+ <Dropdown.Item
34
+ as="span"
35
+ active={isActive}
36
+ className={cx({ active: isActive })}
37
+ {...option}
38
+ onClick={(event, selItem) => {
39
+ toggleStyle(editor, {
40
+ cssClass: selItem.value,
41
+ isBlock: selItem.isBlock,
42
+ });
43
+ }}
44
+ />
45
+ );
46
+ };
47
+
48
+ const StylingsButton = (props) => {
49
+ const editor = useSlate();
50
+ const intl = useIntl();
51
+
52
+ // Converting the settings to a format that is required by dropdowns.
53
+ const inlineOpts = [
54
+ ...config.settings.slate.styleMenu.inlineStyles.map((def) => {
55
+ return {
56
+ value: def.cssClass,
57
+ text: def.label,
58
+ icon: def.icon,
59
+ isBlock: false,
60
+ };
61
+ }),
62
+ ];
63
+ const blockOpts = [
64
+ ...config.settings.slate.styleMenu.blockStyles.map((def) => {
65
+ return {
66
+ value: def.cssClass,
67
+ text: def.label,
68
+ icon: def.icon,
69
+ isBlock: true,
70
+ };
71
+ }),
72
+ ];
73
+
74
+ // Calculating the initial selection.
75
+ const toSelect = [];
76
+ // block styles
77
+ for (const val of blockOpts) {
78
+ const ia = isBlockStyleActive(editor, val.value);
79
+ if (ia) {
80
+ toSelect.push(val);
81
+ }
82
+ }
83
+ // inline styles
84
+ for (const val of inlineOpts) {
85
+ const ia = isInlineStyleActive(editor, val.value);
86
+ if (ia) {
87
+ toSelect.push(val);
88
+ }
89
+ }
90
+
91
+ const menuItemProps = {
92
+ toSelect,
93
+ editor,
94
+ };
95
+ const showMenu = inlineOpts.length || blockOpts.length;
96
+ return showMenu ? (
97
+ <Dropdown
98
+ id="style-menu"
99
+ pointing=" top left"
100
+ multiple
101
+ value={toSelect}
102
+ disabled={config.settings.slate.styleMenu.disabled ?? false}
103
+ additionLabel={intl.formatMessage(messages.additionalStyles)}
104
+ trigger={
105
+ <StyleMenuButton
106
+ title={intl.formatMessage(messages.additionalStyles)}
107
+ icon={paintSVG}
108
+ active={toSelect.length > 0}
109
+ />
110
+ }
111
+ >
112
+ <Dropdown.Menu>
113
+ {inlineOpts.length && (
114
+ <>
115
+ <Dropdown.Header
116
+ content={intl.formatMessage(messages.inlineStyle)}
117
+ />
118
+ {inlineOpts.map((option) => (
119
+ <MenuOpts {...menuItemProps} option={option} />
120
+ ))}
121
+ </>
122
+ )}
123
+
124
+ {blockOpts.length && (
125
+ <>
126
+ <Dropdown.Header
127
+ content={intl.formatMessage(messages.paragraphStyle)}
128
+ />
129
+ {blockOpts.map((option) => (
130
+ <MenuOpts {...menuItemProps} option={option} />
131
+ ))}
132
+ </>
133
+ )}
134
+ </Dropdown.Menu>
135
+ </Dropdown>
136
+ ) : (
137
+ ''
138
+ );
139
+ };
140
+
141
+ export default StylingsButton;
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import StyleMenu from './StyleMenu';
3
+ import './style.less';
4
+
5
+ export default function install(config) {
6
+ const { slate } = config.settings;
7
+
8
+ slate.buttons.styleMenu = (props) => <StyleMenu {...props} title="Styles" />;
9
+
10
+ slate.toolbarButtons.push('styleMenu');
11
+ slate.expandedToolbarButtons.push('styleMenu');
12
+
13
+ slate.styleMenu = {
14
+ inlineStyles: [],
15
+ blockStyles: [],
16
+ };
17
+
18
+ return config;
19
+ }
@@ -0,0 +1,29 @@
1
+ #style-menu.ui.dropdown .menu {
2
+ .item {
3
+ display: flex;
4
+ user-select: none;
5
+
6
+ span.text {
7
+ margin-top: auto;
8
+ line-height: normal;
9
+ vertical-align: middle;
10
+ }
11
+ }
12
+
13
+ .header {
14
+ color: rgb(153, 153, 153);
15
+ font-size: 1rem;
16
+ font-weight: normal;
17
+ }
18
+
19
+ .active {
20
+ z-index: 1;
21
+ background: #68778d;
22
+ box-shadow: none;
23
+ color: #ffffff;
24
+ font-weight: 300;
25
+ }
26
+
27
+ overflow: auto;
28
+ max-height: 50vh;
29
+ }
@@ -0,0 +1,159 @@
1
+ /* eslint no-console: ["error", { allow: ["warn", "error"] }] */
2
+ import { Editor, Transforms } from 'slate';
3
+ import { isBlockActive } from '@plone/volto-slate/utils';
4
+ import config from '@plone/volto/registry';
5
+
6
+ /**
7
+ * Toggles a style (e.g. in the StyleMenu plugin).
8
+ * @param {Editor} editor
9
+ * @param {object} options
10
+ * @param {boolean} options.isRequested Whether the given style is requested by
11
+ * the user. The style is only applied if it is requested and only removed if it
12
+ * is not requested.
13
+ */
14
+ export const toggleStyle = (editor, { cssClass, isBlock, isRequested }) => {
15
+ if (isBlock) {
16
+ toggleBlockStyle(editor, cssClass);
17
+ } else {
18
+ toggleInlineStyle(editor, cssClass);
19
+ }
20
+ };
21
+
22
+ export const toggleBlockStyle = (editor, style) => {
23
+ // We have 6 boolean variables which need to be accounted for.
24
+ // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing
25
+ const { slate } = config.settings;
26
+
27
+ const isListItem = isBlockActive(editor, slate.listItemType);
28
+ const isActive = isBlockStyleActive(editor, style);
29
+ const wantsList = false;
30
+
31
+ if (isListItem && !wantsList) {
32
+ toggleBlockStyleAsListItem(editor, style);
33
+ } else if (isListItem && wantsList && !isActive) {
34
+ // switchListType(editor, format); // this will deconstruct to Volto blocks
35
+ } else if (!isListItem && wantsList) {
36
+ // changeBlockToList(editor, format);
37
+ } else if (!isListItem && !wantsList) {
38
+ internalToggleBlockStyle(editor, style);
39
+ } else {
40
+ console.warn('toggleBlockStyle case not covered, please examine:', {
41
+ wantsList,
42
+ isActive,
43
+ isListItem,
44
+ });
45
+ }
46
+ };
47
+
48
+ export const toggleInlineStyle = (editor, style) => {
49
+ // We have 6 boolean variables which need to be accounted for.
50
+ // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing
51
+ const { slate } = config.settings;
52
+
53
+ const isListItem = isBlockActive(editor, slate.listItemType);
54
+ const isActive = isInlineStyleActive(editor, style);
55
+ const wantsList = false;
56
+
57
+ if (isListItem && !wantsList) {
58
+ toggleInlineStyleAsListItem(editor, style);
59
+ } else if (isListItem && wantsList && !isActive) {
60
+ // switchListType(editor, format); // this will deconstruct to Volto blocks
61
+ } else if (!isListItem && wantsList) {
62
+ // changeBlockToList(editor, format);
63
+ } else if (!isListItem && !wantsList) {
64
+ internalToggleInlineStyle(editor, style);
65
+ } else {
66
+ console.warn('toggleInlineStyle case not covered, please examine:', {
67
+ wantsList,
68
+ isActive,
69
+ isListItem,
70
+ });
71
+ }
72
+ };
73
+
74
+ export const isBlockStyleActive = (editor, style) => {
75
+ const sn = Array.from(
76
+ Editor.nodes(editor, {
77
+ match: (n) => !Editor.isEditor(n) && typeof n.styleName === 'string',
78
+ mode: 'highest',
79
+ }),
80
+ );
81
+
82
+ for (const [n] of sn) {
83
+ if (n.styleName.split(' ').filter((x) => x === style).length > 0) {
84
+ return true;
85
+ }
86
+ }
87
+
88
+ return false;
89
+ };
90
+
91
+ export const isInlineStyleActive = (editor, style) => {
92
+ const m = Editor.marks(editor);
93
+ const keyName = `style-${style}`;
94
+ if (m && m[keyName]) {
95
+ return true;
96
+ }
97
+ return false;
98
+ };
99
+
100
+ export const internalToggleBlockStyle = (editor, style) => {
101
+ toggleBlockStyleInSelection(editor, style);
102
+ };
103
+
104
+ export const internalToggleInlineStyle = (editor, style) => {
105
+ toggleInlineStyleInSelection(editor, style);
106
+ };
107
+
108
+ /*
109
+ * Applies a block format unto a list item. Will split the list and deconstruct the
110
+ * block
111
+ */
112
+ export const toggleBlockStyleAsListItem = (editor, style) => {
113
+ toggleBlockStyleInSelection(editor, style);
114
+ };
115
+
116
+ /*
117
+ * Applies an inline style unto a list item.
118
+ */
119
+ export const toggleInlineStyleAsListItem = (editor, style) => {
120
+ toggleInlineStyleInSelection(editor, style);
121
+ };
122
+
123
+ function toggleInlineStyleInSelection(editor, style) {
124
+ const m = Editor.marks(editor);
125
+ const keyName = 'style-' + style;
126
+
127
+ if (m && m[keyName]) {
128
+ Editor.removeMark(editor, keyName);
129
+ } else {
130
+ Editor.addMark(editor, keyName, true);
131
+ }
132
+ }
133
+
134
+ function toggleBlockStyleInSelection(editor, style) {
135
+ const sn = Array.from(
136
+ Editor.nodes(editor, {
137
+ mode: 'highest',
138
+ match: (n) => {
139
+ return !Editor.isEditor(n);
140
+ },
141
+ }),
142
+ );
143
+
144
+ for (const [n, p] of sn) {
145
+ let cn = n.styleName;
146
+ if (typeof n.styleName !== 'string') {
147
+ cn = style;
148
+ } else if (n.styleName.split(' ').filter((x) => x === style).length > 0) {
149
+ cn = cn
150
+ .split(' ')
151
+ .filter((x) => x !== style)
152
+ .join(' ');
153
+ } else {
154
+ // the style is not set but other styles are set
155
+ cn = cn.split(' ').concat(style).join(' ');
156
+ }
157
+ Transforms.setNodes(editor, { styleName: cn }, { at: p });
158
+ }
159
+ }
@@ -4,6 +4,7 @@ import installImage from './Image';
4
4
  import installLinkPlugin from './Link';
5
5
  import installMarkdown from './Markdown';
6
6
  import installTable from './Table';
7
+ import installStyleMenu from './StyleMenu';
7
8
 
8
9
  export default function install(config) {
9
10
  return [
@@ -13,5 +14,6 @@ export default function install(config) {
13
14
  installMarkdown,
14
15
  installImage,
15
16
  installTable,
17
+ installStyleMenu,
16
18
  ].reduce((acc, apply) => apply(acc), config);
17
19
  }
@@ -31,6 +31,7 @@ export default (config) => {
31
31
  'numbered-list',
32
32
  'bulleted-list',
33
33
  'blockquote',
34
+ 'styleMenu',
34
35
  ];
35
36
 
36
37
  config.addonReducers = {
@@ -212,7 +212,6 @@ export const toggleBlock = (editor, format, allowedChildren) => {
212
212
  const isListItem = isBlockActive(editor, slate.listItemType);
213
213
  const isActive = isBlockActive(editor, format);
214
214
  const wantsList = listTypes.includes(format);
215
- // console.log({ isListItem, isActive, wantsList, format });
216
215
 
217
216
  if (isListItem && !wantsList) {
218
217
  toggleFormatAsListItem(editor, format);
@@ -223,7 +222,7 @@ export const toggleBlock = (editor, format, allowedChildren) => {
223
222
  } else if (!isListItem && !wantsList) {
224
223
  toggleFormat(editor, format, allowedChildren);
225
224
  } else if (isListItem && wantsList && isActive) {
226
- toggleFormatAsListItem(editor, slate.defaultBlockType);
225
+ clearFormatting(editor);
227
226
  } else {
228
227
  console.warn('toggleBlock case not covered, please examine:', {
229
228
  wantsList,
@@ -77,7 +77,7 @@ const BlocksForm = (props) => {
77
77
  e.preventDefault();
78
78
  }
79
79
  if (e.key === 'Enter' && !disableEnter) {
80
- onAddBlock(config.settings.defaultBlockType, index + 1);
80
+ onSelectBlock(onAddBlock(config.settings.defaultBlockType, index + 1));
81
81
  e.preventDefault();
82
82
  }
83
83
  };
@@ -1,13 +1,16 @@
1
1
  import { getQueryStringResults } from '@plone/volto/actions';
2
+ import { resolveBlockExtensions } from '@plone/volto/helpers';
3
+
4
+ export default ({ dispatch, data, path, blocksConfig }) => {
5
+ const { resolvedExtensions } = resolveBlockExtensions(data, blocksConfig);
2
6
 
3
- export default ({ dispatch, data, path }) => {
4
7
  return [
5
8
  dispatch(
6
9
  getQueryStringResults(
7
10
  path,
8
11
  {
9
12
  ...data.querystring,
10
- ...(data.variation?.fullobjects
13
+ ...(resolvedExtensions?.variation?.fullobjects
11
14
  ? { fullobjects: 1 }
12
15
  : { metadata_fields: '_all' }),
13
16
  },
@@ -78,6 +78,10 @@ const messages = defineMessages({
78
78
  id: 'User Group Membership',
79
79
  defaultMessage: 'User Group Membership',
80
80
  },
81
+ urlmanagement: {
82
+ id: 'URL Management',
83
+ defaultMessage: 'URL Management',
84
+ },
81
85
  });
82
86
 
83
87
  /**
@@ -175,8 +179,8 @@ class Controlpanels extends Component {
175
179
  },
176
180
  {
177
181
  '@id': '/aliases',
178
- group: 'General',
179
- title: 'URL Management',
182
+ group: this.props.intl.formatMessage(messages.general),
183
+ title: this.props.intl.formatMessage(messages.urlmanagement),
180
184
  },
181
185
  {
182
186
  '@id': '/moderate-comments',
@@ -27,6 +27,7 @@ import {
27
27
  FormattedDate,
28
28
  Icon,
29
29
  Toolbar,
30
+ Unauthorized,
30
31
  } from '@plone/volto/components';
31
32
 
32
33
  import backSVG from '@plone/volto/icons/back.svg';
@@ -66,6 +67,7 @@ class Diff extends Component {
66
67
  getSchema: PropTypes.func.isRequired,
67
68
  getHistory: PropTypes.func.isRequired,
68
69
  schema: PropTypes.objectOf(PropTypes.any),
70
+ error: PropTypes.objectOf(PropTypes.any),
69
71
  pathname: PropTypes.string.isRequired,
70
72
  one: PropTypes.string.isRequired,
71
73
  two: PropTypes.string.isRequired,
@@ -205,7 +207,9 @@ class Diff extends Component {
205
207
  }),
206
208
  );
207
209
 
208
- return (
210
+ return this.props.error?.status === 401 ? (
211
+ <Unauthorized />
212
+ ) : (
209
213
  <Container id="page-diff">
210
214
  <Helmet title={this.props.intl.formatMessage(messages.diff)} />
211
215
  <h1>
@@ -363,12 +367,13 @@ export default compose(
363
367
  data: state.diff.data,
364
368
  historyEntries: state.history.entries,
365
369
  schema: state.schema.schema,
370
+ error: state.diff.error,
366
371
  pathname: props.location.pathname,
367
372
  one: qs.parse(props.location.search).one,
368
373
  two: qs.parse(props.location.search).two,
369
374
  view: qs.parse(props.location.search).view || 'split',
370
- title: state.content.data.title,
371
- type: state.content.data['@type'],
375
+ title: state.content.data?.title,
376
+ type: state.content.data?.['@type'],
372
377
  }),
373
378
  { getDiff, getSchema, getHistory },
374
379
  ),
@@ -6,8 +6,8 @@ import {
6
6
  } from '@plone/volto/helpers';
7
7
 
8
8
  const BlockDataForm = compose(
9
- withVariationSchemaEnhancer,
10
9
  withStylingSchemaEnhancer,
10
+ withVariationSchemaEnhancer,
11
11
  )(InlineForm);
12
12
 
13
13
  export default BlockDataForm;
@@ -28,6 +28,7 @@ beforeAll(() => {
28
28
  },
29
29
  testBlock: {
30
30
  id: 'testBlock',
31
+ // enableStyling: true,
31
32
  variations: [
32
33
  {
33
34
  id: 'default',
@@ -143,4 +144,77 @@ describe('BlockDataForm', () => {
143
144
  // schema is cloned, not mutated in place
144
145
  expect(testSchema.fieldsets[0].fields).toStrictEqual([]);
145
146
  });
147
+
148
+ it('should add styling field to schema', () => {
149
+ config.blocks.blocksConfig.testBlock.enableStyling = true;
150
+ const store = mockStore({
151
+ intl: {
152
+ locale: 'en',
153
+ messages: {},
154
+ },
155
+ });
156
+ const testSchema = {
157
+ fieldsets: [{ title: 'Default', id: 'default', fields: [] }],
158
+ properties: {},
159
+ required: [],
160
+ };
161
+ const formData = {
162
+ '@type': 'testBlock',
163
+ };
164
+ const { container } = render(
165
+ <Provider store={store}>
166
+ <BlockDataForm
167
+ formData={formData}
168
+ schema={testSchema}
169
+ onChangeField={(id, value) => {}}
170
+ />
171
+ </Provider>,
172
+ );
173
+ expect(container).toMatchSnapshot();
174
+ });
175
+
176
+ it('should allow variations to enhance styling schema', () => {
177
+ config.blocks.blocksConfig.testBlock.enableStyling = true;
178
+ let finalSchema; // the schema is cloned during enhancing; we need it for tests because the ObjectWidget is not rendered properly in the test
179
+ config.blocks.blocksConfig.testBlock.variations[0].schemaEnhancer = ({
180
+ schema,
181
+ }) => {
182
+ const stylesSchema = schema.properties.styles.schema;
183
+ stylesSchema.properties.extraField = { title: 'Extra field' };
184
+ stylesSchema.fieldsets[0].fields.push('extraField');
185
+ finalSchema = schema;
186
+ return schema;
187
+ };
188
+
189
+ const store = mockStore({
190
+ intl: {
191
+ locale: 'en',
192
+ messages: {},
193
+ },
194
+ });
195
+ const testSchema = {
196
+ fieldsets: [{ title: 'Default', id: 'default', fields: [] }],
197
+ properties: {},
198
+ required: [],
199
+ };
200
+ const formData = {
201
+ '@type': 'testBlock',
202
+ };
203
+ const { container } = render(
204
+ <Provider store={store}>
205
+ <BlockDataForm
206
+ formData={formData}
207
+ schema={testSchema}
208
+ onChangeField={(id, value) => {}}
209
+ />
210
+ </Provider>,
211
+ );
212
+ expect(container).toMatchSnapshot();
213
+ expect(
214
+ finalSchema.properties.styles.schema.properties.extraField,
215
+ ).toStrictEqual({ title: 'Extra field' });
216
+ expect(
217
+ finalSchema.properties.styles.schema.fieldsets[0].fields,
218
+ ).toStrictEqual(['align', 'extraField']);
219
+ });
146
220
  });
@@ -74,6 +74,7 @@ class Form extends Component {
74
74
  onCancel: PropTypes.func,
75
75
  submitLabel: PropTypes.string,
76
76
  resetAfterSubmit: PropTypes.bool,
77
+ resetOnCancel: PropTypes.bool,
77
78
  isEditForm: PropTypes.bool,
78
79
  isAdminForm: PropTypes.bool,
79
80
  title: PropTypes.string,
@@ -105,6 +106,7 @@ class Form extends Component {
105
106
  onCancel: null,
106
107
  submitLabel: null,
107
108
  resetAfterSubmit: false,
109
+ resetOnCancel: false,
108
110
  isEditForm: false,
109
111
  isAdminForm: false,
110
112
  title: null,
@@ -399,7 +401,7 @@ class Form extends Component {
399
401
  if (event) {
400
402
  event.preventDefault();
401
403
  }
402
- if (this.props.resetAfterSubmit) {
404
+ if (this.props.resetOnCancel || this.props.resetAfterSubmit) {
403
405
  this.setState({
404
406
  formData: this.props.formData,
405
407
  });
@@ -18,5 +18,10 @@ export default {
18
18
  </div>
19
19
  ),
20
20
  ],
21
- argTypes: {},
21
+ argTypes: {
22
+ actions: {
23
+ control: 'check',
24
+ options: ['left', 'right', 'center', 'wide', 'full'],
25
+ },
26
+ },
22
27
  };
@@ -44,7 +44,7 @@ const NumberWidget = (props) => {
44
44
  disabled={isDisabled}
45
45
  min={minimum || null}
46
46
  max={maximum || null}
47
- value={value || defaultValue}
47
+ value={value ?? defaultValue}
48
48
  placeholder={placeholder}
49
49
  onChange={({ target }) =>
50
50
  onChange(id, target.value === '' ? undefined : target.value)
@@ -223,6 +223,7 @@ export const fetchContent = async ({ store, location }) => {
223
223
  location,
224
224
  id,
225
225
  data,
226
+ blocksConfig,
226
227
  });
227
228
  if (!p?.length) {
228
229
  throw new Error(
@@ -1,2 +1,5 @@
1
1
  export * from './withBlockSchemaEnhancer';
2
- export withBlockExtensions, { resolveExtension } from './withBlockExtensions';
2
+ export withBlockExtensions, {
3
+ resolveExtension,
4
+ resolveBlockExtensions,
5
+ } from './withBlockExtensions';
@@ -40,7 +40,9 @@ const isMinPropertyValid = (value, valueToCompare, maxCriterion, intlFunc) => {
40
40
  const widgetValidation = {
41
41
  email: {
42
42
  isValidEmail: (emailValue, emailObj, intlFunc) => {
43
- const emailRegex = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/;
43
+ // Email Regex taken from from WHATWG living standard:
44
+ // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type=email)
45
+ const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
44
46
  const isValid = emailRegex.test(emailValue);
45
47
  return !isValid ? intlFunc(messages.isValidEmail) : null;
46
48
  },
@@ -76,5 +76,16 @@ describe('FormValidation', () => {
76
76
  email: [messages.isValidEmail.defaultMessage],
77
77
  });
78
78
  });
79
+
80
+ it('validates correct email', () => {
81
+ formData.email = 'test@domain.name';
82
+ expect(
83
+ FormValidation.validateFieldsPerFieldset({
84
+ schema,
85
+ formData,
86
+ formatMessage,
87
+ }),
88
+ ).toEqual({});
89
+ });
79
90
  });
80
91
  });
@@ -93,6 +93,7 @@ export {
93
93
  withBlockExtensions,
94
94
  applySchemaEnhancer,
95
95
  resolveExtension,
96
+ resolveBlockExtensions,
96
97
  } from './Extensions';
97
98
  export { asyncConnect } from './AsyncConnect';
98
99
  export { userHasRoles } from './User/User';