@strapi/content-manager 5.17.0-beta.0 → 5.17.0

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.
@@ -1,8 +1,8 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { useForm, useField, useNotification, useFocusInputField } from '@strapi/admin/strapi-admin';
4
- import { Flex, Box, Link, TextButton, Field, Combobox, ComboboxOption, Typography, VisuallyHidden, useComposedRefs, IconButton } from '@strapi/design-system';
5
- import { ArrowClockwise, Plus, Link as Link$1, Drag, Cross } from '@strapi/icons';
3
+ import { useForm, useField, useNotification, useRBAC, useFocusInputField } from '@strapi/admin/strapi-admin';
4
+ import { Flex, Box, Link, TextButton, EmptyStateLayout, Field, Combobox, ComboboxOption, Typography, VisuallyHidden, useComposedRefs, IconButton } from '@strapi/design-system';
5
+ import { ArrowClockwise, WarningCircle, Plus, Link as Link$1, Drag, Cross } from '@strapi/icons';
6
6
  import { generateNKeysBetween } from 'fractional-indexing';
7
7
  import pipe from 'lodash/fp/pipe';
8
8
  import { getEmptyImage } from 'react-dnd-html5-backend';
@@ -11,6 +11,8 @@ import { FixedSizeList } from 'react-window';
11
11
  import { styled } from 'styled-components';
12
12
  import { COLLECTION_TYPES } from '../../../../../constants/collections.mjs';
13
13
  import { ItemTypes } from '../../../../../constants/dragAndDrop.mjs';
14
+ import { PERMISSIONS } from '../../../../../constants/plugin.mjs';
15
+ import { DocumentRBAC, useDocumentRBAC } from '../../../../../features/DocumentRBAC.mjs';
14
16
  import { useDebounce } from '../../../../../hooks/useDebounce.mjs';
15
17
  import { useDocument } from '../../../../../hooks/useDocument.mjs';
16
18
  import { useDocumentContext } from '../../../../../hooks/useDocumentContext.mjs';
