@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.
Files changed (216) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/locales/ca/LC_MESSAGES/volto.po +12 -2
  3. package/locales/ca.json +1 -1
  4. package/locales/de/LC_MESSAGES/volto.po +12 -2
  5. package/locales/de.json +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +12 -2
  7. package/locales/en.json +1 -1
  8. package/locales/es/LC_MESSAGES/volto.po +12 -2
  9. package/locales/es.json +1 -1
  10. package/locales/eu/LC_MESSAGES/volto.po +12 -2
  11. package/locales/eu.json +1 -1
  12. package/locales/fr/LC_MESSAGES/volto.po +12 -2
  13. package/locales/fr.json +1 -1
  14. package/locales/it/LC_MESSAGES/volto.po +12 -2
  15. package/locales/it.json +1 -1
  16. package/locales/ja/LC_MESSAGES/volto.po +12 -2
  17. package/locales/ja.json +1 -1
  18. package/locales/nl/LC_MESSAGES/volto.po +12 -2
  19. package/locales/nl.json +1 -1
  20. package/locales/pt/LC_MESSAGES/volto.po +12 -2
  21. package/locales/pt.json +1 -1
  22. package/locales/pt_BR/LC_MESSAGES/volto.po +12 -2
  23. package/locales/pt_BR.json +1 -1
  24. package/locales/ro/LC_MESSAGES/volto.po +12 -2
  25. package/locales/ro.json +1 -1
  26. package/locales/volto.pot +12 -2
  27. package/package.json +2 -1
  28. package/public/icon.svg +13 -0
  29. package/src/actions/vocabularies/vocabularies.js +15 -3
  30. package/src/components/index.js +1 -0
  31. package/src/components/manage/Blocks/HeroImageLeft/Edit.jsx +1 -1
  32. package/src/components/manage/Blocks/Listing/getAsyncData.js +1 -1
  33. package/src/components/manage/Blocks/Text/Edit.jsx +19 -0
  34. package/src/components/manage/Form/Form.jsx +11 -1
  35. package/src/components/manage/Form/UndoToolbar.jsx +78 -0
  36. package/src/components/manage/Widgets/AlignWidget.stories.jsx +5 -21
  37. package/src/components/manage/Widgets/ArrayWidget.jsx +83 -101
  38. package/src/components/manage/Widgets/ArrayWidget.stories.jsx +29 -64
  39. package/src/components/manage/Widgets/CheckboxWidget.stories.jsx +5 -22
  40. package/src/components/manage/Widgets/DatetimeWidget.jsx +65 -74
  41. package/src/components/manage/Widgets/DatetimeWidget.stories.jsx +7 -23
  42. package/src/components/manage/Widgets/DatetimeWidget.test.jsx +17 -15
  43. package/src/components/manage/Widgets/EmailWidget.stories.jsx +5 -22
  44. package/src/components/manage/Widgets/FileWidget.stories.jsx +5 -22
  45. package/src/components/manage/Widgets/NumberWidget.stories.jsx +5 -23
  46. package/src/components/manage/Widgets/ObjectBrowserWidget.stories.js +24 -32
  47. package/src/components/manage/Widgets/ObjectListWidget.stories.js +44 -44
  48. package/src/components/manage/Widgets/ObjectWidget.stories.jsx +13 -28
  49. package/src/components/manage/Widgets/PasswordWidget.stories.jsx +5 -22
  50. package/src/components/manage/Widgets/QueryWidget.jsx +2 -2
  51. package/src/components/manage/Widgets/QueryWidget.stories.jsx +1637 -22
  52. package/src/components/manage/Widgets/SelectAutoComplete.jsx +79 -48
  53. package/src/components/manage/Widgets/SelectAutoComplete.test.jsx +16 -0
  54. package/src/components/manage/Widgets/SelectAutocompleteWidget.stories.jsx +161 -0
  55. package/src/components/manage/Widgets/SelectUtils.js +90 -30
  56. package/src/components/manage/Widgets/SelectUtils.test.jsx +76 -1
  57. package/src/components/manage/Widgets/SelectWidget.jsx +26 -37
  58. package/src/components/manage/Widgets/SelectWidget.stories.jsx +96 -28
  59. package/src/components/manage/Widgets/TextWidget.stories.jsx +5 -22
  60. package/src/components/manage/Widgets/TextareaWidget.stories.jsx +5 -22
  61. package/src/components/manage/Widgets/TokenWidget.jsx +19 -17
  62. package/src/components/manage/Widgets/TokenWidget.stories.jsx +141 -0
  63. package/src/components/manage/Widgets/UrlWidget.stories.jsx +5 -21
  64. package/src/components/manage/Widgets/VocabularyTermsWidget.stories.js +27 -64
  65. package/src/components/manage/Widgets/WysiwygWidget.stories.jsx +5 -22
  66. package/src/components/manage/Widgets/story.jsx +38 -0
  67. package/src/components/theme/ContactForm/ContactForm.jsx +1 -0
  68. package/src/components/theme/ContactForm/ContactForm.stories.jsx +126 -0
  69. package/src/components/theme/CorsError/CorsError.jsx +2 -2
  70. package/src/config/Loadables.jsx +2 -0
  71. package/src/config/index.js +1 -0
  72. package/src/helpers/Html/Html.jsx +2 -12
  73. package/src/helpers/UndoManager/useUndoManager.js +102 -0
  74. package/src/helpers/index.js +1 -0
  75. package/src/middleware/Api.test.js +57 -6
  76. package/src/middleware/api.js +34 -13
  77. package/src/reducers/vocabularies/vocabularies.js +13 -2
  78. package/src/store.js +1 -1
  79. package/src/storybook.jsx +55 -0
  80. package/theme/themes/pastanaga/extras/time-picker-overrides.less +1 -1
  81. package/include/python3.8/Python-ast.h +0 -715
  82. package/include/python3.8/Python.h +0 -160
  83. package/include/python3.8/abstract.h +0 -844
  84. package/include/python3.8/asdl.h +0 -46
  85. package/include/python3.8/ast.h +0 -37
  86. package/include/python3.8/bitset.h +0 -23
  87. package/include/python3.8/bltinmodule.h +0 -14
  88. package/include/python3.8/boolobject.h +0 -34
  89. package/include/python3.8/bytearrayobject.h +0 -62
  90. package/include/python3.8/bytes_methods.h +0 -69
  91. package/include/python3.8/bytesobject.h +0 -224
  92. package/include/python3.8/cellobject.h +0 -29
  93. package/include/python3.8/ceval.h +0 -231
  94. package/include/python3.8/classobject.h +0 -59
  95. package/include/python3.8/code.h +0 -180
  96. package/include/python3.8/codecs.h +0 -240
  97. package/include/python3.8/compile.h +0 -106
  98. package/include/python3.8/complexobject.h +0 -69
  99. package/include/python3.8/context.h +0 -84
  100. package/include/python3.8/cpython/abstract.h +0 -319
  101. package/include/python3.8/cpython/dictobject.h +0 -94
  102. package/include/python3.8/cpython/fileobject.h +0 -24
  103. package/include/python3.8/cpython/initconfig.h +0 -434
  104. package/include/python3.8/cpython/interpreteridobject.h +0 -19
  105. package/include/python3.8/cpython/object.h +0 -470
  106. package/include/python3.8/cpython/objimpl.h +0 -113
  107. package/include/python3.8/cpython/pyerrors.h +0 -188
  108. package/include/python3.8/cpython/pylifecycle.h +0 -78
  109. package/include/python3.8/cpython/pymem.h +0 -108
  110. package/include/python3.8/cpython/pystate.h +0 -252
  111. package/include/python3.8/cpython/sysmodule.h +0 -21
  112. package/include/python3.8/cpython/traceback.h +0 -22
  113. package/include/python3.8/cpython/tupleobject.h +0 -36
  114. package/include/python3.8/cpython/unicodeobject.h +0 -1239
  115. package/include/python3.8/datetime.h +0 -259
  116. package/include/python3.8/descrobject.h +0 -108
  117. package/include/python3.8/dictobject.h +0 -94
  118. package/include/python3.8/dtoa.h +0 -19
  119. package/include/python3.8/dynamic_annotations.h +0 -499
  120. package/include/python3.8/enumobject.h +0 -17
  121. package/include/python3.8/errcode.h +0 -38
  122. package/include/python3.8/eval.h +0 -37
  123. package/include/python3.8/fileobject.h +0 -49
  124. package/include/python3.8/fileutils.h +0 -185
  125. package/include/python3.8/floatobject.h +0 -130
  126. package/include/python3.8/frameobject.h +0 -92
  127. package/include/python3.8/funcobject.h +0 -104
  128. package/include/python3.8/genobject.h +0 -109
  129. package/include/python3.8/graminit.h +0 -94
  130. package/include/python3.8/grammar.h +0 -77
  131. package/include/python3.8/import.h +0 -149
  132. package/include/python3.8/internal/pycore_accu.h +0 -39
  133. package/include/python3.8/internal/pycore_atomic.h +0 -558
  134. package/include/python3.8/internal/pycore_ceval.h +0 -37
  135. package/include/python3.8/internal/pycore_code.h +0 -27
  136. package/include/python3.8/internal/pycore_condvar.h +0 -95
  137. package/include/python3.8/internal/pycore_context.h +0 -42
  138. package/include/python3.8/internal/pycore_fileutils.h +0 -54
  139. package/include/python3.8/internal/pycore_getopt.h +0 -22
  140. package/include/python3.8/internal/pycore_gil.h +0 -50
  141. package/include/python3.8/internal/pycore_hamt.h +0 -116
  142. package/include/python3.8/internal/pycore_initconfig.h +0 -166
  143. package/include/python3.8/internal/pycore_object.h +0 -81
  144. package/include/python3.8/internal/pycore_pathconfig.h +0 -75
  145. package/include/python3.8/internal/pycore_pyerrors.h +0 -62
  146. package/include/python3.8/internal/pycore_pyhash.h +0 -10
  147. package/include/python3.8/internal/pycore_pylifecycle.h +0 -118
  148. package/include/python3.8/internal/pycore_pymem.h +0 -212
  149. package/include/python3.8/internal/pycore_pystate.h +0 -326
  150. package/include/python3.8/internal/pycore_traceback.h +0 -96
  151. package/include/python3.8/internal/pycore_tupleobject.h +0 -19
  152. package/include/python3.8/internal/pycore_warnings.h +0 -25
  153. package/include/python3.8/interpreteridobject.h +0 -17
  154. package/include/python3.8/intrcheck.h +0 -33
  155. package/include/python3.8/iterobject.h +0 -25
  156. package/include/python3.8/listobject.h +0 -81
  157. package/include/python3.8/longintrepr.h +0 -99
  158. package/include/python3.8/longobject.h +0 -242
  159. package/include/python3.8/marshal.h +0 -28
  160. package/include/python3.8/memoryobject.h +0 -72
  161. package/include/python3.8/methodobject.h +0 -131
  162. package/include/python3.8/modsupport.h +0 -248
  163. package/include/python3.8/moduleobject.h +0 -90
  164. package/include/python3.8/namespaceobject.h +0 -19
  165. package/include/python3.8/node.h +0 -48
  166. package/include/python3.8/object.h +0 -753
  167. package/include/python3.8/objimpl.h +0 -284
  168. package/include/python3.8/odictobject.h +0 -43
  169. package/include/python3.8/opcode.h +0 -148
  170. package/include/python3.8/osdefs.h +0 -51
  171. package/include/python3.8/osmodule.h +0 -17
  172. package/include/python3.8/parsetok.h +0 -110
  173. package/include/python3.8/patchlevel.h +0 -35
  174. package/include/python3.8/picklebufobject.h +0 -31
  175. package/include/python3.8/py_curses.h +0 -100
  176. package/include/python3.8/pyarena.h +0 -64
  177. package/include/python3.8/pycapsule.h +0 -59
  178. package/include/python3.8/pyconfig.h +0 -1665
  179. package/include/python3.8/pyctype.h +0 -39
  180. package/include/python3.8/pydebug.h +0 -40
  181. package/include/python3.8/pydtrace.h +0 -59
  182. package/include/python3.8/pydtrace_probes.h +0 -228
  183. package/include/python3.8/pyerrors.h +0 -335
  184. package/include/python3.8/pyexpat.h +0 -55
  185. package/include/python3.8/pyfpe.h +0 -12
  186. package/include/python3.8/pyhash.h +0 -145
  187. package/include/python3.8/pylifecycle.h +0 -75
  188. package/include/python3.8/pymacconfig.h +0 -102
  189. package/include/python3.8/pymacro.h +0 -106
  190. package/include/python3.8/pymath.h +0 -230
  191. package/include/python3.8/pymem.h +0 -150
  192. package/include/python3.8/pyport.h +0 -850
  193. package/include/python3.8/pystate.h +0 -136
  194. package/include/python3.8/pystrcmp.h +0 -23
  195. package/include/python3.8/pystrhex.h +0 -22
  196. package/include/python3.8/pystrtod.h +0 -45
  197. package/include/python3.8/pythonrun.h +0 -210
  198. package/include/python3.8/pythread.h +0 -161
  199. package/include/python3.8/pytime.h +0 -246
  200. package/include/python3.8/rangeobject.h +0 -27
  201. package/include/python3.8/setobject.h +0 -108
  202. package/include/python3.8/sliceobject.h +0 -65
  203. package/include/python3.8/structmember.h +0 -74
  204. package/include/python3.8/structseq.h +0 -49
  205. package/include/python3.8/symtable.h +0 -123
  206. package/include/python3.8/sysmodule.h +0 -41
  207. package/include/python3.8/token.h +0 -92
  208. package/include/python3.8/traceback.h +0 -28
  209. package/include/python3.8/tracemalloc.h +0 -38
  210. package/include/python3.8/tupleobject.h +0 -48
  211. package/include/python3.8/typeslots.h +0 -85
  212. package/include/python3.8/ucnhash.h +0 -36
  213. package/include/python3.8/unicodeobject.h +0 -1044
  214. package/include/python3.8/warnings.h +0 -67
  215. package/include/python3.8/weakrefobject.h +0 -86
  216. package/src/components/theme/ContactForm/ContactForm.stories.mdx +0 -39
