@plone/volto 19.0.0-alpha.36 → 19.0.0-alpha.38
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 +48 -0
- package/README.md +1 -1
- package/locales/af/LC_MESSAGES/volto.po +29 -3
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +29 -3
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +29 -3
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +29 -3
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +32 -6
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +30 -4
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +29 -3
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +29 -3
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +32 -6
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +29 -3
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +25 -0
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +29 -3
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +29 -3
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +29 -3
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +30 -5
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +29 -3
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +30 -5
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +29 -3
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +30 -4
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +208 -183
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +29 -3
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +27 -2
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +29 -3
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +34 -8
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +30 -4
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +29 -3
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +29 -3
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +29 -3
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +29 -4
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +29 -3
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +29 -3
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +29 -3
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +29 -3
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +30 -4
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +29 -3
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +29 -3
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +29 -3
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +29 -3
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +29 -3
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +69 -43
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +29 -3
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +30 -4
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +30 -4
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +54 -29
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +29 -3
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +30 -5
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +30 -4
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +30 -4
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +29 -3
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +29 -3
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +29 -3
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +30 -4
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +29 -3
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +29 -3
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +31 -5
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +30 -5
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +29 -3
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +29 -3
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +29 -3
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +29 -4
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +30 -4
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +29 -3
- package/locales/vi.json +1 -1
- package/locales/volto.pot +26 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +29 -4
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +29 -3
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +29 -3
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +9 -9
- package/src/components/manage/BlockChooser/BlockChooser.jsx +7 -10
- package/src/components/manage/Blocks/Block/Edit.jsx +19 -10
- package/src/components/manage/Blocks/Block/Order/Item.jsx +9 -4
- package/src/components/manage/Contents/DropZoneContent.jsx +1 -0
- package/src/components/manage/Controlpanels/BlockType.tsx +2 -3
- package/src/components/manage/Controlpanels/ContentTypes.jsx +9 -2
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +58 -5
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.jsx +624 -0
- package/src/components/manage/Controlpanels/Users/UsersControlpanel.test.jsx +8 -0
- package/src/components/manage/Form/Form.jsx +6 -1
- package/src/components/manage/Form/ModalForm.jsx +164 -88
- package/src/components/manage/Sidebar/ObjectBrowser.jsx +7 -0
- package/src/components/manage/Sidebar/ObjectBrowserBody.jsx +7 -3
- package/src/components/manage/Sidebar/ObjectBrowserBody.test.jsx +52 -0
- package/src/components/manage/Sidebar/Sidebar.jsx +2 -0
- package/src/components/manage/Toolbar/Toolbar.jsx +89 -7
- package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +1 -0
- package/src/components/manage/Widgets/TokenWidget.jsx +142 -186
- package/src/components/theme/Search/Search.jsx +230 -327
- package/src/components/theme/Search/Search.test.jsx +5 -10
- package/src/components/theme/Sitemap/Sitemap.jsx +22 -30
- package/src/components/theme/Sitemap/Sitemap.test.jsx +18 -0
- package/src/components/theme/Widgets/DateWidget.jsx +4 -5
- package/src/components/theme/Widgets/DatetimeWidget.jsx +4 -5
- package/src/components/theme/Widgets/RichTextWidget.jsx +1 -1
- package/src/config/index.js +1 -0
- package/src/helpers/I18n/I18n.test.ts +44 -0
- package/src/helpers/I18n/I18n.ts +31 -0
- package/src/helpers/Utils/Date.js +26 -1
- package/src/helpers/Utils/Date.test.js +237 -0
- package/src/helpers/index.js +1 -0
- package/theme/themes/pastanaga/collections/form.overrides +21 -0
- package/theme/themes/pastanaga/elements/button.overrides +30 -3
- package/types/components/manage/Controlpanels/Relations/RelationsMatrix.d.ts +1 -1
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.d.ts +2 -6
- package/types/components/manage/Controlpanels/Users/UsersControlpanel.ssr.test.d.ts +1 -0
- package/types/components/manage/Controlpanels/index.d.ts +1 -1
- package/types/components/manage/Multilingual/ManageTranslations.d.ts +1 -1
- package/types/components/manage/Sidebar/ObjectBrowser.d.ts +1 -1
- package/types/components/manage/Sidebar/ObjectBrowserBody.test.d.ts +1 -0
- package/types/components/manage/Widgets/ImageWidget.d.ts +1 -1
- package/types/components/manage/Widgets/InternalUrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/UrlWidget.d.ts +1 -1
- package/types/components/manage/Widgets/index.d.ts +2 -2
- package/types/components/theme/Search/Search.d.ts +1 -1
- package/types/helpers/I18n/I18n.d.ts +20 -0
- package/types/helpers/index.d.ts +1 -0
|
@@ -46,6 +46,14 @@ const messages = defineMessages({
|
|
|
46
46
|
id: 'Cancel',
|
|
47
47
|
defaultMessage: 'Cancel',
|
|
48
48
|
},
|
|
49
|
+
dialogOpened: {
|
|
50
|
+
id: 'Pop-up opened: {title}',
|
|
51
|
+
defaultMessage: 'Pop-up opened: {title}',
|
|
52
|
+
},
|
|
53
|
+
dialogClosed: {
|
|
54
|
+
id: 'Pop-up closed.',
|
|
55
|
+
defaultMessage: 'Pop-up closed.',
|
|
56
|
+
},
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
/**
|
|
@@ -54,6 +62,7 @@ const messages = defineMessages({
|
|
|
54
62
|
* @extends Component
|
|
55
63
|
*/
|
|
56
64
|
class ModalForm extends Component {
|
|
65
|
+
static idCounter = 0;
|
|
57
66
|
/**
|
|
58
67
|
* Property types.
|
|
59
68
|
* @property {Object} propTypes Property types.
|
|
@@ -110,17 +119,21 @@ class ModalForm extends Component {
|
|
|
110
119
|
*/
|
|
111
120
|
constructor(props) {
|
|
112
121
|
super(props);
|
|
122
|
+
this.headerId = `modal-title-${++ModalForm.idCounter}`;
|
|
113
123
|
this.state = {
|
|
114
124
|
currentTab: 0,
|
|
115
125
|
errors: {},
|
|
116
126
|
isFormPristine: true,
|
|
117
127
|
formData: props.formData,
|
|
118
128
|
};
|
|
129
|
+
this.modalRef = React.createRef();
|
|
130
|
+
this.announceRef = React.createRef();
|
|
119
131
|
this.selectTab = this.selectTab.bind(this);
|
|
120
132
|
this.onChangeField = this.onChangeField.bind(this);
|
|
121
133
|
this.onBlurField = this.onBlurField.bind(this);
|
|
122
134
|
this.onClickInput = this.onClickInput.bind(this);
|
|
123
135
|
this.onSubmit = this.onSubmit.bind(this);
|
|
136
|
+
this.onKeyDown = this.onKeyDown.bind(this);
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
/**
|
|
@@ -209,12 +222,57 @@ class ModalForm extends Component {
|
|
|
209
222
|
});
|
|
210
223
|
}
|
|
211
224
|
|
|
225
|
+
onKeyDown(event) {
|
|
226
|
+
if (event.key !== 'Tab') return;
|
|
227
|
+
const modal = document.getElementById(this.headerId)?.closest('.ui.modal');
|
|
228
|
+
if (!modal) return;
|
|
229
|
+
const focusable = modal.querySelectorAll(
|
|
230
|
+
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])',
|
|
231
|
+
);
|
|
232
|
+
if (!focusable.length) return;
|
|
233
|
+
const first = focusable[0];
|
|
234
|
+
const last = focusable[focusable.length - 1];
|
|
235
|
+
if (event.shiftKey) {
|
|
236
|
+
if (document.activeElement === first) {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
last.focus();
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
if (document.activeElement === last) {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
first.focus();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
212
248
|
/**
|
|
213
249
|
* Component did update lifecycle handler
|
|
214
250
|
* @param {Object} prevProps
|
|
215
251
|
* @param {Object} prevState
|
|
216
252
|
*/
|
|
217
|
-
|
|
253
|
+
componentWillUnmount() {
|
|
254
|
+
document.removeEventListener('keydown', this.onKeyDown);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
componentDidUpdate(prevProps, prevState) {
|
|
258
|
+
if (!prevProps.open && this.props.open) {
|
|
259
|
+
document.addEventListener('keydown', this.onKeyDown);
|
|
260
|
+
this.modalRef.current?.focus();
|
|
261
|
+
if (this.announceRef.current) {
|
|
262
|
+
this.announceRef.current.textContent = this.props.intl.formatMessage(
|
|
263
|
+
messages.dialogOpened,
|
|
264
|
+
{ title: this.props.title },
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (prevProps.open && !this.props.open) {
|
|
269
|
+
document.removeEventListener('keydown', this.onKeyDown);
|
|
270
|
+
if (this.announceRef.current) {
|
|
271
|
+
this.announceRef.current.textContent = this.props.intl.formatMessage(
|
|
272
|
+
messages.dialogClosed,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
218
276
|
if (this.props.onChangeFormData) {
|
|
219
277
|
if (!isEqual(prevState?.formData, this.state.formData)) {
|
|
220
278
|
this.props.onChangeFormData(this.state.formData);
|
|
@@ -259,100 +317,118 @@ class ModalForm extends Component {
|
|
|
259
317
|
|
|
260
318
|
const state_errors = keys(this.state.errors).length > 0;
|
|
261
319
|
return (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
''
|
|
320
|
+
<>
|
|
321
|
+
{/* aria-live region outside Modal so it persists through open/close cycles */}
|
|
322
|
+
<div
|
|
323
|
+
ref={this.announceRef}
|
|
324
|
+
aria-live="assertive"
|
|
325
|
+
aria-atomic="true"
|
|
326
|
+
style={{
|
|
327
|
+
position: 'absolute',
|
|
328
|
+
width: '1px',
|
|
329
|
+
height: '1px',
|
|
330
|
+
overflow: 'hidden',
|
|
331
|
+
opacity: 0,
|
|
332
|
+
}}
|
|
333
|
+
/>
|
|
334
|
+
<Modal
|
|
335
|
+
role="dialog"
|
|
336
|
+
dimmer={this.props.dimmer}
|
|
337
|
+
open={this.props.open}
|
|
338
|
+
className={this.props.className}
|
|
339
|
+
aria-labelledby={this.headerId}
|
|
340
|
+
aria-modal="true"
|
|
341
|
+
>
|
|
342
|
+
<Header id={this.headerId}>{this.props.title}</Header>
|
|
343
|
+
<Dimmer active={this.props.loading}>
|
|
344
|
+
<Loader>
|
|
345
|
+
{this.props.loadingMessage || (
|
|
346
|
+
<FormattedMessage id="Loading" defaultMessage="Loading." />
|
|
290
347
|
)}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
348
|
+
</Loader>
|
|
349
|
+
</Dimmer>
|
|
350
|
+
<Modal.Content scrolling>
|
|
351
|
+
{/* outline suppressed for programmatic focus via CSS :focus:not(:focus-visible) on .modal-focus-trap */}
|
|
352
|
+
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
|
353
|
+
<div ref={this.modalRef} tabIndex={-1} className="modal-focus-trap">
|
|
354
|
+
<UiForm
|
|
355
|
+
method="post"
|
|
356
|
+
onSubmit={this.onSubmit}
|
|
357
|
+
error={state_errors || Boolean(this.props.submitError)}
|
|
358
|
+
>
|
|
359
|
+
{description}
|
|
360
|
+
<Message error>
|
|
361
|
+
{state_errors ? (
|
|
362
|
+
<FormattedMessage
|
|
363
|
+
id="There were some errors."
|
|
364
|
+
defaultMessage="There were some errors."
|
|
365
|
+
/>
|
|
366
|
+
) : (
|
|
367
|
+
''
|
|
368
|
+
)}
|
|
369
|
+
<div>{this.props.submitError}</div>
|
|
370
|
+
</Message>
|
|
371
|
+
{schema.fieldsets?.length > 1 && (
|
|
372
|
+
<Menu tabular stackable>
|
|
373
|
+
{map(schema.fieldsets, (item, index) => (
|
|
374
|
+
<Menu.Item
|
|
375
|
+
name={item.id}
|
|
376
|
+
index={index}
|
|
377
|
+
key={item.id}
|
|
378
|
+
active={this.state.currentTab === index}
|
|
379
|
+
onClick={this.selectTab}
|
|
380
|
+
>
|
|
381
|
+
{item.title}
|
|
382
|
+
</Menu.Item>
|
|
383
|
+
))}
|
|
384
|
+
</Menu>
|
|
385
|
+
)}
|
|
386
|
+
{fields.map((field) => (
|
|
387
|
+
<Field
|
|
388
|
+
{...field}
|
|
389
|
+
key={field.id}
|
|
390
|
+
onBlur={this.onBlurField}
|
|
391
|
+
onClick={this.onClickInput}
|
|
392
|
+
error={this.state.errors[field.id]}
|
|
393
|
+
/>
|
|
305
394
|
))}
|
|
306
|
-
</
|
|
395
|
+
</UiForm>
|
|
396
|
+
</div>
|
|
397
|
+
</Modal.Content>
|
|
398
|
+
<Modal.Actions>
|
|
399
|
+
{onCancel && (
|
|
400
|
+
<Button
|
|
401
|
+
type="button"
|
|
402
|
+
basic
|
|
403
|
+
secondary
|
|
404
|
+
aria-label={this.props.intl.formatMessage(messages.cancel)}
|
|
405
|
+
title={this.props.intl.formatMessage(messages.cancel)}
|
|
406
|
+
onClick={onCancel}
|
|
407
|
+
>
|
|
408
|
+
<Icon name={clearSVG} className="circled" size="30px" />
|
|
409
|
+
</Button>
|
|
307
410
|
)}
|
|
308
|
-
{fields.map((field) => (
|
|
309
|
-
<Field
|
|
310
|
-
{...field}
|
|
311
|
-
key={field.id}
|
|
312
|
-
onBlur={this.onBlurField}
|
|
313
|
-
onClick={this.onClickInput}
|
|
314
|
-
error={this.state.errors[field.id]}
|
|
315
|
-
/>
|
|
316
|
-
))}
|
|
317
|
-
</UiForm>
|
|
318
|
-
</Modal.Content>
|
|
319
|
-
<Modal.Actions>
|
|
320
|
-
<Button
|
|
321
|
-
basic
|
|
322
|
-
circular
|
|
323
|
-
primary
|
|
324
|
-
floated="right"
|
|
325
|
-
aria-label={
|
|
326
|
-
this.props.submitLabel
|
|
327
|
-
? this.props.submitLabel
|
|
328
|
-
: this.props.intl.formatMessage(messages.save)
|
|
329
|
-
}
|
|
330
|
-
title={
|
|
331
|
-
this.props.submitLabel
|
|
332
|
-
? this.props.submitLabel
|
|
333
|
-
: this.props.intl.formatMessage(messages.save)
|
|
334
|
-
}
|
|
335
|
-
onClick={this.onSubmit}
|
|
336
|
-
loading={this.props.loading}
|
|
337
|
-
>
|
|
338
|
-
<Icon name={aheadSVG} className="contents circled" size="30px" />
|
|
339
|
-
</Button>
|
|
340
|
-
{onCancel && (
|
|
341
411
|
<Button
|
|
342
|
-
type="button"
|
|
343
412
|
basic
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
413
|
+
primary
|
|
414
|
+
aria-label={
|
|
415
|
+
this.props.submitLabel
|
|
416
|
+
? this.props.submitLabel
|
|
417
|
+
: this.props.intl.formatMessage(messages.save)
|
|
418
|
+
}
|
|
419
|
+
title={
|
|
420
|
+
this.props.submitLabel
|
|
421
|
+
? this.props.submitLabel
|
|
422
|
+
: this.props.intl.formatMessage(messages.save)
|
|
423
|
+
}
|
|
424
|
+
onClick={this.onSubmit}
|
|
425
|
+
loading={this.props.loading}
|
|
350
426
|
>
|
|
351
|
-
<Icon name={
|
|
427
|
+
<Icon name={aheadSVG} className="contents circled" size="30px" />
|
|
352
428
|
</Button>
|
|
353
|
-
|
|
354
|
-
</Modal
|
|
355
|
-
|
|
429
|
+
</Modal.Actions>
|
|
430
|
+
</Modal>
|
|
431
|
+
</>
|
|
356
432
|
);
|
|
357
433
|
}
|
|
358
434
|
}
|
|
@@ -58,6 +58,7 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
58
58
|
selectableTypes,
|
|
59
59
|
maximumSelectionSize,
|
|
60
60
|
currentPath,
|
|
61
|
+
initialPath,
|
|
61
62
|
onlyFolderishSelectable,
|
|
62
63
|
} = {}) =>
|
|
63
64
|
this.setState(() => ({
|
|
@@ -71,6 +72,7 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
71
72
|
selectableTypes,
|
|
72
73
|
maximumSelectionSize,
|
|
73
74
|
currentPath,
|
|
75
|
+
initialPath,
|
|
74
76
|
onlyFolderishSelectable,
|
|
75
77
|
}));
|
|
76
78
|
|
|
@@ -82,6 +84,10 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
82
84
|
this.props.pathname ||
|
|
83
85
|
this.props.location?.pathname;
|
|
84
86
|
|
|
87
|
+
let initialPath = this.state?.initialPath
|
|
88
|
+
? getBaseUrl(this.state.initialPath)
|
|
89
|
+
: null;
|
|
90
|
+
|
|
85
91
|
return (
|
|
86
92
|
<>
|
|
87
93
|
<WrappedComponent
|
|
@@ -105,6 +111,7 @@ const withObjectBrowser = (WrappedComponent) =>
|
|
|
105
111
|
: this.props.data
|
|
106
112
|
}
|
|
107
113
|
contextURL={getBaseUrl(contextURL)}
|
|
114
|
+
initialPath={initialPath}
|
|
108
115
|
closeObjectBrowser={this.closeObjectBrowser}
|
|
109
116
|
mode={this.state.mode}
|
|
110
117
|
onSelectItem={this.state.onSelectItem}
|
|
@@ -84,6 +84,7 @@ class ObjectBrowserBody extends Component {
|
|
|
84
84
|
onSelectItem: PropTypes.func,
|
|
85
85
|
dataName: PropTypes.string,
|
|
86
86
|
maximumSelectionSize: PropTypes.number,
|
|
87
|
+
initialPath: PropTypes.string,
|
|
87
88
|
contextURL: PropTypes.string,
|
|
88
89
|
searchableTypes: PropTypes.arrayOf(PropTypes.string),
|
|
89
90
|
onlyFolderishSelectable: PropTypes.bool,
|
|
@@ -113,18 +114,21 @@ class ObjectBrowserBody extends Component {
|
|
|
113
114
|
*/
|
|
114
115
|
constructor(props) {
|
|
115
116
|
super(props);
|
|
117
|
+
const defaultMultiplePath = props.initialPath || '/';
|
|
116
118
|
this.state = {
|
|
117
119
|
currentFolder:
|
|
118
|
-
this.props.mode === 'multiple'
|
|
120
|
+
this.props.mode === 'multiple'
|
|
121
|
+
? defaultMultiplePath
|
|
122
|
+
: this.props.contextURL || '/',
|
|
119
123
|
currentImageFolder:
|
|
120
124
|
this.props.mode === 'multiple'
|
|
121
|
-
?
|
|
125
|
+
? defaultMultiplePath
|
|
122
126
|
: this.props.mode === 'image' && this.props.data?.url
|
|
123
127
|
? getParentURL(this.props.data.url)
|
|
124
128
|
: '/',
|
|
125
129
|
currentLinkFolder:
|
|
126
130
|
this.props.mode === 'multiple'
|
|
127
|
-
?
|
|
131
|
+
? defaultMultiplePath
|
|
128
132
|
: this.props.mode === 'link' && this.props.data?.href
|
|
129
133
|
? getParentURL(this.props.data.href)
|
|
130
134
|
: '/',
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import { Provider } from 'react-intl-redux';
|
|
5
|
+
import ObjectBrowserBody from './ObjectBrowserBody';
|
|
6
|
+
|
|
7
|
+
const mockStore = configureStore();
|
|
8
|
+
|
|
9
|
+
const baseState = {
|
|
10
|
+
search: { subrequests: {} },
|
|
11
|
+
intl: { locale: 'en', messages: {} },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const baseProps = {
|
|
15
|
+
block: 'test-block',
|
|
16
|
+
data: {},
|
|
17
|
+
closeObjectBrowser: () => {},
|
|
18
|
+
onChangeBlock: () => {},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getInitialSearchPath = (actions) => {
|
|
22
|
+
const action = actions.find((a) => a.type === 'SEARCH_CONTENT');
|
|
23
|
+
return action?.request?.path?.split('/@search')[0];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('ObjectBrowserBody', () => {
|
|
27
|
+
it('uses initialPath as the default folder when mode=multiple', () => {
|
|
28
|
+
const store = mockStore(baseState);
|
|
29
|
+
render(
|
|
30
|
+
<Provider store={store}>
|
|
31
|
+
<ObjectBrowserBody
|
|
32
|
+
{...baseProps}
|
|
33
|
+
mode="multiple"
|
|
34
|
+
initialPath="/company/team"
|
|
35
|
+
/>
|
|
36
|
+
</Provider>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(getInitialSearchPath(store.getActions())).toBe('/company/team');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('defaults to root when mode=multiple and initialPath is not provided', () => {
|
|
43
|
+
const store = mockStore(baseState);
|
|
44
|
+
render(
|
|
45
|
+
<Provider store={store}>
|
|
46
|
+
<ObjectBrowserBody {...baseProps} mode="multiple" />
|
|
47
|
+
</Provider>,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(getInitialSearchPath(store.getActions())).toBe('/');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -155,6 +155,7 @@ const Sidebar = (props) => {
|
|
|
155
155
|
key: 'documentTab',
|
|
156
156
|
as: 'button',
|
|
157
157
|
className: 'ui button',
|
|
158
|
+
type: 'button',
|
|
158
159
|
content: type || intl.formatMessage(messages.document),
|
|
159
160
|
},
|
|
160
161
|
pane: (
|
|
@@ -170,6 +171,7 @@ const Sidebar = (props) => {
|
|
|
170
171
|
key: 'blockTab',
|
|
171
172
|
as: 'button',
|
|
172
173
|
className: 'ui button',
|
|
174
|
+
type: 'button',
|
|
173
175
|
content: intl.formatMessage(messages.block),
|
|
174
176
|
},
|
|
175
177
|
pane: (
|
|
@@ -108,6 +108,18 @@ const messages = defineMessages({
|
|
|
108
108
|
id: 'Unlock',
|
|
109
109
|
defaultMessage: 'Unlock',
|
|
110
110
|
},
|
|
111
|
+
menuOpened: {
|
|
112
|
+
id: 'Menu opened',
|
|
113
|
+
defaultMessage: 'Menu opened',
|
|
114
|
+
},
|
|
115
|
+
menuClosed: {
|
|
116
|
+
id: 'Menu closed',
|
|
117
|
+
defaultMessage: 'Menu closed',
|
|
118
|
+
},
|
|
119
|
+
focusOn: {
|
|
120
|
+
id: 'Focus on',
|
|
121
|
+
defaultMessage: 'Focus on',
|
|
122
|
+
},
|
|
111
123
|
});
|
|
112
124
|
|
|
113
125
|
let toolbarComponents = {
|
|
@@ -185,6 +197,7 @@ class Toolbar extends Component {
|
|
|
185
197
|
toolbarRef = React.createRef();
|
|
186
198
|
toolbarWindow = React.createRef();
|
|
187
199
|
buttonRef = React.createRef();
|
|
200
|
+
announceRef = React.createRef();
|
|
188
201
|
|
|
189
202
|
constructor(props) {
|
|
190
203
|
super(props);
|
|
@@ -305,10 +318,40 @@ class Toolbar extends Component {
|
|
|
305
318
|
}
|
|
306
319
|
// PersonalTools always shows at bottom
|
|
307
320
|
if (selector === 'personalTools') {
|
|
308
|
-
this.setState(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
321
|
+
this.setState(
|
|
322
|
+
(state) => ({
|
|
323
|
+
showMenu: !state.showMenu,
|
|
324
|
+
menuStyle: { bottom: 0 },
|
|
325
|
+
}),
|
|
326
|
+
() => {
|
|
327
|
+
// Scoped only to personalTools — does not affect other toolbar flows
|
|
328
|
+
const candidates =
|
|
329
|
+
this.toolbarWindow.current?.querySelectorAll(
|
|
330
|
+
'a, button, input, [tabindex]:not([tabindex="-1"])',
|
|
331
|
+
) ?? [];
|
|
332
|
+
const firstVisible = Array.from(candidates).find((el) => {
|
|
333
|
+
const style = window.getComputedStyle(el);
|
|
334
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
335
|
+
});
|
|
336
|
+
firstVisible?.focus();
|
|
337
|
+
|
|
338
|
+
// Announce to screen readers: menu opened + which element received focus
|
|
339
|
+
if (this.announceRef.current) {
|
|
340
|
+
const focusedLabel =
|
|
341
|
+
firstVisible?.getAttribute('aria-label') ||
|
|
342
|
+
firstVisible?.textContent?.trim() ||
|
|
343
|
+
'';
|
|
344
|
+
this.announceRef.current.textContent = '';
|
|
345
|
+
setTimeout(() => {
|
|
346
|
+
if (this.announceRef.current) {
|
|
347
|
+
this.announceRef.current.textContent = focusedLabel
|
|
348
|
+
? `${this.props.intl.formatMessage(messages.menuOpened)}, ${this.props.intl.formatMessage(messages.focusOn)} ${focusedLabel}`
|
|
349
|
+
: this.props.intl.formatMessage(messages.menuOpened);
|
|
350
|
+
}
|
|
351
|
+
}, 100);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
);
|
|
312
355
|
} else if (selector === 'more') {
|
|
313
356
|
this.setState((state) => ({
|
|
314
357
|
showMenu: !state.showMenu,
|
|
@@ -337,10 +380,22 @@ class Toolbar extends Component {
|
|
|
337
380
|
|
|
338
381
|
handleClickOutside = (e) => {
|
|
339
382
|
const target = e.target;
|
|
340
|
-
if (this.pusher && doesNodeContainClick(this.pusher, e)) return;
|
|
341
383
|
|
|
342
|
-
|
|
343
|
-
|
|
384
|
+
if (this.pusher && doesNodeContainClick(this.pusher, e)) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (
|
|
389
|
+
this.toolbarRef.current &&
|
|
390
|
+
doesNodeContainClick(this.toolbarRef.current, e)
|
|
391
|
+
) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (target.closest('.ui.modal') || target.closest('.ui.dimmer')) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
344
399
|
const button =
|
|
345
400
|
doesNodeContainClick(this.toolbarRef.current, e) &&
|
|
346
401
|
this.findAncestor(target, 'button');
|
|
@@ -376,12 +431,39 @@ class Toolbar extends Component {
|
|
|
376
431
|
<BodyClass
|
|
377
432
|
className={expanded ? 'has-toolbar' : 'has-toolbar-collapsed'}
|
|
378
433
|
/>
|
|
434
|
+
<span
|
|
435
|
+
aria-live="assertive"
|
|
436
|
+
aria-atomic="true"
|
|
437
|
+
role="status"
|
|
438
|
+
className="visually-hidden"
|
|
439
|
+
ref={this.announceRef}
|
|
440
|
+
/>
|
|
379
441
|
<div
|
|
380
442
|
style={this.state.menuStyle}
|
|
381
443
|
className={
|
|
382
444
|
this.state.showMenu ? 'toolbar-content show' : 'toolbar-content'
|
|
383
445
|
}
|
|
384
446
|
ref={this.toolbarWindow}
|
|
447
|
+
onBlur={(e) => {
|
|
448
|
+
if (!this.toolbarWindow.current?.contains(e.relatedTarget)) {
|
|
449
|
+
this.toolbarRef.current
|
|
450
|
+
?.querySelector('button.toolbar-handler-button')
|
|
451
|
+
?.focus();
|
|
452
|
+
|
|
453
|
+
this.closeMenu();
|
|
454
|
+
|
|
455
|
+
if (this.announceRef.current) {
|
|
456
|
+
this.announceRef.current.textContent = '';
|
|
457
|
+
// Timeout to allow the screen reader to pick up the change in content after the menu is closed
|
|
458
|
+
setTimeout(() => {
|
|
459
|
+
if (this.announceRef.current) {
|
|
460
|
+
this.announceRef.current.textContent =
|
|
461
|
+
this.props.intl.formatMessage(messages.menuClosed);
|
|
462
|
+
}
|
|
463
|
+
}, 100);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}}
|
|
385
467
|
>
|
|
386
468
|
{this.state.showMenu && (
|
|
387
469
|
// This sets the scroll locker in the body tag in mobile
|
|
@@ -307,6 +307,7 @@ export class ObjectBrowserWidgetComponent extends Component {
|
|
|
307
307
|
this.props.openObjectBrowser({
|
|
308
308
|
mode: this.props.mode,
|
|
309
309
|
currentPath: this.props.initialPath || this.props.location.pathname,
|
|
310
|
+
initialPath: this.props.initialPath,
|
|
310
311
|
propDataName: 'value',
|
|
311
312
|
onSelectItem: (url, item) => {
|
|
312
313
|
this.onChange(item);
|