@plone/volto 14.0.0 → 14.1.1
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 +40 -0
- package/locales/ca/LC_MESSAGES/volto.po +12 -2
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +12 -2
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +12 -2
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +12 -2
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +12 -2
- package/locales/eu.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +12 -2
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +12 -2
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +12 -2
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +12 -2
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +12 -2
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +12 -2
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +12 -2
- package/locales/ro.json +1 -1
- package/locales/volto.pot +12 -2
- package/package.json +2 -1
- package/public/icon.svg +13 -0
- package/src/actions/vocabularies/vocabularies.js +15 -3
- package/src/components/index.js +1 -0
- package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +1 -1
- package/src/components/manage/Blocks/Listing/getAsyncData.js +1 -1
- package/src/components/manage/Blocks/Text/Edit.jsx +19 -0
- package/src/components/manage/Form/Form.jsx +11 -1
- package/src/components/manage/Form/UndoToolbar.jsx +78 -0
- package/src/components/manage/Widgets/AlignWidget.stories.jsx +5 -21
- package/src/components/manage/Widgets/ArrayWidget.jsx +83 -101
- package/src/components/manage/Widgets/ArrayWidget.stories.jsx +29 -64
- package/src/components/manage/Widgets/CheckboxWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/DatetimeWidget.jsx +65 -74
- package/src/components/manage/Widgets/DatetimeWidget.stories.jsx +7 -23
- package/src/components/manage/Widgets/DatetimeWidget.test.jsx +17 -15
- package/src/components/manage/Widgets/EmailWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/FileWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/NumberWidget.stories.jsx +5 -23
- package/src/components/manage/Widgets/ObjectBrowserWidget.stories.js +24 -32
- package/src/components/manage/Widgets/ObjectListWidget.stories.js +44 -44
- package/src/components/manage/Widgets/ObjectWidget.stories.jsx +13 -28
- package/src/components/manage/Widgets/PasswordWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/QueryWidget.jsx +2 -2
- package/src/components/manage/Widgets/QueryWidget.stories.jsx +1637 -22
- package/src/components/manage/Widgets/SelectAutoComplete.jsx +79 -48
- package/src/components/manage/Widgets/SelectAutoComplete.test.jsx +16 -0
- package/src/components/manage/Widgets/SelectAutocompleteWidget.stories.jsx +161 -0
- package/src/components/manage/Widgets/SelectUtils.js +90 -30
- package/src/components/manage/Widgets/SelectUtils.test.jsx +76 -1
- package/src/components/manage/Widgets/SelectWidget.jsx +26 -37
- package/src/components/manage/Widgets/SelectWidget.stories.jsx +96 -28
- package/src/components/manage/Widgets/TextWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/TextareaWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/TokenWidget.jsx +19 -17
- package/src/components/manage/Widgets/TokenWidget.stories.jsx +141 -0
- package/src/components/manage/Widgets/UrlWidget.stories.jsx +5 -21
- package/src/components/manage/Widgets/VocabularyTermsWidget.stories.js +27 -64
- package/src/components/manage/Widgets/WysiwygWidget.stories.jsx +5 -22
- package/src/components/manage/Widgets/story.jsx +38 -0
- package/src/components/theme/ContactForm/ContactForm.jsx +1 -0
- package/src/components/theme/ContactForm/ContactForm.stories.jsx +126 -0
- package/src/components/theme/CorsError/CorsError.jsx +2 -2
- package/src/config/Loadables.jsx +2 -0
- package/src/config/index.js +1 -0
- package/src/helpers/Html/Html.jsx +2 -12
- package/src/helpers/UndoManager/useUndoManager.js +102 -0
- package/src/helpers/index.js +1 -0
- package/src/middleware/Api.test.js +57 -6
- package/src/middleware/api.js +34 -13
- package/src/reducers/vocabularies/vocabularies.js +13 -2
- package/src/store.js +1 -1
- package/src/storybook.jsx +55 -0
- package/theme/themes/pastanaga/extras/time-picker-overrides.less +1 -1
- package/include/python3.8/Python-ast.h +0 -715
- package/include/python3.8/Python.h +0 -160
- package/include/python3.8/abstract.h +0 -844
- package/include/python3.8/asdl.h +0 -46
- package/include/python3.8/ast.h +0 -37
- package/include/python3.8/bitset.h +0 -23
- package/include/python3.8/bltinmodule.h +0 -14
- package/include/python3.8/boolobject.h +0 -34
- package/include/python3.8/bytearrayobject.h +0 -62
- package/include/python3.8/bytes_methods.h +0 -69
- package/include/python3.8/bytesobject.h +0 -224
- package/include/python3.8/cellobject.h +0 -29
- package/include/python3.8/ceval.h +0 -231
- package/include/python3.8/classobject.h +0 -59
- package/include/python3.8/code.h +0 -180
- package/include/python3.8/codecs.h +0 -240
- package/include/python3.8/compile.h +0 -106
- package/include/python3.8/complexobject.h +0 -69
- package/include/python3.8/context.h +0 -84
- package/include/python3.8/cpython/abstract.h +0 -319
- package/include/python3.8/cpython/dictobject.h +0 -94
- package/include/python3.8/cpython/fileobject.h +0 -24
- package/include/python3.8/cpython/initconfig.h +0 -434
- package/include/python3.8/cpython/interpreteridobject.h +0 -19
- package/include/python3.8/cpython/object.h +0 -470
- package/include/python3.8/cpython/objimpl.h +0 -113
- package/include/python3.8/cpython/pyerrors.h +0 -188
- package/include/python3.8/cpython/pylifecycle.h +0 -78
- package/include/python3.8/cpython/pymem.h +0 -108
- package/include/python3.8/cpython/pystate.h +0 -252
- package/include/python3.8/cpython/sysmodule.h +0 -21
- package/include/python3.8/cpython/traceback.h +0 -22
- package/include/python3.8/cpython/tupleobject.h +0 -36
- package/include/python3.8/cpython/unicodeobject.h +0 -1239
- package/include/python3.8/datetime.h +0 -259
- package/include/python3.8/descrobject.h +0 -108
- package/include/python3.8/dictobject.h +0 -94
- package/include/python3.8/dtoa.h +0 -19
- package/include/python3.8/dynamic_annotations.h +0 -499
- package/include/python3.8/enumobject.h +0 -17
- package/include/python3.8/errcode.h +0 -38
- package/include/python3.8/eval.h +0 -37
- package/include/python3.8/fileobject.h +0 -49
- package/include/python3.8/fileutils.h +0 -185
- package/include/python3.8/floatobject.h +0 -130
- package/include/python3.8/frameobject.h +0 -92
- package/include/python3.8/funcobject.h +0 -104
- package/include/python3.8/genobject.h +0 -109
- package/include/python3.8/graminit.h +0 -94
- package/include/python3.8/grammar.h +0 -77
- package/include/python3.8/import.h +0 -149
- package/include/python3.8/internal/pycore_accu.h +0 -39
- package/include/python3.8/internal/pycore_atomic.h +0 -558
- package/include/python3.8/internal/pycore_ceval.h +0 -37
- package/include/python3.8/internal/pycore_code.h +0 -27
- package/include/python3.8/internal/pycore_condvar.h +0 -95
- package/include/python3.8/internal/pycore_context.h +0 -42
- package/include/python3.8/internal/pycore_fileutils.h +0 -54
- package/include/python3.8/internal/pycore_getopt.h +0 -22
- package/include/python3.8/internal/pycore_gil.h +0 -50
- package/include/python3.8/internal/pycore_hamt.h +0 -116
- package/include/python3.8/internal/pycore_initconfig.h +0 -166
- package/include/python3.8/internal/pycore_object.h +0 -81
- package/include/python3.8/internal/pycore_pathconfig.h +0 -75
- package/include/python3.8/internal/pycore_pyerrors.h +0 -62
- package/include/python3.8/internal/pycore_pyhash.h +0 -10
- package/include/python3.8/internal/pycore_pylifecycle.h +0 -118
- package/include/python3.8/internal/pycore_pymem.h +0 -212
- package/include/python3.8/internal/pycore_pystate.h +0 -326
- package/include/python3.8/internal/pycore_traceback.h +0 -96
- package/include/python3.8/internal/pycore_tupleobject.h +0 -19
- package/include/python3.8/internal/pycore_warnings.h +0 -25
- package/include/python3.8/interpreteridobject.h +0 -17
- package/include/python3.8/intrcheck.h +0 -33
- package/include/python3.8/iterobject.h +0 -25
- package/include/python3.8/listobject.h +0 -81
- package/include/python3.8/longintrepr.h +0 -99
- package/include/python3.8/longobject.h +0 -242
- package/include/python3.8/marshal.h +0 -28
- package/include/python3.8/memoryobject.h +0 -72
- package/include/python3.8/methodobject.h +0 -131
- package/include/python3.8/modsupport.h +0 -248
- package/include/python3.8/moduleobject.h +0 -90
- package/include/python3.8/namespaceobject.h +0 -19
- package/include/python3.8/node.h +0 -48
- package/include/python3.8/object.h +0 -753
- package/include/python3.8/objimpl.h +0 -284
- package/include/python3.8/odictobject.h +0 -43
- package/include/python3.8/opcode.h +0 -148
- package/include/python3.8/osdefs.h +0 -51
- package/include/python3.8/osmodule.h +0 -17
- package/include/python3.8/parsetok.h +0 -110
- package/include/python3.8/patchlevel.h +0 -35
- package/include/python3.8/picklebufobject.h +0 -31
- package/include/python3.8/py_curses.h +0 -100
- package/include/python3.8/pyarena.h +0 -64
- package/include/python3.8/pycapsule.h +0 -59
- package/include/python3.8/pyconfig.h +0 -1665
- package/include/python3.8/pyctype.h +0 -39
- package/include/python3.8/pydebug.h +0 -40
- package/include/python3.8/pydtrace.h +0 -59
- package/include/python3.8/pydtrace_probes.h +0 -228
- package/include/python3.8/pyerrors.h +0 -335
- package/include/python3.8/pyexpat.h +0 -55
- package/include/python3.8/pyfpe.h +0 -12
- package/include/python3.8/pyhash.h +0 -145
- package/include/python3.8/pylifecycle.h +0 -75
- package/include/python3.8/pymacconfig.h +0 -102
- package/include/python3.8/pymacro.h +0 -106
- package/include/python3.8/pymath.h +0 -230
- package/include/python3.8/pymem.h +0 -150
- package/include/python3.8/pyport.h +0 -850
- package/include/python3.8/pystate.h +0 -136
- package/include/python3.8/pystrcmp.h +0 -23
- package/include/python3.8/pystrhex.h +0 -22
- package/include/python3.8/pystrtod.h +0 -45
- package/include/python3.8/pythonrun.h +0 -210
- package/include/python3.8/pythread.h +0 -161
- package/include/python3.8/pytime.h +0 -246
- package/include/python3.8/rangeobject.h +0 -27
- package/include/python3.8/setobject.h +0 -108
- package/include/python3.8/sliceobject.h +0 -65
- package/include/python3.8/structmember.h +0 -74
- package/include/python3.8/structseq.h +0 -49
- package/include/python3.8/symtable.h +0 -123
- package/include/python3.8/sysmodule.h +0 -41
- package/include/python3.8/token.h +0 -92
- package/include/python3.8/traceback.h +0 -28
- package/include/python3.8/tracemalloc.h +0 -38
- package/include/python3.8/tupleobject.h +0 -48
- package/include/python3.8/typeslots.h +0 -85
- package/include/python3.8/ucnhash.h +0 -36
- package/include/python3.8/unicodeobject.h +0 -1044
- package/include/python3.8/warnings.h +0 -67
- package/include/python3.8/weakrefobject.h +0 -86
- package/src/components/theme/ContactForm/ContactForm.stories.mdx +0 -39
|
@@ -136,6 +136,25 @@ class Edit extends Component {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
componentDidUpdate(prevProps) {
|
|
140
|
+
if (
|
|
141
|
+
!isEqual(this.props.data, prevProps.data) &&
|
|
142
|
+
!isEqual(
|
|
143
|
+
convertToRaw(this.state.editorState.getCurrentContent()),
|
|
144
|
+
this.props.data.text,
|
|
145
|
+
)
|
|
146
|
+
) {
|
|
147
|
+
const editorState =
|
|
148
|
+
this.props.data && this.props.data.text
|
|
149
|
+
? EditorState.createWithContent(convertFromRaw(this.props.data.text))
|
|
150
|
+
: EditorState.createEmpty();
|
|
151
|
+
|
|
152
|
+
this.setState({
|
|
153
|
+
editorState: editorState,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
139
158
|
/**
|
|
140
159
|
* @param {*} nextProps
|
|
141
160
|
* @param {*} nextState
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
} from 'semantic-ui-react';
|
|
40
40
|
import { v4 as uuid } from 'uuid';
|
|
41
41
|
import { toast } from 'react-toastify';
|
|
42
|
-
import { BlocksToolbar } from '@plone/volto/components';
|
|
42
|
+
import { BlocksToolbar, UndoToolbar } from '@plone/volto/components';
|
|
43
43
|
import { setSidebarTab } from '@plone/volto/actions';
|
|
44
44
|
import { compose } from 'redux';
|
|
45
45
|
import config from '@plone/volto/registry';
|
|
@@ -221,6 +221,7 @@ class Form extends Component {
|
|
|
221
221
|
|
|
222
222
|
if (this.props.onChangeFormData) {
|
|
223
223
|
if (
|
|
224
|
+
// TODO: use fast-deep-equal
|
|
224
225
|
JSON.stringify(prevState?.formData) !==
|
|
225
226
|
JSON.stringify(this.state.formData)
|
|
226
227
|
) {
|
|
@@ -526,6 +527,15 @@ class Form extends Component {
|
|
|
526
527
|
}
|
|
527
528
|
onSelectBlock={this.onSelectBlock}
|
|
528
529
|
/>
|
|
530
|
+
<UndoToolbar
|
|
531
|
+
state={{
|
|
532
|
+
formData: this.state.formData,
|
|
533
|
+
selected: this.state.selected,
|
|
534
|
+
multiSelected: this.state.multiSelected,
|
|
535
|
+
}}
|
|
536
|
+
enableHotKeys
|
|
537
|
+
onUndoRedo={({ state }) => this.setState(state)}
|
|
538
|
+
/>
|
|
529
539
|
<BlocksForm
|
|
530
540
|
onChangeFormData={(newFormData) =>
|
|
531
541
|
this.setState({
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
+
import { Plug } from '@plone/volto/components/manage/Pluggable';
|
|
4
|
+
import { Icon } from '@plone/volto/components';
|
|
5
|
+
import { Button } from 'semantic-ui-react';
|
|
6
|
+
import { useUndoManager } from '@plone/volto/helpers';
|
|
7
|
+
import config from '@plone/volto/registry';
|
|
8
|
+
|
|
9
|
+
import undoSVG from '@plone/volto/icons/undo.svg';
|
|
10
|
+
import redoSVG from '@plone/volto/icons/redo.svg';
|
|
11
|
+
|
|
12
|
+
const messages = defineMessages({
|
|
13
|
+
undo: {
|
|
14
|
+
id: 'Undo',
|
|
15
|
+
defaultMessage: 'Undo',
|
|
16
|
+
},
|
|
17
|
+
redo: {
|
|
18
|
+
id: 'Redo',
|
|
19
|
+
defaultMessage: 'Redo',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const UndoToolbar = ({ state, onUndoRedo, maxUndoLevels, enableHotKeys }) => {
|
|
24
|
+
const intl = useIntl();
|
|
25
|
+
const undoLevels = maxUndoLevels ?? config.settings.maxUndoLevels;
|
|
26
|
+
const { doUndo, doRedo, canUndo, canRedo } = useUndoManager(
|
|
27
|
+
state,
|
|
28
|
+
onUndoRedo,
|
|
29
|
+
{
|
|
30
|
+
maxUndoLevels: undoLevels,
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<Plug
|
|
37
|
+
pluggable="main.toolbar.bottom"
|
|
38
|
+
id="undo-btn"
|
|
39
|
+
dependencies={[canUndo, canRedo]}
|
|
40
|
+
>
|
|
41
|
+
<Button
|
|
42
|
+
className="undo"
|
|
43
|
+
onClick={() => doUndo()}
|
|
44
|
+
aria-label={intl.formatMessage(messages.undo)}
|
|
45
|
+
disabled={!canUndo}
|
|
46
|
+
>
|
|
47
|
+
<Icon
|
|
48
|
+
name={undoSVG}
|
|
49
|
+
className="circled"
|
|
50
|
+
size="30px"
|
|
51
|
+
title={intl.formatMessage(messages.undo)}
|
|
52
|
+
/>
|
|
53
|
+
</Button>
|
|
54
|
+
</Plug>
|
|
55
|
+
<Plug
|
|
56
|
+
pluggable="main.toolbar.bottom"
|
|
57
|
+
id="redo-btn"
|
|
58
|
+
dependencies={[canUndo, canRedo]}
|
|
59
|
+
>
|
|
60
|
+
<Button
|
|
61
|
+
className="redo"
|
|
62
|
+
onClick={() => doRedo()}
|
|
63
|
+
aria-label={intl.formatMessage(messages.redo)}
|
|
64
|
+
disabled={!canRedo}
|
|
65
|
+
>
|
|
66
|
+
<Icon
|
|
67
|
+
name={redoSVG}
|
|
68
|
+
className="circled"
|
|
69
|
+
size="30px"
|
|
70
|
+
title={intl.formatMessage(messages.redo)}
|
|
71
|
+
/>
|
|
72
|
+
</Button>
|
|
73
|
+
</Plug>
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default UndoToolbar;
|
|
@@ -1,27 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import AlignWidget from './AlignWidget';
|
|
3
|
-
import
|
|
3
|
+
import WidgetStory from './story';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
10
|
-
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
11
|
-
<AlignWidget
|
|
12
|
-
{...args}
|
|
13
|
-
id="alignWidgetItem"
|
|
14
|
-
title="Align"
|
|
15
|
-
block="testBlock"
|
|
16
|
-
value={value}
|
|
17
|
-
onChange={onChange}
|
|
18
|
-
/>
|
|
19
|
-
</div>
|
|
20
|
-
</Wrapper>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const Align = AlignWidgetComponent.bind({});
|
|
5
|
+
export const Align = WidgetStory.bind({
|
|
6
|
+
props: { id: 'align', title: 'Align' },
|
|
7
|
+
widget: AlignWidget,
|
|
8
|
+
});
|
|
25
9
|
|
|
26
10
|
export default {
|
|
27
11
|
title: 'Widgets/Align',
|
|
@@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
|
|
|
9
9
|
import { compose } from 'redux';
|
|
10
10
|
import { connect } from 'react-redux';
|
|
11
11
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
12
|
-
import { find, isObject
|
|
12
|
+
import { find, isObject } from 'lodash';
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
getVocabFromHint,
|
|
@@ -56,10 +56,79 @@ function arrayMove(array, from, to) {
|
|
|
56
56
|
return slicedArray;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function normalizeArrayValue(choices, value) {
|
|
60
|
+
if (!value || !Array.isArray(value)) return [];
|
|
61
|
+
if (value.length === 0) return value;
|
|
62
|
+
|
|
63
|
+
if (typeof value[0] === 'string') {
|
|
64
|
+
// raw value like ['foo', 'bar']
|
|
65
|
+
return value.map((v) => {
|
|
66
|
+
return {
|
|
67
|
+
label: find(choices, (c) => c.value === v)?.label || v,
|
|
68
|
+
value: v,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
isObject(value[0]) &&
|
|
75
|
+
Object.keys(value[0]).includes('token') // Array of objects, w/ label+value
|
|
76
|
+
) {
|
|
77
|
+
return value
|
|
78
|
+
.map((v) => {
|
|
79
|
+
const item = find(choices, (c) => c.value === v.token);
|
|
80
|
+
return item
|
|
81
|
+
? {
|
|
82
|
+
label: item.label || item.title || item.token,
|
|
83
|
+
value: v.token,
|
|
84
|
+
}
|
|
85
|
+
: {
|
|
86
|
+
// avoid a crash if choices doesn't include this item
|
|
87
|
+
label: v.label,
|
|
88
|
+
value: v.token,
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
.filter((f) => !!f);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeChoices(choices) {
|
|
98
|
+
if (Array.isArray(choices) && choices.length && Array.isArray(choices[0])) {
|
|
99
|
+
return choices.map((option) => ({
|
|
100
|
+
value: option[0],
|
|
101
|
+
label:
|
|
102
|
+
// Fix "None" on the serializer, to remove when fixed in p.restapi
|
|
103
|
+
option[1] !== 'None' && option[1] ? option[1] : option[0],
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return choices;
|
|
108
|
+
}
|
|
109
|
+
|
|
59
110
|
/**
|
|
60
111
|
* ArrayWidget component class.
|
|
61
112
|
* @class ArrayWidget
|
|
62
113
|
* @extends Component
|
|
114
|
+
*
|
|
115
|
+
* A createable select array widget will be rendered if the named vocabulary is
|
|
116
|
+
* in the widget definition (hint) like:
|
|
117
|
+
*
|
|
118
|
+
* ```
|
|
119
|
+
* list_field_voc_unconstrained = schema.List(
|
|
120
|
+
* title=u"List field with values from vocabulary but not constrained to them.",
|
|
121
|
+
* description=u"zope.schema.List",
|
|
122
|
+
* value_type=schema.TextLine(),
|
|
123
|
+
* required=False,
|
|
124
|
+
* missing_value=[],
|
|
125
|
+
* )
|
|
126
|
+
* directives.widget(
|
|
127
|
+
* "list_field_voc_unconstrained",
|
|
128
|
+
* AjaxSelectFieldWidget,
|
|
129
|
+
* vocabulary="plone.app.vocabularies.PortalTypes",
|
|
130
|
+
* )
|
|
131
|
+
* ```
|
|
63
132
|
*/
|
|
64
133
|
class ArrayWidget extends Component {
|
|
65
134
|
/**
|
|
@@ -119,18 +188,6 @@ class ArrayWidget extends Component {
|
|
|
119
188
|
super(props);
|
|
120
189
|
|
|
121
190
|
this.handleChange = this.handleChange.bind(this);
|
|
122
|
-
|
|
123
|
-
this.state = {
|
|
124
|
-
selectedOption: this.props.vocabBaseUrl
|
|
125
|
-
? []
|
|
126
|
-
: props.value
|
|
127
|
-
? props.value.map((item) =>
|
|
128
|
-
isObject(item)
|
|
129
|
-
? { label: item.title || item.token, value: item.token }
|
|
130
|
-
: { label: item, value: item },
|
|
131
|
-
)
|
|
132
|
-
: [],
|
|
133
|
-
};
|
|
134
191
|
}
|
|
135
192
|
|
|
136
193
|
/**
|
|
@@ -150,64 +207,8 @@ class ArrayWidget extends Component {
|
|
|
150
207
|
subrequest: this.props.intl.locale,
|
|
151
208
|
});
|
|
152
209
|
}
|
|
153
|
-
this.setDefaultValues();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
componentDidUpdate() {
|
|
157
|
-
this.setDefaultValues();
|
|
158
210
|
}
|
|
159
211
|
|
|
160
|
-
normalizeArrayValue = (choices, value) => {
|
|
161
|
-
// Array of tokens (on add, and on change tab in Tab component)
|
|
162
|
-
if (
|
|
163
|
-
value &&
|
|
164
|
-
isArray(value) &&
|
|
165
|
-
value.length > 0 &&
|
|
166
|
-
typeof value[0] === 'string'
|
|
167
|
-
) {
|
|
168
|
-
return value.map((v) => {
|
|
169
|
-
return {
|
|
170
|
-
label: find(choices, (c) => c.value === v)?.label || v,
|
|
171
|
-
value: v,
|
|
172
|
-
};
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
// Array of objects, containing label,value
|
|
176
|
-
if (
|
|
177
|
-
value &&
|
|
178
|
-
isArray(value) &&
|
|
179
|
-
value.length > 0 &&
|
|
180
|
-
isObject(value[0]) &&
|
|
181
|
-
Object.keys(value[0]).includes('token')
|
|
182
|
-
) {
|
|
183
|
-
return value.map((v) => {
|
|
184
|
-
return {
|
|
185
|
-
label: find(choices, (c) => c.value === v.token).label,
|
|
186
|
-
value: v.token,
|
|
187
|
-
};
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
setDefaultValues = () => {
|
|
194
|
-
if (
|
|
195
|
-
(this.state.selectedOption || []).length === 0 &&
|
|
196
|
-
this.props.value &&
|
|
197
|
-
this.props.choices?.length > 0
|
|
198
|
-
) {
|
|
199
|
-
const normalizedValue = this.normalizeArrayValue(
|
|
200
|
-
this.props.choices,
|
|
201
|
-
this.props.value,
|
|
202
|
-
);
|
|
203
|
-
if (normalizedValue !== null) {
|
|
204
|
-
this.setState({
|
|
205
|
-
selectedOption: normalizedValue,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
212
|
/**
|
|
212
213
|
* Handle the field change, store it in the local state and back to simple
|
|
213
214
|
* array of tokens for correct serialization
|
|
@@ -216,75 +217,56 @@ class ArrayWidget extends Component {
|
|
|
216
217
|
* @returns {undefined}
|
|
217
218
|
*/
|
|
218
219
|
handleChange(selectedOption) {
|
|
219
|
-
this.setState({ selectedOption });
|
|
220
|
-
|
|
221
220
|
this.props.onChange(
|
|
222
221
|
this.props.id,
|
|
223
222
|
selectedOption ? selectedOption.map((item) => item.value) : null,
|
|
224
223
|
);
|
|
225
224
|
}
|
|
226
225
|
|
|
226
|
+
onSortEnd = (selectedOption, { oldIndex, newIndex }) => {
|
|
227
|
+
const newValue = arrayMove(selectedOption, oldIndex, newIndex);
|
|
228
|
+
|
|
229
|
+
this.handleChange(newValue);
|
|
230
|
+
};
|
|
231
|
+
|
|
227
232
|
/**
|
|
228
233
|
* Render method.
|
|
229
234
|
* @method render
|
|
230
235
|
* @returns {string} Markup for the component.
|
|
231
236
|
*/
|
|
232
237
|
render() {
|
|
233
|
-
const
|
|
238
|
+
const choices = normalizeChoices(this.props?.choices || []);
|
|
239
|
+
const selectedOption = normalizeArrayValue(choices, this.props.value);
|
|
240
|
+
|
|
234
241
|
const CreatableSelect = this.props.reactSelectCreateable.default;
|
|
235
242
|
const { SortableContainer } = this.props.reactSortableHOC;
|
|
236
243
|
const Select = this.props.reactSelect.default;
|
|
237
244
|
const SortableSelect =
|
|
238
|
-
// It will be only createable if the named vocabulary is in the widget definition
|
|
239
|
-
// (hint) like:
|
|
240
|
-
// list_field_voc_unconstrained = schema.List(
|
|
241
|
-
// title=u"List field with values from vocabulary but not constrained to them.",
|
|
242
|
-
// description=u"zope.schema.List",
|
|
243
|
-
// value_type=schema.TextLine(),
|
|
244
|
-
// required=False,
|
|
245
|
-
// missing_value=[],
|
|
246
|
-
// )
|
|
247
|
-
// directives.widget(
|
|
248
|
-
// "list_field_voc_unconstrained",
|
|
249
|
-
// AjaxSelectFieldWidget,
|
|
250
|
-
// vocabulary="plone.app.vocabularies.PortalTypes",
|
|
251
|
-
// )
|
|
252
245
|
this.props?.choices && !getVocabFromHint(this.props)
|
|
253
246
|
? SortableContainer(Select)
|
|
254
247
|
: SortableContainer(CreatableSelect);
|
|
255
248
|
|
|
256
|
-
const onSortEnd = ({ oldIndex, newIndex }) => {
|
|
257
|
-
const newValue = arrayMove(this.state.selectedOption, oldIndex, newIndex);
|
|
258
|
-
|
|
259
|
-
this.setState({ selectedOption: newValue });
|
|
260
|
-
};
|
|
261
|
-
|
|
262
249
|
return (
|
|
263
250
|
<FormFieldWrapper {...this.props}>
|
|
264
251
|
<SortableSelect
|
|
265
252
|
useDragHandle
|
|
266
253
|
// react-sortable-hoc props:
|
|
267
254
|
axis="xy"
|
|
268
|
-
onSortEnd={onSortEnd}
|
|
255
|
+
onSortEnd={this.onSortEnd}
|
|
269
256
|
distance={4}
|
|
270
257
|
// small fix for https://github.com/clauderic/react-sortable-hoc/pull/352:
|
|
271
258
|
getHelperDimensions={({ node }) => node.getBoundingClientRect()}
|
|
272
259
|
id={`field-${this.props.id}`}
|
|
273
260
|
key={this.props.id}
|
|
274
|
-
isDisabled={this.props.isDisabled}
|
|
261
|
+
isDisabled={this.props.disabled || this.props.isDisabled}
|
|
275
262
|
className="react-select-container"
|
|
276
263
|
classNamePrefix="react-select"
|
|
277
264
|
options={
|
|
278
265
|
this.props.vocabBaseUrl
|
|
279
|
-
?
|
|
266
|
+
? choices
|
|
280
267
|
: this.props.choices
|
|
281
268
|
? [
|
|
282
|
-
...
|
|
283
|
-
value: option[0],
|
|
284
|
-
label:
|
|
285
|
-
// Fix "None" on the serializer, to remove when fixed in p.restapi
|
|
286
|
-
option[1] !== 'None' && option[1] ? option[1] : option[0],
|
|
287
|
-
})),
|
|
269
|
+
...choices,
|
|
288
270
|
...(this.props.noValueOption && !this.props.default
|
|
289
271
|
? [
|
|
290
272
|
{
|
|
@@ -1,70 +1,47 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ArrayWidgetComponent } from './ArrayWidget';
|
|
3
|
-
import
|
|
4
|
-
import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
|
|
2
|
+
import ArrayWidget, { ArrayWidgetComponent } from './ArrayWidget';
|
|
3
|
+
import WidgetStory from './story';
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
]
|
|
5
|
+
const choices = [
|
|
6
|
+
['foo', 'Foo'],
|
|
7
|
+
['bar', 'Bar'],
|
|
8
|
+
['fooBar', 'FooBar'],
|
|
9
|
+
];
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const [value, setValue] = React.useState(args.value ?? '');
|
|
13
|
-
const onChange = (block, value) => {
|
|
14
|
-
// args.onChange({ value });
|
|
15
|
-
setValue(value);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<Wrapper>
|
|
20
|
-
<ArrayComponent {...args} onChange={onChange} value={value} />
|
|
21
|
-
</Wrapper>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const Default = Array.bind({});
|
|
11
|
+
export const Default = WidgetStory.bind({ widget: ArrayWidget });
|
|
26
12
|
Default.args = {
|
|
27
13
|
id: 'field-empty',
|
|
28
14
|
title: 'field 1 title',
|
|
29
15
|
description: 'Optional help text',
|
|
30
16
|
placeholder: 'Type something…',
|
|
31
|
-
choices
|
|
32
|
-
['Foo', 'Foo'],
|
|
33
|
-
['Bar', 'Bar'],
|
|
34
|
-
['FooBar', 'FooBar'],
|
|
35
|
-
],
|
|
17
|
+
choices,
|
|
36
18
|
};
|
|
37
19
|
|
|
38
|
-
export const Required =
|
|
20
|
+
export const Required = WidgetStory.bind({ widget: ArrayWidget });
|
|
39
21
|
Required.args = {
|
|
40
22
|
id: 'field-empty',
|
|
41
23
|
title: 'field 1 title',
|
|
42
24
|
description: 'Optional help text',
|
|
43
25
|
placeholder: 'Type something…',
|
|
44
|
-
choices
|
|
45
|
-
['Foo', 'Foo'],
|
|
46
|
-
['Bar', 'Bar'],
|
|
47
|
-
['FooBar', 'FooBar'],
|
|
48
|
-
],
|
|
26
|
+
choices,
|
|
49
27
|
required: true,
|
|
50
28
|
};
|
|
51
29
|
|
|
52
|
-
export const Filled =
|
|
30
|
+
export const Filled = WidgetStory.bind({
|
|
31
|
+
widget: ArrayWidget,
|
|
32
|
+
props: { value: ['foo'] },
|
|
33
|
+
});
|
|
53
34
|
Filled.args = {
|
|
54
35
|
id: 'field-filled',
|
|
55
36
|
title: 'Filled field title',
|
|
56
37
|
description: 'Optional help text',
|
|
57
|
-
choices
|
|
58
|
-
|
|
59
|
-
['Bar', 'Bar'],
|
|
60
|
-
['FooBar', 'FooBar'],
|
|
61
|
-
],
|
|
62
|
-
value: ['Foo'],
|
|
38
|
+
choices,
|
|
39
|
+
value: ['foo'],
|
|
63
40
|
placeholder: 'Type something…',
|
|
64
41
|
required: true,
|
|
65
42
|
};
|
|
66
43
|
|
|
67
|
-
export const Errored =
|
|
44
|
+
export const Errored = WidgetStory.bind({ widget: ArrayWidget });
|
|
68
45
|
Errored.args = {
|
|
69
46
|
id: 'field-errored',
|
|
70
47
|
title: 'Errored field title',
|
|
@@ -81,45 +58,33 @@ Errored.args = {
|
|
|
81
58
|
// required=False,
|
|
82
59
|
// default=None,
|
|
83
60
|
// )
|
|
84
|
-
choices
|
|
85
|
-
['Foo', 'Foo'],
|
|
86
|
-
['Bar', 'Bar'],
|
|
87
|
-
['FooBar', 'FooBar'],
|
|
88
|
-
],
|
|
61
|
+
choices,
|
|
89
62
|
value: ['Foo'],
|
|
90
63
|
error: ['This is the error'],
|
|
91
64
|
required: true,
|
|
92
65
|
};
|
|
93
66
|
|
|
94
|
-
export const NoPlaceholder =
|
|
67
|
+
export const NoPlaceholder = WidgetStory.bind({ widget: ArrayWidget });
|
|
95
68
|
NoPlaceholder.args = {
|
|
96
69
|
id: 'field-without-novalue',
|
|
97
70
|
title: 'Field title',
|
|
98
71
|
description: 'This field has no value option',
|
|
99
|
-
choices
|
|
100
|
-
['Foo', 'Foo'],
|
|
101
|
-
['Bar', 'Bar'],
|
|
102
|
-
['FooBar', 'FooBar'],
|
|
103
|
-
],
|
|
72
|
+
choices,
|
|
104
73
|
required: true,
|
|
105
74
|
};
|
|
106
75
|
|
|
107
|
-
export const WithoutNoValueOption =
|
|
76
|
+
export const WithoutNoValueOption = WidgetStory.bind({ widget: ArrayWidget });
|
|
108
77
|
WithoutNoValueOption.args = {
|
|
109
78
|
id: 'field-without-novalue',
|
|
110
79
|
title: 'Field title',
|
|
111
80
|
description: 'This field has no value option',
|
|
112
81
|
placeholder: 'something…',
|
|
113
|
-
choices
|
|
114
|
-
['Foo', 'Foo'],
|
|
115
|
-
['Bar', 'Bar'],
|
|
116
|
-
['FooBar', 'FooBar'],
|
|
117
|
-
],
|
|
82
|
+
choices,
|
|
118
83
|
required: true,
|
|
119
84
|
noValueOption: false,
|
|
120
85
|
};
|
|
121
86
|
|
|
122
|
-
export const VocabularyBased =
|
|
87
|
+
export const VocabularyBased = WidgetStory.bind({ widget: ArrayWidget });
|
|
123
88
|
VocabularyBased.args = {
|
|
124
89
|
id: 'field-vocab-based',
|
|
125
90
|
title: 'field title',
|
|
@@ -146,7 +111,7 @@ VocabularyBased.args = {
|
|
|
146
111
|
vocabBaseUrl: 'https://anapivocabularyURL',
|
|
147
112
|
};
|
|
148
113
|
|
|
149
|
-
export const Disabled =
|
|
114
|
+
export const Disabled = WidgetStory.bind({ widget: ArrayWidget });
|
|
150
115
|
Disabled.args = {
|
|
151
116
|
id: 'field-disabled',
|
|
152
117
|
title: 'Disabled field title',
|
|
@@ -157,12 +122,12 @@ Disabled.args = {
|
|
|
157
122
|
const getOptionsGenerator = (count) => {
|
|
158
123
|
const options = [];
|
|
159
124
|
for (let i = 0; i < count; i = i + 1) {
|
|
160
|
-
options.push([i, `Option ${i}`]);
|
|
125
|
+
options.push([i.toString(), `Option ${i}`]);
|
|
161
126
|
}
|
|
162
127
|
return options;
|
|
163
128
|
};
|
|
164
129
|
|
|
165
|
-
export const ManyOptions1000 =
|
|
130
|
+
export const ManyOptions1000 = WidgetStory.bind({ widget: ArrayWidget });
|
|
166
131
|
ManyOptions1000.args = {
|
|
167
132
|
id: 'field-empty',
|
|
168
133
|
title: 'field 1 title',
|
|
@@ -171,7 +136,7 @@ ManyOptions1000.args = {
|
|
|
171
136
|
choices: getOptionsGenerator(1000),
|
|
172
137
|
};
|
|
173
138
|
|
|
174
|
-
export const ManyOptions500 =
|
|
139
|
+
export const ManyOptions500 = WidgetStory.bind({ widget: ArrayWidget });
|
|
175
140
|
ManyOptions500.args = {
|
|
176
141
|
id: 'field-empty',
|
|
177
142
|
title: 'field 1 title',
|
|
@@ -182,7 +147,7 @@ ManyOptions500.args = {
|
|
|
182
147
|
|
|
183
148
|
export default {
|
|
184
149
|
title: 'Widgets/Array',
|
|
185
|
-
component:
|
|
150
|
+
component: ArrayWidgetComponent,
|
|
186
151
|
decorators: [
|
|
187
152
|
(Story) => (
|
|
188
153
|
<div style={{ width: '400px' }}>
|
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import CheckboxWidget from './CheckboxWidget';
|
|
3
|
-
import
|
|
3
|
+
import WidgetStory from './story';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
|
|
10
|
-
<div className="ui segment form attached" style={{ width: '400px' }}>
|
|
11
|
-
<CheckboxWidget
|
|
12
|
-
{...args}
|
|
13
|
-
id="field"
|
|
14
|
-
title="Checkbox"
|
|
15
|
-
block="testBlock"
|
|
16
|
-
value={value}
|
|
17
|
-
onChange={onChange}
|
|
18
|
-
/>
|
|
19
|
-
</div>
|
|
20
|
-
<pre>Value: {JSON.stringify(value, null, 4)}</pre>
|
|
21
|
-
</Wrapper>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const Checkbox = CheckboxWidgetComponent.bind({});
|
|
5
|
+
export const Checkbox = WidgetStory.bind({
|
|
6
|
+
props: { id: 'field', title: 'Checkbox', block: 'block' },
|
|
7
|
+
widget: CheckboxWidget,
|
|
8
|
+
});
|
|
26
9
|
|
|
27
10
|
export default {
|
|
28
11
|
title: 'Widgets/Checkbox',
|