@@ -1,17 +1,19 @@
1
1
  import VocabularyTermsWidget from './VocabularyTermsWidget';
2
- import Wrapper from '@plone/volto/storybook';
3
2
  import React from 'react';
4
3
 
5
- const customStore = {
6
- userSession: { token: '1234' },
7
- intl: {
8
- locale: 'en',
9
- messages: {},
10
- },
11
- };
4
+ import WidgetStory from './story';
12
5
 
13
- const WrappedJSONField = (args) => {
14
- const [value, setValue] = React.useState({
6
+ export const JSONField = WidgetStory.bind({
7
+ props: { id: 'simplevocabulary', title: 'Vocabulary terms' },
8
+ widget: VocabularyTermsWidget,
9
+ customStore: {
10
+ userSession: { token: '1234' },
11
+ intl: {
12
+ locale: 'en',
13
+ messages: {},
14
+ },
15
+ },
16
+ initialValue: {
15
17
  items: [
16
18
  {
17
19
  token: 'talk',
@@ -30,60 +32,24 @@ const WrappedJSONField = (args) => {
30
32
  },
31
33
  },
32
34
  ],
33
- });
34
- const onChange = (block, value) => setValue(value);
35
-
36
- return (
37
- <Wrapper
38
- location={{ pathname: '/folder2/folder21/doc212' }}
39
- customStore={customStore}
40
- >
41
- <div className="ui segment form attached">
42
- <VocabularyTermsWidget
43
- {...args}
44
- id="simplevocabulary"
45
- title="Vocabulary terms"
46
- block="testBlock"
47
- value={value}
48
- onChange={onChange}
49
- />
50
- <pre>{JSON.stringify(value, null, 4)}</pre>
51
- </div>
52
- </Wrapper>
53
- );
54
- };
35
+ },
36
+ });
55
37
 
