@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.
- package/CHANGELOG.md +29 -0
- package/locales/ca/LC_MESSAGES/volto.po +1 -0
- package/locales/de/LC_MESSAGES/volto.po +1 -0
- package/locales/en/LC_MESSAGES/volto.po +1 -0
- package/locales/es/LC_MESSAGES/volto.po +1 -0
- package/locales/eu/LC_MESSAGES/volto.po +1 -0
- package/locales/fr/LC_MESSAGES/volto.po +1 -0
- package/locales/it/LC_MESSAGES/volto.po +1 -0
- package/locales/ja/LC_MESSAGES/volto.po +1 -0
- package/locales/nl/LC_MESSAGES/volto.po +1 -0
- package/locales/pt/LC_MESSAGES/volto.po +1 -0
- package/locales/pt_BR/LC_MESSAGES/volto.po +1 -0
- package/locales/ro/LC_MESSAGES/volto.po +1 -0
- package/locales/volto.pot +2 -1
- package/package.json +1 -1
- package/packages/volto-slate/src/editor/plugins/StyleMenu/StyleMenu.jsx +141 -0
- package/packages/volto-slate/src/editor/plugins/StyleMenu/index.js +19 -0
- package/packages/volto-slate/src/editor/plugins/StyleMenu/style.less +29 -0
- package/packages/volto-slate/src/editor/plugins/StyleMenu/utils.js +159 -0
- package/packages/volto-slate/src/editor/plugins/index.js +2 -0
- package/packages/volto-slate/src/index.js +1 -0
- package/packages/volto-slate/src/utils/blocks.js +1 -2
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +1 -1
- package/src/components/manage/Blocks/Listing/getAsyncData.js +5 -2
- package/src/components/manage/Controlpanels/Controlpanels.jsx +6 -2
- package/src/components/manage/Diff/Diff.jsx +8 -3
- package/src/components/manage/Form/BlockDataForm.jsx +1 -1
- package/src/components/manage/Form/BlockDataForm.test.jsx +74 -0
- package/src/components/manage/Form/Form.jsx +3 -1
- package/src/components/manage/Widgets/AlignWidget.stories.jsx +6 -1
- package/src/components/manage/Widgets/NumberWidget.jsx +1 -1
- package/src/components/theme/App/App.jsx +1 -0
- package/src/helpers/Extensions/index.js +4 -1
- package/src/helpers/FormValidation/FormValidation.js +3 -1
- package/src/helpers/FormValidation/FormValidation.test.js +11 -0
- 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-
|
|
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
|
@@ -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
|
}
|
|
@@ -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
|
-
|
|
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
|
-
...(
|
|
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:
|
|
179
|
-
title:
|
|
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
|
|
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
|
),
|
|
@@ -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
|
});
|
|
@@ -44,7 +44,7 @@ const NumberWidget = (props) => {
|
|
|
44
44
|
disabled={isDisabled}
|
|
45
45
|
min={minimum || null}
|
|
46
46
|
max={maximum || null}
|
|
47
|
-
value={value
|
|
47
|
+
value={value ?? defaultValue}
|
|
48
48
|
placeholder={placeholder}
|
|
49
49
|
onChange={({ target }) =>
|
|
50
50
|
onChange(id, target.value === '' ? undefined : target.value)
|
|
@@ -40,7 +40,9 @@ const isMinPropertyValid = (value, valueToCompare, maxCriterion, intlFunc) => {
|
|
|
40
40
|
const widgetValidation = {
|
|
41
41
|
email: {
|
|
42
42
|
isValidEmail: (emailValue, emailObj, intlFunc) => {
|
|
43
|
-
|
|
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
|
});
|
package/src/helpers/index.js
CHANGED