@@ -307,7 +309,6 @@ const ONE_WAY_RELATIONS = [
307
309
  * @description Contains all the logic for the combobox that can search
308
310
  * for relations and then add them to the field's connect array.
309
311
  */ const RelationsInput = ({ hint, id, model, label, labelAction, name, mainField, placeholder, required, unique: _unique, 'aria-label': _ariaLabel, onChange, isRelatedToCurrentDocument, ...props })=>{
310
- const [textValue, setTextValue] = React.useState('');
311
312
  const [searchParams, setSearchParams] = React.useState({
312
313
  _q: '',
313
314
  page: 1
@@ -315,7 +316,6 @@ const ONE_WAY_RELATIONS = [
315
316
  const { toggleNotification } = useNotification();
316
317
  const { currentDocumentMeta } = useDocumentContext('RelationsInput');
317
318
  const { formatMessage } = useIntl();
318
- const fieldRef = useFocusInputField(name);
319
319
  const field = useField(name);
320
320
  const searchParamsDebounced = useDebounce(searchParams, 300);
321
321
  const [searchForTrigger, { data, isLoading }] = useLazySearchRelationsQuery();
@@ -356,13 +356,6 @@ const ONE_WAY_RELATIONS = [
356
356
  isRelatedToCurrentDocument,
357
357
  currentDocumentMeta.params
358
358
  ]);
359
- const handleSearch = async (search)=>{
360
- setSearchParams((s)=>({
361
- ...s,
362
- _q: search,
363
- page: 1
364
- }));
365
- };
366
359
  const hasNextPage = data?.pagination ? data.pagination.page < data.pagination.pageCount : false;
367
360
  const options = data?.results ?? [];
368
361
  const handleChange = (relationId)=>{
@@ -390,21 +383,6 @@ const ONE_WAY_RELATIONS = [
390
383
  *
391
384
  */ onChange(relation);
392
385
  };
393
- const handleLoadMore = ()=>{
394
- if (!data || !data.pagination) {
395
- return;
396
- } else if (data.pagination.page < data.pagination.pageCount) {
397
- setSearchParams((s)=>({
398
- ...s,
399
- page: s.page + 1
400
- }));
401
- }
402
- };
403
- React.useLayoutEffect(()=>{
404
- setTextValue('');
405
- }, [
406
- field.value
407
- ]);
408
386
  const relation = {
409
387
  collectionType: COLLECTION_TYPES,
410
388
  // @ts-expect-error – targetModel does exist on the attribute. But it's not typed.
@@ -412,9 +390,26 @@ const ONE_WAY_RELATIONS = [
412
390
  documentId: '',
413
391
  params: currentDocumentMeta.params
414
392
  };
415
- const { componentUID } = useComponent('RelationsField', ({ uid })=>({
416
- componentUID: uid
417
- }));
393
+ const { permissions = [], isLoading: isLoadingPermissions, error } = useRBAC(PERMISSIONS.map((action)=>({
394
+ action,
395
+ subject: relation.model
396
+ })));
397
+ if (error) {
398
+ return /*#__PURE__*/ jsx(Flex, {
399
+ alignItems: "center",
400
+ height: "100%",
401
+ justifyContent: "center",
402
+ children: /*#__PURE__*/ jsx(EmptyStateLayout, {
403
+ icon: /*#__PURE__*/ jsx(WarningCircle, {
404
+ width: "16rem"
405
+ }),
406
+ content: formatMessage({
407
+ id: 'anErrorOccurred',
408
+ defaultMessage: 'Whoops! Something went wrong. Please, try again.'
409
+ })
410
+ })
411
+ });
412
+ }
418
413
  return /*#__PURE__*/ jsxs(Field.Root, {
419
414
  error: field.error,
420
415
  hint: hint,
@@ -425,90 +420,143 @@ const ONE_WAY_RELATIONS = [
425
420
  action: labelAction,
426
421
  children: label
427
422
  }),
428
- /*#__PURE__*/ jsx(RelationModalRenderer, {
429
- children: ({ dispatch })=>/*#__PURE__*/ jsx(Combobox, {
430
- ref: fieldRef,
431
- creatable: "visible",
432
- createMessage: ()=>formatMessage({
433
- id: getTranslation('relation.create'),
434
- defaultMessage: 'Create a relation'
435
- }),
436
- onCreateOption: ()=>{
437
- dispatch({
438
- type: 'GO_TO_RELATION',
439
- payload: {
440
- document: relation,
441
- shouldBypassConfirmation: false,
442
- fieldToConnect: name,
443
- fieldToConnectUID: componentUID
444
- }
445
- });
446
- },
447
- creatableStartIcon: /*#__PURE__*/ jsx(Plus, {
448
- fill: "neutral500"
449
- }),
450
- name: name,
451
- autocomplete: "list",
452
- placeholder: placeholder || formatMessage({
453
- id: getTranslation('relation.add'),
454
- defaultMessage: 'Add relation'
455
- }),
456
- hasMoreItems: hasNextPage,
457
- loading: isLoading,
458
- onOpenChange: ()=>{
459
- handleSearch(textValue ?? '');
460
- },
461
- noOptionsMessage: ()=>formatMessage({
462
- id: getTranslation('relation.notAvailable'),
463
- defaultMessage: 'No relations available'
464
- }),
465
- loadingMessage: formatMessage({
466
- id: getTranslation('relation.isLoading'),
467
- defaultMessage: 'Relations are loading'
468
- }),
469
- onLoadMore: handleLoadMore,
423
+ /*#__PURE__*/ jsx(DocumentRBAC, {
424
+ permissions: permissions,
425
+ model: relation.model,
426
+ children: /*#__PURE__*/ jsx(RelationModalWithContext, {
427
+ relation: relation,
428
+ name: name,
429
+ placeholder: placeholder,
430
+ hasNextPage: hasNextPage,
431
+ isLoadingPermissions: isLoadingPermissions,
432
+ isLoadingSearchRelations: isLoading,
433
+ handleChange: handleChange,
434
+ setSearchParams: setSearchParams,
435
+ data: data,
436
+ mainField: mainField,
437
+ fieldValue: field.value,
438
+ ...props
439
+ })
440
+ }),
441
+ /*#__PURE__*/ jsx(Field.Error, {}),
442
+ /*#__PURE__*/ jsx(Field.Hint, {})
443
+ ]
444
+ });
445
+ };
446
+ const RelationModalWithContext = ({ relation, name, placeholder, hasNextPage, isLoadingSearchRelations, isLoadingPermissions, handleChange, mainField, setSearchParams, fieldValue, data, ...props })=>{
447
+ const [textValue, setTextValue] = React.useState('');
448
+ const { formatMessage } = useIntl();
449
+ const canCreate = useDocumentRBAC('RelationModalWrapper', (state)=>state.canCreate);
450
+ const fieldRef = useFocusInputField(name);
451
+ const { componentUID } = useComponent('RelationsField', ({ uid })=>({
452
+ componentUID: uid
453
+ }));
454
+ const handleLoadMore = ()=>{
455
+ if (!data || !data.pagination) {
456
+ return;
457
+ } else if (data.pagination.page < data.pagination.pageCount) {
458
+ setSearchParams((s)=>({
459
+ ...s,
460
+ page: s.page + 1
461
+ }));
462
+ }
463
+ };
464
+ const options = data?.results ?? [];
465
+ React.useLayoutEffect(()=>{
466
+ setTextValue('');
467
+ }, [
468
+ fieldValue
469
+ ]);
470
+ const handleSearch = async (search)=>{
471
+ setSearchParams((s)=>({
472
+ ...s,
473
+ _q: search,
474
+ page: 1
475
+ }));
476
+ };
477
+ return /*#__PURE__*/ jsx(RelationModalRenderer, {
478
+ children: ({ dispatch })=>/*#__PURE__*/ jsx(Combobox, {
479
+ ref: fieldRef,
480
+ creatable: "visible",
481
+ creatableDisabled: !canCreate,
482
+ createMessage: ()=>formatMessage({
483
+ id: getTranslation('relation.create'),
484
+ defaultMessage: 'Create a relation'
485
+ }),
486
+ onCreateOption: ()=>{
487
+ if (canCreate) {
488
+ dispatch({
489
+ type: 'GO_TO_RELATION',
490
+ payload: {
491
+ document: relation,
492
+ shouldBypassConfirmation: false,
493
+ fieldToConnect: name,
494
+ fieldToConnectUID: componentUID
495
+ }
496
+ });
497
+ }
498
+ },
499
+ creatableStartIcon: /*#__PURE__*/ jsx(Plus, {
500
+ fill: "neutral500"
501
+ }),
502
+ name: name,
503
+ autocomplete: "list",
504
+ placeholder: placeholder || formatMessage({
505
+ id: getTranslation('relation.add'),
506
+ defaultMessage: 'Add relation'
507
+ }),
508
+ hasMoreItems: hasNextPage,
509
+ loading: isLoadingSearchRelations || isLoadingPermissions,
510
+ onOpenChange: ()=>{
511
+ handleSearch(textValue ?? '');
512
+ },
513
+ noOptionsMessage: ()=>formatMessage({
514
+ id: getTranslation('relation.notAvailable'),
515
+ defaultMessage: 'No relations available'
516
+ }),
517
+ loadingMessage: formatMessage({
518
+ id: getTranslation('relation.isLoading'),
519
+ defaultMessage: 'Relations are loading'
520
+ }),
521
+ onLoadMore: handleLoadMore,
522
+ textValue: textValue,
523
+ onChange: handleChange,
524
+ onTextValueChange: (text)=>{
525
+ setTextValue(text);
526
+ },
527
+ onInputChange: (event)=>{
528
+ handleSearch(event.currentTarget.value);
529
+ },
530
+ ...props,
531
+ children: options?.map((opt)=>{
532
+ const textValue = getRelationLabel(opt, mainField);
533
+ return /*#__PURE__*/ jsx(ComboboxOption, {
534
+ value: opt.id.toString(),
470
535
  textValue: textValue,
471
- onChange: handleChange,
472
- onTextValueChange: (text)=>{
473
- setTextValue(text);
474
- },
475
- onInputChange: (event)=>{
476
- handleSearch(event.currentTarget.value);
477
- },
478
- ...props,
479
- children: options.map((opt)=>{
480
- const textValue = getRelationLabel(opt, mainField);
481
- return /*#__PURE__*/ jsx(ComboboxOption, {
482
- value: opt.id.toString(),
483
- textValue: textValue,
484
- children: /*#__PURE__*/ jsxs(Flex, {
536
+ children: /*#__PURE__*/ jsxs(Flex, {
537
+ gap: 2,
538
+ justifyContent: "space-between",
539
+ children: [
540
+ /*#__PURE__*/ jsxs(Flex, {
485
541
  gap: 2,
486
- justifyContent: "space-between",
487
542
  children: [
488
- /*#__PURE__*/ jsxs(Flex, {
489
- gap: 2,
490
- children: [
491
- /*#__PURE__*/ jsx(Link$1, {
492
- fill: "neutral500"
493
- }),
494
- /*#__PURE__*/ jsx(Typography, {
495
- ellipsis: true,
496
- children: textValue
497
- })
498
- ]
543
+ /*#__PURE__*/ jsx(Link$1, {
544
+ fill: "neutral500"
499
545
  }),
500
- opt.status ? /*#__PURE__*/ jsx(DocumentStatus, {
501
- status: opt.status
502
- }) : null
546
+ /*#__PURE__*/ jsx(Typography, {
547
+ ellipsis: true,
548
+ children: textValue
549
+ })
503
550
  ]
504
- })
505
- }, opt.id);
551
+ }),
552
+ opt.status ? /*#__PURE__*/ jsx(DocumentStatus, {
553
+ status: opt.status
554
+ }) : null
555
+ ]
506
556
  })
507
- })
508
- }),
509
- /*#__PURE__*/ jsx(Field.Error, {}),
510
- /*#__PURE__*/ jsx(Field.Hint, {})
511
- ]
557
+ }, opt.id);
558
+ })
559
+ })
512
560
  });
513
561
  };
514
562
  /* -------------------------------------------------------------------------------------------------