56
- const WrappedSimple = (args) => {
57
- const [value, setValue] = React.useState({
38
+ export const Simple = WidgetStory.bind({
39
+ props: { id: 'simplevocabulary', title: 'Vocabulary terms' },
40
+ widget: VocabularyTermsWidget,
41
+ customStore: {
42
+ userSession: { token: '1234' },
43
+ intl: {
44
+ locale: 'en',
45
+ messages: {},
46
+ },
47
+ },
48
+ initialValue: {
58
49
  '001': 'manual',
59
50
  '002': 'questions & answers',
60
- });
61
- const onChange = (block, value) => setValue(value);
62
-
63
- return (
64
- <Wrapper
65
- location={{ pathname: '/folder2/folder21/doc212' }}
66
- customStore={customStore}
67
- >
68
- <div className="ui segment form attached">
69
- <VocabularyTermsWidget
70
- {...args}
71
- id="Simple"
72
- title="Vocabulary terms"
73
- block="testBlock"
74
- value={value}
75
- value_type={{
76
- schema: {
77
- type: 'string',
78
- },
79
- }}
80
- onChange={onChange}
81
- />
82
- <pre>{JSON.stringify(value, null, 4)}</pre>
83
- </div>
84
- </Wrapper>
85
- );
86
- };
51
+ },
52
+ });
87
53
 
88
54
  export default {
89
55
  title: 'Widgets/Vocabulary',
@@ -96,6 +62,3 @@ export default {
96
62
  ),
97
63
  ],
98
64
  };
99
-
100
- export const JSONField = () => <WrappedJSONField />;
101
- export const Simple = () => <WrappedSimple />;
@@ -1,28 +1,11 @@
1
1
  import React from 'react';
2
2
  import WysiwygWidget from './WysiwygWidget';
3
- import Wrapper from '@plone/volto/storybook';
3
+ import WidgetStory from './story';
4
4
 
5
- const WysiwygWidgetComponent = ({ children, ...args }) => {
6
- const [value, setValue] = React.useState('');
7
- const onChange = (block, value) => setValue(value);
8
- return (
9
- <Wrapper location={{ pathname: '/folder2/folder21/doc212' }}>
10
- <div className="ui segment form attached" style={{ width: '400px' }}>
11
- <WysiwygWidget
12
- {...args}
13
- id="field"
14
- title="Wysiwyg"
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 Wysiwyg = WysiwygWidgetComponent.bind({});
5
+ export const Wysiwyg = WidgetStory.bind({
6
+ props: { id: 'text', title: 'Rich text' },
7
+ widget: WysiwygWidget,
8
+ });
26
9
 
27
10
  export default {
28
11
  title: 'Widgets/Wysiwyg',
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import {
3
+ RealStoreWrapper as Wrapper,
4
+ FormUndoWrapper,
5
+ } from '@plone/volto/storybook';
6
+
7
+ export default function StoryComponent({ children, ...args }) {
8
+ const Widget = this.widget;
9
+ const props = this.props || {};
10
+ return (
11
+ <Wrapper
12
+ location={{ pathname: '/folder2/folder21/doc212' }}
13
+ customStore={this.customStore}
14
+ >
15
+ <FormUndoWrapper
16
+ initialState={{
17
+ value: args.value || args.initialValue || this.initialValue,
18
+ }}
19
+ showControls={this.showUndoControls ?? true}
20
+ >
21
+ {({ state, onChange }) => (
22
+ <div className="ui segment form attached" style={{ width: '400px' }}>
23
+ <Widget
24
+ id="field"
25
+ title="Field"
26
+ block="block"
27
+ {...args}
28
+ {...props}
29
+ value={state.value}
30
+ onChange={(block, value) => onChange({ value })}
31
+ />
32
+ <pre>Value: {JSON.stringify(state.value, null, 4)}</pre>
33
+ </div>
34
+ )}
35
+ </FormUndoWrapper>
36
+ </Wrapper>
37
+ );
38
+ }
@@ -221,6 +221,7 @@ export class ContactFormComponent extends Component {
221
221
  <Portal node={document.getElementById('toolbar')}>
222
222
  <Toolbar
223
223
  pathname={this.props.pathname}
224
+ hideDefaultViewButtons
224
225
  inner={
225
226
  <Link
226
227
  to={`${getBaseUrl(this.props.pathname)}`}
@@ -0,0 +1,126 @@
1
+ import React from 'react';
2
+ import { ContactFormComponent } from './ContactForm';
3
+ import { injectIntl } from 'react-intl';
4
+ import { RealStoreWrapper as Wrapper } from '@plone/volto/storybook';
5
+
6
+ const IntlContactFormComponent = injectIntl(ContactFormComponent);
7
+
8
+ const actions = {
9
+ actions: {
10
+ error: null,
11
+ actions: {
12
+ document_actions: [],
13
+ object: [
14
+ {
15
+ icon: '',
16
+ id: 'view',
17
+ title: 'View',
18
+ },
19
+ {
20
+ icon: '',
21
+ id: 'edit',
22
+ title: 'Edit',
23
+ },
24
+ {
25
+ icon: '',
26
+ id: 'folderContents',
27
+ title: 'Contents',
28
+ },
29
+ {
30
+ icon: '',
31
+ id: 'history',
32
+ title: 'History',
33
+ },
34
+ {
35
+ icon: '',
36
+ id: 'local_roles',
37
+ title: 'Sharing',
38
+ },
39
+ ],
40
+ object_buttons: [],
41
+ portal_tabs: [
42
+ {
43
+ icon: '',
44
+ id: 'index_html',
45
+ title: 'Home',
46
+ },
47
+ ],
48
+ site_actions: [
49
+ {
50
+ icon: '',
51
+ id: 'sitemap',
52
+ title: 'Site Map',
53
+ },
54
+ {
55
+ icon: '',
56
+ id: 'accessibility',
57
+ title: 'Accessibility',
58
+ },
59
+ {
60
+ icon: '',
61
+ id: 'contact',
62
+ title: 'Contact',
63
+ },
64
+ ],
65
+ user: [
66
+ {
67
+ icon: '',
68
+ id: 'preferences',
69
+ title: 'Preferences',
70
+ },
71
+ {
72
+ icon: '',
73
+ id: 'dashboard',
74
+ title: 'Dashboard',
75
+ },
76
+ {
77
+ icon: '',
78
+ id: 'plone_setup',
79
+ title: 'Site Setup',
80
+ },
81
+ {
82
+ icon: '',
83
+ id: 'logout',
84
+ title: 'Log out',
85
+ },
86
+ ],
87
+ },
88
+ loaded: true,
89
+ loading: false,
90
+ },
91
+ };
92
+
93
+ function StoryComponent(props) {
94
+ return (
95
+ <Wrapper customStore={{ actions, unlockRequest: {} }}>
96
+ <div id="toolbar" style={{ display: 'none' }} />
97
+ <IntlContactFormComponent
98
+ {...props}
99
+ pathname="/contact"
100
+ error={props.error ? { message: props.error } : null}
101
+ />
102
+ </Wrapper>
103
+ );
104
+ }
105
+
106
+ export const ContactForm = StoryComponent.bind({});
107
+ ContactForm.args = {
108
+ error: { message: 'Something' },
109
+ loading: false,
110
+ loaded: true,
111
+ };
112
+
113
+ export default {
114
+ title: 'Public components/Contact Form',
115
+ component: ContactFormComponent,
116
+ decorators: [
117
+ (Story) => (
118
+ <div className="ui segment form attached" style={{ width: '400px' }}>
119
+ <Story />
120
+ </div>
121
+ ),
122
+ ],
123
+ argTypes: {
124
+ error: { control: 'text' },
125
+ },
126
+ };
@@ -61,8 +61,8 @@ const CorsError = () => (
61
61
  }}
62
62
  >
63
63
  <FormattedMessage
64
- id="The backend server of your website is not anwering, we apologize for the inconvenience. Please try to re-load the page and try again. If the problem persists please contact the site administrators."
65
- defaultMessage="The backend server of your website is not anwering, we apologize for the inconvenience. Please try to re-load the page and try again. If the problem persists please contact the site administrators."
64
+ id="The backend server of your website is not answering, we apologize for the inconvenience. Please try to re-load the page and try again. If the problem persists please contact the site administrators."
65
+ defaultMessage="The backend server of your website is not answering, we apologize for the inconvenience. Please try to re-load the page and try again. If the problem persists please contact the site administrators."
66
66
  />
67
67
  </p>
68
68
 
@@ -27,4 +27,6 @@ export const loadables = {
27
27
  },
28
28
  ),
29
29
  diffLib: loadable.lib(() => import('diff')),
30
+ moment: loadable.lib(() => import('moment')),
31
+ reactDates: loadable.lib(() => import('react-dates')),
30
32
  };
@@ -157,6 +157,7 @@ let config = {
157
157
  showSelfRegistration: false,
158
158
  contentMetadataTagsImageField: 'image',
159
159
  hasWorkingCopySupport: false,
160
+ maxUndoLevels: 200, // undo history size for the main form
160
161
  },
161
162
  widgets: {
162
163
  ...widgetMapping,
@@ -123,23 +123,13 @@ class Html extends Component {
123
123
  }}
124
124
  />
125
125
 
126
+ <link rel="icon" href="/favicon.ico" sizes="any" />
127
+ <link rel="icon" href="/icon.svg" type="image/svg+xml" />
126
128
  <link
127
129
  rel="apple-touch-icon"
128
130
  sizes="180x180"
129
131
  href="/apple-touch-icon.png"
130
132
  />
131
- <link
132
- rel="icon"
133
- type="image/png"
134
- sizes="32x32"
135
- href="/favicon-32x32.png"
136
- />
137
- <link
138
- rel="icon"
139
- type="image/png"
140
- sizes="16x16"
141
- href="/favicon-16x16.png"
142
- />
143
133
  <link rel="manifest" href="/site.webmanifest" />
144
134
  <meta name="generator" content="Plone 6 - https://plone.org" />
145
135
  <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+ import Undoo from 'undoo';
3
+
4
+ // Code based on Apache-2.0 License
5
+ // https://github.com/reaviz/reaflow/blob/78d60aa04f514947a17097c906efdbbd6bae5080/src/helpers/useUndo.ts
6
+
7
+ const useUndoManager = (
8
+ state,
9
+ onUndoRedo,
10
+ { maxUndoLevels, enableHotKeys = true },
11
+ ) => {
12
+ const [canUndo, setCanUndo] = React.useState(false);
13
+ const [canRedo, setCanRedo] = React.useState(false);
14
+ const manager = React.useRef(new Undoo({ maxLength: maxUndoLevels }));
15
+ // Reference:
16
+ // https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback
17
+ const callbackRef = React.useRef(onUndoRedo);
18
+ React.useEffect(() => {
19
+ callbackRef.current = onUndoRedo;
20
+ }, [onUndoRedo]);
21
+
22
+ const doUndo = React.useCallback(() => {
23
+ manager.current.undo(({ state }) => {
24
+ const nextUndo = manager.current.canUndo();
25
+ const nextRedo = manager.current.canRedo();
26
+ setCanUndo(nextUndo);
27
+ setCanRedo(nextRedo);
28
+
29
+ callbackRef.current({
30
+ state,
31
+ type: 'undo',
32
+ canUndo: nextUndo,
33
+ canRedo: nextRedo,
34
+ });
35
+ });
36
+ }, []);
37
+
38
+ React.useEffect(() => {
39
+ manager.current.save({
40
+ state,
41
+ });
42
+
43
+ setCanUndo(manager.current.canUndo());
44
+ setCanRedo(manager.current.canRedo());
45
+ }, [state]);
46
+
47
+ const doRedo = React.useCallback(() => {
48
+ manager.current.redo(({ state }) => {
49
+ const nextUndo = manager.current.canUndo();
50
+ const nextRedo = manager.current.canRedo();
51
+ setCanUndo(nextUndo);
52
+ setCanRedo(nextRedo);
53
+
54
+ callbackRef.current({
55
+ state,
56
+ type: 'redo',
57
+ canUndo: nextUndo,
58
+ canRedo: nextRedo,
59
+ });
60
+ });
61
+ }, []);
62
+
63
+ const handleKeys = React.useCallback(
64
+ (event) => {
65
+ const keyName = event.key;
66
+
67
+ if (keyName === 'Control' || keyName === 'Meta') {
68
+ // do not alert when only Control key is pressed.
69
+ return;
70
+ }
71
+ if (event.ctrlKey || event.metaKey) {
72
+ if (keyName === 'z') {
73
+ event.preventDefault();
74
+ event.stopPropagation();
75
+ doUndo();
76
+ } else if (keyName === 'y') {
77
+ event.preventDefault();
78
+ event.stopPropagation();
79
+ doRedo();
80
+ }
81
+ } else {
82
+ return;
83
+ }
84
+ },
85
+ [doUndo, doRedo],
86
+ );
87
+
88
+ React.useEffect(() => {
89
+ if (!enableHotKeys) return;
90
+ document.addEventListener('keydown', handleKeys);
91
+ return () => document.removeEventListener('keydown', handleKeys);
92
+ }, [enableHotKeys, handleKeys]);
93
+
94
+ return {
95
+ doUndo,
96
+ doRedo,
97
+ canUndo,
98
+ canRedo,
99
+ };
100
+ };
101
+
102
+ export default useUndoManager;
@@ -89,3 +89,4 @@ export { userHasRoles } from './User/User';
89
89
  export { useDetectClickOutside } from './Utils/useDetectClickOutside';
90
90
  export { usePrevious } from './Utils/usePrevious';
91
91
  export { usePagination } from './Utils/usePagination';
92
+ export useUndoManager from './UndoManager/useUndoManager';
@@ -14,7 +14,7 @@ describe('api middleware helpers', () => {
14
14
  const result = addExpandersToPath('/de/mypage', GET_CONTENT);
15
15
  expect(result).toEqual('/de/mypage?expand=mycustomexpander');
16
16
  });
17
- it('addExpandersToPath with expanders already present (multilingual)', () => {
17
+ it('addExpandersToPath - Custom expander from settings, with expander (translation) already present (multilingual) in query', () => {
18
18
  config.settings.apiExpanders = [
19
19
  {
20
20
  match: '/',
@@ -28,7 +28,7 @@ describe('api middleware helpers', () => {
28
28
  );
29
29
  expect(result).toEqual('/de/mypage?expand=translations,mycustomexpander');
30
30
  });
31
- it('addExpandersToPath not matching', () => {
31
+ it('addExpandersToPath - Path not matching', () => {
32
32
  config.settings.apiExpanders = [
33
33
  {
34
34
  match: '/de/otherpath',
@@ -39,7 +39,7 @@ describe('api middleware helpers', () => {
39
39
  const result = addExpandersToPath('/de/mypage', GET_CONTENT);
40
40
  expect(result).toEqual('/de/mypage');
41
41
  });
42
- it('addExpandersToPath not matching, preserve query', () => {
42
+ it('addExpandersToPath - Path not matching, preserve query', () => {
43
43
  config.settings.apiExpanders = [
44
44
  {
45
45
  match: '/de/otherpath',
@@ -53,7 +53,7 @@ describe('api middleware helpers', () => {
53
53
  );
54
54
  expect(result).toEqual('/de/mypage/@navigation?expand.navigation.depth=3');
55
55
  });
56
- it('addExpandersToPath should work as expected, several', () => {
56
+ it('addExpandersToPath - Two custom expanders from settings', () => {
57
57
  config.settings.apiExpanders = [
58
58
  {
59
59
  match: '/',
@@ -67,7 +67,7 @@ describe('api middleware helpers', () => {
67
67
  '/de/mypage?expand=mycustomexpander,mycustomexpander2',
68
68
  );
69
69
  });
70
- it('addExpandersToPath should work as expected, already query present', () => {
70
+ it('addExpandersToPath - Two custom expanders from settings, expansion nested (with dots notation) present', () => {
71
71
  config.settings.apiExpanders = [
72
72
  {
73
73
  match: '/',
@@ -84,7 +84,7 @@ describe('api middleware helpers', () => {
84
84
  '/de/mypage/@navigation?expand=mycustomexpander,mycustomexpander2&expand.navigation.depth=3',
85
85
  );
86
86
  });
87
- it('addExpandersToPath should work as expected, already query present', () => {
87
+ it('addExpandersToPath - Two custom expanders from settings, query present ', () => {
88
88
  config.settings.apiExpanders = [
89
89
  {
90
90
  match: '/',
@@ -101,4 +101,55 @@ describe('api middleware helpers', () => {
101
101
  '/de/mypage/@navigation?expand=mycustomexpander,mycustomexpander2&expand.navigation.depth=3&someotherquery=1',
102
102
  );
103
103
  });
104
+ it('addExpandersToPath - Two custom expanders from settings, a list parameter present', () => {
105
+ config.settings.apiExpanders = [
106
+ {
107
+ match: '/',
108
+ GET_CONTENT: ['mycustomexpander', 'mycustomexpander2'],
109
+ },
110
+ ];
111
+
112
+ const result = addExpandersToPath(
113
+ '/de/mypage/@navigation?expand.navigation.depth=3&someotherquery=1&someotherquery=2',
114
+ GET_CONTENT,
115
+ );
116
+ // No need to stringify
117
+ expect(result).toEqual(
118
+ '/de/mypage/@navigation?expand=mycustomexpander,mycustomexpander2&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
119
+ );
120
+ });
121
+ it('addExpandersToPath - Two custom expanders from settings, a list parameter present, and a translation expand already in query', () => {
122
+ config.settings.apiExpanders = [
123
+ {
124
+ match: '/',
125
+ GET_CONTENT: ['mycustomexpander', 'mycustomexpander2'],
126
+ },
127
+ ];
128
+
129
+ const result = addExpandersToPath(
130
+ '/de/mypage/@navigation?expand=translations&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
131
+ GET_CONTENT,
132
+ );
133
+ // No need to stringify
134
+ expect(result).toEqual(
135
+ '/de/mypage/@navigation?expand=translations,mycustomexpander,mycustomexpander2&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
136
+ );
137
+ });
138
+ it('addExpandersToPath - Two custom expanders from settings, a list parameter present, and a two expand present already in query', () => {
139
+ config.settings.apiExpanders = [
140
+ {
141
+ match: '/',
142
+ GET_CONTENT: ['mycustomexpander', 'mycustomexpander2'],
143
+ },
144
+ ];
145
+
146
+ const result = addExpandersToPath(
147
+ '/de/mypage/@navigation?expand=translations,secondexpand&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
148
+ GET_CONTENT,
149
+ );
150
+ // No need to stringify
151
+ expect(result).toEqual(
152
+ '/de/mypage/@navigation?expand=translations,secondexpand,mycustomexpander,mycustomexpander2&expand.navigation.depth=3&someotherquery=1&someotherquery=2',
153
+ );
154
+ });
104
155
  });
@@ -22,6 +22,13 @@ let socket = null;
22
22
  /**
23
23
  *
24
24
  * Add configured expanders to an api call for an action
25
+ * Requirements:
26
+ *
27
+ * - It should add the expanders set in the config settings
28
+ * - It should preserve any query if present
29
+ * - It should preserve (and add) any expand parameter (if present) e.g. translations
30
+ * - It should take use the correct codification for arrays in querystring (repeated parameter for each member of the array)
31
+ *
25
32
  * @function addExpandersToPath
26
33
  * @param {string} path The url/path including the querystring
27
34
  * @param {*} type The action type
@@ -31,24 +38,38 @@ export function addExpandersToPath(path, type) {
31
38
  const { settings } = config;
32
39
  const { apiExpanders = [] } = settings;
33
40
 
34
- const pathPart = path.split('?')[0] || '';
35
- const expanders = apiExpanders
36
- .filter((expand) => matchPath(pathPart, expand.match) && expand[type])
41
+ const {
42
+ url,
43
+ query: { expand, ...query },
44
+ } = qs.parseUrl(path);
45
+
46
+ const expandersFromConfig = apiExpanders
47
+ .filter((expand) => matchPath(url, expand.match) && expand[type])
37
48
  .map((expand) => expand[type]);
38
- const query = qs.parse(qs.extract(path));
39
- const expand = compact(union([query.expand, ...flatten(expanders)]));
40
- if (expand) {
41
- query.expand = expand;
42
- }
49
+
50
+ const expandMerge = compact(union([expand, ...flatten(expandersFromConfig)]));
51
+
52
+ const stringifiedExpand = qs.stringify(
53
+ { expand: expandMerge },
54
+ {
55
+ arrayFormat: 'comma',
56
+ encode: false,
57
+ },
58
+ );
59
+
43
60
  const stringifiedQuery = qs.stringify(query, {
44
- arrayFormat: 'comma',
45
61
  encode: false,
46
62
  });
47
- if (!stringifiedQuery) {
48
- return pathPart;
49
- }
50
63
 
51
- return `${pathPart}?${stringifiedQuery}`;
64
+ if (stringifiedQuery && stringifiedExpand) {
65
+ return `${url}?${stringifiedExpand}&${stringifiedQuery}`;
66
+ } else if (!stringifiedQuery && stringifiedExpand) {
67
+ return `${url}?${stringifiedExpand}`;
68
+ } else if (stringifiedQuery && !stringifiedExpand) {
69
+ return `${url}?${stringifiedQuery}`;
70
+ } else {
71
+ return url;
72
+ }
52
73
  }
53
74
 
54
75
  /**
@@ -71,6 +71,7 @@ export default function vocabularies(state = initialState, action = {}) {
71
71
  [action.vocabulary]: {
72
72
  ...vocabState,
73
73
  subrequests: {
74
+ ...vocabState.subrequests,
74
75
  [action.subrequest]: {
75
76
  ...subrequestState,
76
77
  error: null,
@@ -141,8 +142,18 @@ export default function vocabularies(state = initialState, action = {}) {
141
142
  ...subrequestState,
142
143
  error: null,
143
144
  loaded: true,
144
- loading: !!(vocabState[action.subrequest].loading - 1),
145
- [action.token]: action.result.items[0].title,
145
+ loading: !!(subrequestState.loading - 1),
146
+ ...(action.token && {
147
+ [action.token]: action.result.items[0].title,
148
+ }),
149
+ ...(action.tokens && {
150
+ items: [
151
+ ...action.result.items.map((item) => ({
152
+ label: item.title,
153
+ value: item.token,
154
+ })),
155
+ ],
156
+ }),
146
157
  },
147
158
  },
148
159
  },