node_modules ignore

This commit is contained in:
2025-05-08 23:43:47 +02:00
parent e19d52f172
commit 4574544c9f
65041 changed files with 10593536 additions and 0 deletions

37
server/node_modules/@strapi/content-manager/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,37 @@
Copyright (c) 2015-present Strapi Solutions SAS
Portions of the Strapi software are licensed as follows:
* All software that resides under an "ee/" directory (the “EE Software”), if that directory exists, is licensed under the license defined below.
Enterprise License
If you or the company you represent has entered into a written agreement referencing the Enterprise Edition of the Strapi source code available at
https://github.com/strapi/strapi, then such agreement applies to your use of the Enterprise Edition of the Strapi Software. If you or the company you
represent is using the Enterprise Edition of the Strapi Software in connection with a subscription to our cloud offering, then the agreement you have
agreed to with respect to our cloud offering and the licenses included in such agreement apply to your use of the Enterprise Edition of the Strapi Software.
Otherwise, the Strapi Enterprise Software License Agreement (found here https://strapi.io/enterprise-terms) applies to your use of the Enterprise Edition of the Strapi Software.
BY ACCESSING OR USING THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE RELEVANT REFERENCED AGREEMENT.
IF YOU ARE NOT AUTHORIZED TO ACCEPT THESE TERMS ON BEHALF OF THE COMPANY YOU REPRESENT OR IF YOU DO NOT AGREE TO ALL OF THE RELEVANT TERMS AND CONDITIONS REFERENCED AND YOU
HAVE NOT OTHERWISE EXECUTED A WRITTEN AGREEMENT WITH STRAPI, YOU ARE NOT AUTHORIZED TO ACCESS OR USE OR ALLOW ANY USER TO ACCESS OR USE ANY PART OF
THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE. YOUR ACCESS RIGHTS ARE CONDITIONAL ON YOUR CONSENT TO THE RELEVANT REFERENCED TERMS TO THE EXCLUSION OF ALL OTHER TERMS;
IF THE RELEVANT REFERENCED TERMS ARE CONSIDERED AN OFFER BY YOU, ACCEPTANCE IS EXPRESSLY LIMITED TO THE RELEVANT REFERENCED TERMS.
* All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below.
MIT Expat License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
# Strapi Content Manager
## Description
This plugin allows you to manage your data through a UI.
## Changing the plugin logo
The plugin's logo is located in `./admin/src/assets/logo.svg` it is displayed in the marketplace (`/admin/list-plugins`)

View File

@@ -0,0 +1,176 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var Symbols = require('@strapi/icons/symbols');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var Icons__namespace = /*#__PURE__*/_interopNamespaceDefault(Icons);
var Symbols__namespace = /*#__PURE__*/_interopNamespaceDefault(Symbols);
const ComponentIcon = ({ showBackground = true, icon = 'dashboard', ...props })=>{
const Icon = COMPONENT_ICONS[icon] || COMPONENT_ICONS.dashboard;
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
alignItems: "center",
background: showBackground ? 'neutral200' : undefined,
justifyContent: "center",
height: 8,
width: 8,
color: "neutral600",
borderRadius: showBackground ? '50%' : 0,
...props,
children: /*#__PURE__*/ jsxRuntime.jsx(Icon, {
height: "2rem",
width: "2rem"
})
});
};
const COMPONENT_ICONS = {
alien: Icons__namespace.Alien,
apps: Icons__namespace.GridNine,
archive: Icons__namespace.Archive,
arrowDown: Icons__namespace.ArrowDown,
arrowLeft: Icons__namespace.ArrowLeft,
arrowRight: Icons__namespace.ArrowRight,
arrowUp: Icons__namespace.ArrowUp,
attachment: Icons__namespace.Paperclip,
bell: Icons__namespace.Bell,
bold: Icons__namespace.Bold,
book: Icons__namespace.Book,
briefcase: Icons__namespace.Briefcase,
brush: Icons__namespace.PaintBrush,
bulletList: Icons__namespace.BulletList,
calendar: Icons__namespace.Calendar,
car: Icons__namespace.Car,
cast: Icons__namespace.Cast,
chartBubble: Icons__namespace.ChartBubble,
chartCircle: Icons__namespace.ChartCircle,
chartPie: Icons__namespace.ChartPie,
check: Icons__namespace.Check,
clock: Icons__namespace.Clock,
cloud: Icons__namespace.Cloud,
code: Icons__namespace.Code,
cog: Icons__namespace.Cog,
collapse: Icons__namespace.Collapse,
command: Icons__namespace.Command,
connector: Icons__namespace.Faders,
crop: Icons__namespace.Crop,
crown: Icons__namespace.Crown,
cup: Icons__namespace.Coffee,
cursor: Icons__namespace.Cursor,
dashboard: Icons__namespace.SquaresFour,
database: Icons__namespace.Database,
discuss: Icons__namespace.Discuss,
doctor: Icons__namespace.Stethoscope,
earth: Icons__namespace.Earth,
emotionHappy: Icons__namespace.EmotionHappy,
emotionUnhappy: Icons__namespace.EmotionUnhappy,
envelop: Icons__namespace.Mail,
exit: Icons__namespace.SignOut,
expand: Icons__namespace.Expand,
eye: Icons__namespace.Eye,
feather: Icons__namespace.Feather,
file: Icons__namespace.File,
fileError: Icons__namespace.FileError,
filePdf: Icons__namespace.FilePdf,
filter: Icons__namespace.Filter,
folder: Icons__namespace.Folder,
gate: Icons__namespace.CastleTurret,
gift: Icons__namespace.Gift,
globe: Icons__namespace.Globe,
grid: Icons__namespace.GridFour,
handHeart: Icons__namespace.HandHeart,
hashtag: Icons__namespace.Hashtag,
headphone: Icons__namespace.Headphones,
heart: Icons__namespace.Heart,
house: Icons__namespace.House,
information: Icons__namespace.Information,
italic: Icons__namespace.Italic,
key: Icons__namespace.Key,
landscape: Icons__namespace.Images,
layer: Icons__namespace.ListPlus,
layout: Icons__namespace.Layout,
lightbulb: Icons__namespace.Lightbulb,
link: Icons__namespace.Link,
lock: Icons__namespace.Lock,
magic: Icons__namespace.Magic,
manyToMany: Icons__namespace.ManyToMany,
manyToOne: Icons__namespace.ManyToOne,
manyWays: Icons__namespace.ManyWays,
medium: Symbols__namespace.Medium,
message: Icons__namespace.Message,
microphone: Icons__namespace.Microphone,
monitor: Icons__namespace.Monitor,
moon: Icons__namespace.Moon,
music: Icons__namespace.MusicNotes,
oneToMany: Icons__namespace.OneToMany,
oneToOne: Icons__namespace.OneToOne,
oneWay: Icons__namespace.OneWay,
paint: Icons__namespace.PaintBrush,
paintBrush: Icons__namespace.PaintBrush,
paperPlane: Icons__namespace.PaperPlane,
pencil: Icons__namespace.Pencil,
phone: Icons__namespace.Phone,
picture: Icons__namespace.Image,
pin: Icons__namespace.Pin,
pinMap: Icons__namespace.PinMap,
plane: Icons__namespace.Plane,
play: Icons__namespace.Play,
plus: Icons__namespace.Plus,
priceTag: Icons__namespace.PriceTag,
puzzle: Icons__namespace.PuzzlePiece,
question: Icons__namespace.Question,
quote: Icons__namespace.Quotes,
refresh: Icons__namespace.ArrowClockwise,
restaurant: Icons__namespace.Restaurant,
rocket: Icons__namespace.Rocket,
rotate: Icons__namespace.ArrowsCounterClockwise,
scissors: Icons__namespace.Scissors,
search: Icons__namespace.Search,
seed: Icons__namespace.Plant,
server: Icons__namespace.Server,
shield: Icons__namespace.Shield,
shirt: Icons__namespace.Shirt,
shoppingCart: Icons__namespace.ShoppingCart,
slideshow: Icons__namespace.PresentationChart,
stack: Icons__namespace.Stack,
star: Icons__namespace.Star,
store: Icons__namespace.Store,
strikeThrough: Icons__namespace.StrikeThrough,
sun: Icons__namespace.Sun,
television: Icons__namespace.Television,
thumbDown: Icons__namespace.ThumbDown,
thumbUp: Icons__namespace.ThumbUp,
train: Icons__namespace.Train,
twitter: Symbols__namespace.X,
typhoon: Icons__namespace.Typhoon,
underline: Icons__namespace.Underline,
user: Icons__namespace.User,
volumeMute: Icons__namespace.VolumeMute,
volumeUp: Icons__namespace.VolumeUp,
walk: Icons__namespace.Walk,
wheelchair: Icons__namespace.Wheelchair,
write: Icons__namespace.Feather
};
exports.COMPONENT_ICONS = COMPONENT_ICONS;
exports.ComponentIcon = ComponentIcon;
//# sourceMappingURL=ComponentIcon.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,153 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { Flex } from '@strapi/design-system';
import * as Icons from '@strapi/icons';
import * as Symbols from '@strapi/icons/symbols';
const ComponentIcon = ({ showBackground = true, icon = 'dashboard', ...props })=>{
const Icon = COMPONENT_ICONS[icon] || COMPONENT_ICONS.dashboard;
return /*#__PURE__*/ jsx(Flex, {
alignItems: "center",
background: showBackground ? 'neutral200' : undefined,
justifyContent: "center",
height: 8,
width: 8,
color: "neutral600",
borderRadius: showBackground ? '50%' : 0,
...props,
children: /*#__PURE__*/ jsx(Icon, {
height: "2rem",
width: "2rem"
})
});
};
const COMPONENT_ICONS = {
alien: Icons.Alien,
apps: Icons.GridNine,
archive: Icons.Archive,
arrowDown: Icons.ArrowDown,
arrowLeft: Icons.ArrowLeft,
arrowRight: Icons.ArrowRight,
arrowUp: Icons.ArrowUp,
attachment: Icons.Paperclip,
bell: Icons.Bell,
bold: Icons.Bold,
book: Icons.Book,
briefcase: Icons.Briefcase,
brush: Icons.PaintBrush,
bulletList: Icons.BulletList,
calendar: Icons.Calendar,
car: Icons.Car,
cast: Icons.Cast,
chartBubble: Icons.ChartBubble,
chartCircle: Icons.ChartCircle,
chartPie: Icons.ChartPie,
check: Icons.Check,
clock: Icons.Clock,
cloud: Icons.Cloud,
code: Icons.Code,
cog: Icons.Cog,
collapse: Icons.Collapse,
command: Icons.Command,
connector: Icons.Faders,
crop: Icons.Crop,
crown: Icons.Crown,
cup: Icons.Coffee,
cursor: Icons.Cursor,
dashboard: Icons.SquaresFour,
database: Icons.Database,
discuss: Icons.Discuss,
doctor: Icons.Stethoscope,
earth: Icons.Earth,
emotionHappy: Icons.EmotionHappy,
emotionUnhappy: Icons.EmotionUnhappy,
envelop: Icons.Mail,
exit: Icons.SignOut,
expand: Icons.Expand,
eye: Icons.Eye,
feather: Icons.Feather,
file: Icons.File,
fileError: Icons.FileError,
filePdf: Icons.FilePdf,
filter: Icons.Filter,
folder: Icons.Folder,
gate: Icons.CastleTurret,
gift: Icons.Gift,
globe: Icons.Globe,
grid: Icons.GridFour,
handHeart: Icons.HandHeart,
hashtag: Icons.Hashtag,
headphone: Icons.Headphones,
heart: Icons.Heart,
house: Icons.House,
information: Icons.Information,
italic: Icons.Italic,
key: Icons.Key,
landscape: Icons.Images,
layer: Icons.ListPlus,
layout: Icons.Layout,
lightbulb: Icons.Lightbulb,
link: Icons.Link,
lock: Icons.Lock,
magic: Icons.Magic,
manyToMany: Icons.ManyToMany,
manyToOne: Icons.ManyToOne,
manyWays: Icons.ManyWays,
medium: Symbols.Medium,
message: Icons.Message,
microphone: Icons.Microphone,
monitor: Icons.Monitor,
moon: Icons.Moon,
music: Icons.MusicNotes,
oneToMany: Icons.OneToMany,
oneToOne: Icons.OneToOne,
oneWay: Icons.OneWay,
paint: Icons.PaintBrush,
paintBrush: Icons.PaintBrush,
paperPlane: Icons.PaperPlane,
pencil: Icons.Pencil,
phone: Icons.Phone,
picture: Icons.Image,
pin: Icons.Pin,
pinMap: Icons.PinMap,
plane: Icons.Plane,
play: Icons.Play,
plus: Icons.Plus,
priceTag: Icons.PriceTag,
puzzle: Icons.PuzzlePiece,
question: Icons.Question,
quote: Icons.Quotes,
refresh: Icons.ArrowClockwise,
restaurant: Icons.Restaurant,
rocket: Icons.Rocket,
rotate: Icons.ArrowsCounterClockwise,
scissors: Icons.Scissors,
search: Icons.Search,
seed: Icons.Plant,
server: Icons.Server,
shield: Icons.Shield,
shirt: Icons.Shirt,
shoppingCart: Icons.ShoppingCart,
slideshow: Icons.PresentationChart,
stack: Icons.Stack,
star: Icons.Star,
store: Icons.Store,
strikeThrough: Icons.StrikeThrough,
sun: Icons.Sun,
television: Icons.Television,
thumbDown: Icons.ThumbDown,
thumbUp: Icons.ThumbUp,
train: Icons.Train,
twitter: Symbols.X,
typhoon: Icons.Typhoon,
underline: Icons.Underline,
user: Icons.User,
volumeMute: Icons.VolumeMute,
volumeUp: Icons.VolumeUp,
walk: Icons.Walk,
wheelchair: Icons.Wheelchair,
write: Icons.Feather
};
export { COMPONENT_ICONS, ComponentIcon };
//# sourceMappingURL=ComponentIcon.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,259 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var yup = require('yup');
var attributes = require('../../constants/attributes.js');
var init = require('../../services/init.js');
var strings = require('../../utils/strings.js');
var translations = require('../../utils/translations.js');
var FieldTypeIcon = require('../FieldTypeIcon.js');
var Fields = require('./Fields.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var yup__namespace = /*#__PURE__*/_interopNamespaceDefault(yup);
/* -------------------------------------------------------------------------------------------------
* Constants
* -----------------------------------------------------------------------------------------------*/ const FIELD_SCHEMA = yup__namespace.object().shape({
label: yup__namespace.string().required().nullable(),
description: yup__namespace.string(),
editable: yup__namespace.boolean(),
size: yup__namespace.number().required()
});
const EditFieldForm = ({ attribute, name, onClose })=>{
const { formatMessage } = reactIntl.useIntl();
const { toggleNotification } = strapiAdmin.useNotification();
const { value, onChange } = strapiAdmin.useField(name);
const { data: mainFieldOptions } = init.useGetInitialDataQuery(undefined, {
selectFromResult: (res)=>{
if (attribute?.type !== 'relation' || !res.data) {
return {
data: []
};
}
if ('targetModel' in attribute && typeof attribute.targetModel === 'string') {
const targetSchema = res.data.contentTypes.find((schema)=>schema.uid === attribute.targetModel);
if (targetSchema) {
return {
data: Object.entries(targetSchema.attributes).reduce((acc, [key, attribute])=>{
/**
* Create the list of attributes from the schema as to which can
* be our `mainField` and dictate the display name of the schema
* we're editing.
*/ if (!attributes.ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD.includes(attribute.type)) {
acc.push({
label: key,
value: key
});
}
return acc;
}, [])
};
}
}
return {
data: []
};
},
skip: attribute?.type !== 'relation'
});
if (!value || value.name === Fields.TEMP_FIELD_NAME || !attribute) {
// This is very unlikely to happen, but it ensures the form is not opened without a value.
console.error("You've opened a field to edit without it being part of the form, this is likely a bug with Strapi. Please open an issue.");
toggleNotification({
message: formatMessage({
id: 'content-manager.containers.edit-settings.modal-form.error',
defaultMessage: 'An error occurred while trying to open the form.'
}),
type: 'danger'
});
return null;
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Form, {
method: "PUT",
initialValues: value,
validationSchema: FIELD_SCHEMA,
onSubmit: (data)=>{
onChange(name, data);
onClose();
},
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Header, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 3,
children: [
/*#__PURE__*/ jsxRuntime.jsx(FieldTypeIcon.FieldTypeIcon, {
type: attribute.type
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Title, {
children: formatMessage({
id: 'content-manager.containers.edit-settings.modal-form.label',
defaultMessage: 'Edit {fieldName}'
}, {
fieldName: strings.capitalise(value.name)
})
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Body, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 4,
children: [
{
name: 'label',
label: formatMessage({
id: translations.getTranslation('containers.edit-settings.modal-form.label'),
defaultMessage: 'Label'
}),
size: 6,
type: 'string'
},
{
name: 'description',
label: formatMessage({
id: translations.getTranslation('containers.edit-settings.modal-form.description'),
defaultMessage: 'Description'
}),
size: 6,
type: 'string'
},
{
name: 'placeholder',
label: formatMessage({
id: translations.getTranslation('containers.edit-settings.modal-form.placeholder'),
defaultMessage: 'Placeholder'
}),
size: 6,
type: 'string'
},
{
name: 'editable',
label: formatMessage({
id: translations.getTranslation('containers.edit-settings.modal-form.editable'),
defaultMessage: 'Editable'
}),
size: 6,
type: 'boolean'
},
{
name: 'mainField',
label: formatMessage({
id: translations.getTranslation('containers.edit-settings.modal-form.mainField'),
defaultMessage: 'Entry title'
}),
hint: formatMessage({
id: translations.getTranslation('containers.SettingPage.edit-settings.modal-form.mainField.hint'),
defaultMessage: 'Set the displayed field'
}),
size: 6,
options: mainFieldOptions,
type: 'enumeration'
},
{
name: 'size',
label: formatMessage({
id: translations.getTranslation('containers.ListSettingsView.modal-form.size'),
defaultMessage: 'Size'
}),
size: 6,
options: [
{
value: '4',
label: '33%'
},
{
value: '6',
label: '50%'
},
{
value: '8',
label: '66%'
},
{
value: '12',
label: '100%'
}
],
type: 'enumeration'
}
].filter(filterFieldsBasedOnAttributeType(attribute.type)).map(({ size, ...field })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: size,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.InputRenderer, {
...field
})
}, field.name))
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Footer, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Modal.Close, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "tertiary",
children: formatMessage({
id: 'app.components.Button.cancel',
defaultMessage: 'Cancel'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
type: "submit",
children: formatMessage({
id: 'global.finish',
defaultMessage: 'Finish'
})
})
]
})
]
})
});
};
/**
* @internal
* @description not all edit fields have the same editable properties, it depends on the type
* e.g. a dynamic zone can only change it's label.
*/ const filterFieldsBasedOnAttributeType = (type)=>(field)=>{
switch(type){
case 'blocks':
case 'richtext':
return field.name !== 'size' && field.name !== 'mainField';
case 'boolean':
case 'media':
return field.name !== 'placeholder' && field.name !== 'mainField';
case 'component':
case 'dynamiczone':
return field.name === 'label' || field.name === 'editable';
case 'json':
return field.name !== 'placeholder' && field.name !== 'mainField' && field.name !== 'size';
case 'relation':
return true;
default:
return field.name !== 'mainField';
}
};
exports.EditFieldForm = EditFieldForm;
//# sourceMappingURL=EditFieldForm.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,238 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { useNotification, useField, Form, InputRenderer } from '@strapi/admin/strapi-admin';
import { Modal, Flex, Grid, Button } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import * as yup from 'yup';
import { ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD } from '../../constants/attributes.mjs';
import { useGetInitialDataQuery } from '../../services/init.mjs';
import { capitalise } from '../../utils/strings.mjs';
import { getTranslation } from '../../utils/translations.mjs';
import { FieldTypeIcon } from '../FieldTypeIcon.mjs';
import { TEMP_FIELD_NAME } from './Fields.mjs';
/* -------------------------------------------------------------------------------------------------
* Constants
* -----------------------------------------------------------------------------------------------*/ const FIELD_SCHEMA = yup.object().shape({
label: yup.string().required().nullable(),
description: yup.string(),
editable: yup.boolean(),
size: yup.number().required()
});
const EditFieldForm = ({ attribute, name, onClose })=>{
const { formatMessage } = useIntl();
const { toggleNotification } = useNotification();
const { value, onChange } = useField(name);
const { data: mainFieldOptions } = useGetInitialDataQuery(undefined, {
selectFromResult: (res)=>{
if (attribute?.type !== 'relation' || !res.data) {
return {
data: []
};
}
if ('targetModel' in attribute && typeof attribute.targetModel === 'string') {
const targetSchema = res.data.contentTypes.find((schema)=>schema.uid === attribute.targetModel);
if (targetSchema) {
return {
data: Object.entries(targetSchema.attributes).reduce((acc, [key, attribute])=>{
/**
* Create the list of attributes from the schema as to which can
* be our `mainField` and dictate the display name of the schema
* we're editing.
*/ if (!ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD.includes(attribute.type)) {
acc.push({
label: key,
value: key
});
}
return acc;
}, [])
};
}
}
return {
data: []
};
},
skip: attribute?.type !== 'relation'
});
if (!value || value.name === TEMP_FIELD_NAME || !attribute) {
// This is very unlikely to happen, but it ensures the form is not opened without a value.
console.error("You've opened a field to edit without it being part of the form, this is likely a bug with Strapi. Please open an issue.");
toggleNotification({
message: formatMessage({
id: 'content-manager.containers.edit-settings.modal-form.error',
defaultMessage: 'An error occurred while trying to open the form.'
}),
type: 'danger'
});
return null;
}
return /*#__PURE__*/ jsx(Modal.Content, {
children: /*#__PURE__*/ jsxs(Form, {
method: "PUT",
initialValues: value,
validationSchema: FIELD_SCHEMA,
onSubmit: (data)=>{
onChange(name, data);
onClose();
},
children: [
/*#__PURE__*/ jsx(Modal.Header, {
children: /*#__PURE__*/ jsxs(Flex, {
gap: 3,
children: [
/*#__PURE__*/ jsx(FieldTypeIcon, {
type: attribute.type
}),
/*#__PURE__*/ jsx(Modal.Title, {
children: formatMessage({
id: 'content-manager.containers.edit-settings.modal-form.label',
defaultMessage: 'Edit {fieldName}'
}, {
fieldName: capitalise(value.name)
})
})
]
})
}),
/*#__PURE__*/ jsx(Modal.Body, {
children: /*#__PURE__*/ jsx(Grid.Root, {
gap: 4,
children: [
{
name: 'label',
label: formatMessage({
id: getTranslation('containers.edit-settings.modal-form.label'),
defaultMessage: 'Label'
}),
size: 6,
type: 'string'
},
{
name: 'description',
label: formatMessage({
id: getTranslation('containers.edit-settings.modal-form.description'),
defaultMessage: 'Description'
}),
size: 6,
type: 'string'
},
{
name: 'placeholder',
label: formatMessage({
id: getTranslation('containers.edit-settings.modal-form.placeholder'),
defaultMessage: 'Placeholder'
}),
size: 6,
type: 'string'
},
{
name: 'editable',
label: formatMessage({
id: getTranslation('containers.edit-settings.modal-form.editable'),
defaultMessage: 'Editable'
}),
size: 6,
type: 'boolean'
},
{
name: 'mainField',
label: formatMessage({
id: getTranslation('containers.edit-settings.modal-form.mainField'),
defaultMessage: 'Entry title'
}),
hint: formatMessage({
id: getTranslation('containers.SettingPage.edit-settings.modal-form.mainField.hint'),
defaultMessage: 'Set the displayed field'
}),
size: 6,
options: mainFieldOptions,
type: 'enumeration'
},
{
name: 'size',
label: formatMessage({
id: getTranslation('containers.ListSettingsView.modal-form.size'),
defaultMessage: 'Size'
}),
size: 6,
options: [
{
value: '4',
label: '33%'
},
{
value: '6',
label: '50%'
},
{
value: '8',
label: '66%'
},
{
value: '12',
label: '100%'
}
],
type: 'enumeration'
}
].filter(filterFieldsBasedOnAttributeType(attribute.type)).map(({ size, ...field })=>/*#__PURE__*/ jsx(Grid.Item, {
col: size,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(InputRenderer, {
...field
})
}, field.name))
})
}),
/*#__PURE__*/ jsxs(Modal.Footer, {
children: [
/*#__PURE__*/ jsx(Modal.Close, {
children: /*#__PURE__*/ jsx(Button, {
variant: "tertiary",
children: formatMessage({
id: 'app.components.Button.cancel',
defaultMessage: 'Cancel'
})
})
}),
/*#__PURE__*/ jsx(Button, {
type: "submit",
children: formatMessage({
id: 'global.finish',
defaultMessage: 'Finish'
})
})
]
})
]
})
});
};
/**
* @internal
* @description not all edit fields have the same editable properties, it depends on the type
* e.g. a dynamic zone can only change it's label.
*/ const filterFieldsBasedOnAttributeType = (type)=>(field)=>{
switch(type){
case 'blocks':
case 'richtext':
return field.name !== 'size' && field.name !== 'mainField';
case 'boolean':
case 'media':
return field.name !== 'placeholder' && field.name !== 'mainField';
case 'component':
case 'dynamiczone':
return field.name === 'label' || field.name === 'editable';
case 'json':
return field.name !== 'placeholder' && field.name !== 'mainField' && field.name !== 'size';
case 'relation':
return true;
default:
return field.name !== 'mainField';
}
};
export { EditFieldForm };
//# sourceMappingURL=EditFieldForm.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,534 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var fractionalIndexing = require('fractional-indexing');
var reactDndHtml5Backend = require('react-dnd-html5-backend');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var dragAndDrop = require('../../constants/dragAndDrop.js');
var useDragAndDrop = require('../../hooks/useDragAndDrop.js');
var translations = require('../../utils/translations.js');
var ComponentIcon = require('../ComponentIcon.js');
var EditFieldForm = require('./EditFieldForm.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const Fields = ({ attributes, fieldSizes, components, metadatas = {} })=>{
const { formatMessage } = reactIntl.useIntl();
const layout = strapiAdmin.useForm('Fields', (state)=>state.values.layout ?? []);
const onChange = strapiAdmin.useForm('Fields', (state)=>state.onChange);
const addFieldRow = strapiAdmin.useForm('Fields', (state)=>state.addFieldRow);
const removeFieldRow = strapiAdmin.useForm('Fields', (state)=>state.removeFieldRow);
const existingFields = layout.map((row)=>row.children.map((field)=>field.name)).flat();
/**
* Get the fields that are not already in the layout
* But also check that they are visible before we give users
* the option to display them. e.g. `id` is not visible.
*/ const remainingFields = Object.entries(metadatas).reduce((acc, current)=>{
const [name, { visible, ...field }] = current;
if (!existingFields.includes(name) && visible === true) {
const type = attributes[name]?.type;
const size = type ? fieldSizes[type] : 12;
acc.push({
...field,
label: field.label ?? name,
name,
size
});
}
return acc;
}, []);
const handleMoveField = ([newRowIndex, newFieldIndex], [currentRowIndex, currentFieldIndex])=>{
/**
* Because this view has the constraint that the sum of field sizes cannot be greater
* than 12, we don't use the form's method to move field rows, instead, we calculate
* the new layout and set the entire form.
*/ const newLayout = structuredClone(layout);
/**
* Remove field from the current layout space using splice so we have the item
*/ const [field] = newLayout[currentRowIndex].children.splice(currentFieldIndex, 1);
if (!field || field.name === TEMP_FIELD_NAME) {
return;
}
const newRow = newLayout[newRowIndex].children;
const [newFieldKey] = generateNKeysBetween(newRow, 1, currentFieldIndex, newFieldIndex);
/**
* Next we inject the field into it's new row at it's specified index, we then remove the spaces
* if they exist and recalculate into potentially two arrays ONLY if the sizing is now over 12,
* the row and the rest of the row that couldn't fit.
*
* for example, if i have a row of `[{size: 4}, {size: 6}]` and i add `{size: 8}` a index 0,
* the new array will look like `[{size: 8}, {size: 4}, {size: 6}]` which breaks the limit of 12,
* so instead we make two arrays for the new rows `[[{size: 8}, {size: 4}], [{size: 6}]]` which we
* then inject at the original row point with spacers included.
*/ newRow.splice(newFieldIndex, 0, {
...field,
__temp_key__: newFieldKey
});
if (newLayout[newRowIndex].children.reduce((acc, curr)=>acc + curr.size, 0) > 12) {
const recalculatedRows = chunkArray(newLayout[newRowIndex].children.filter((field)=>field.name !== TEMP_FIELD_NAME));
const rowKeys = generateNKeysBetween(newLayout, recalculatedRows.length, currentRowIndex, newRowIndex);
newLayout.splice(newRowIndex, 1, ...recalculatedRows.map((row, index)=>({
__temp_key__: rowKeys[index],
children: row
})));
}
/**
* Now we remove our spacers from the rows so we can understand what dead rows exist:
* - if there's only spacers left
* - there's nothing in the row, e.g. a size 12 field left it.
* These rows are then filtered out.
* After that, we recalculate the spacers for the rows that need them.
*/ const newLayoutWithSpacers = newLayout.map((row)=>({
...row,
children: row.children.filter((field)=>field.name !== TEMP_FIELD_NAME)
})).filter((row)=>row.children.length > 0).map((row)=>{
const totalSpaceTaken = row.children.reduce((acc, curr)=>acc + curr.size, 0);
if (totalSpaceTaken < 12) {
const [spacerKey] = fractionalIndexing.generateNKeysBetween(row.children.at(-1)?.__temp_key__, undefined, 1);
return {
...row,
children: [
...row.children,
{
name: TEMP_FIELD_NAME,
size: 12 - totalSpaceTaken,
__temp_key__: spacerKey
}
]
};
}
return row;
});
onChange('layout', newLayoutWithSpacers);
};
const handleRemoveField = (rowIndex, fieldIndex)=>()=>{
if (layout[rowIndex].children.length === 1) {
removeFieldRow(`layout`, rowIndex);
} else {
onChange(`layout.${rowIndex}.children`, [
...layout[rowIndex].children.slice(0, fieldIndex),
...layout[rowIndex].children.slice(fieldIndex + 1)
]);
}
};
const handleAddField = (field)=>()=>{
addFieldRow('layout', {
children: [
field
]
});
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
paddingTop: 6,
direction: "column",
alignItems: "stretch",
gap: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
alignItems: "flex-start",
direction: "column",
justifyContent: "space-between",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontWeight: "bold",
children: formatMessage({
id: translations.getTranslation('containers.list.displayedFields'),
defaultMessage: 'Displayed fields'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'containers.SettingPage.editSettings.description',
defaultMessage: 'Drag & drop the fields to build the layout'
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
padding: 4,
hasRadius: true,
borderStyle: "dashed",
borderWidth: "1px",
borderColor: "neutral300",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
layout.map((row, rowIndex)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 2,
children: row.children.map(({ size, ...field }, fieldIndex)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: size,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(Field, {
attribute: attributes[field.name],
components: components,
index: [
rowIndex,
fieldIndex
],
name: `layout.${rowIndex}.children.${fieldIndex}`,
onMoveField: handleMoveField,
onRemoveField: handleRemoveField(rowIndex, fieldIndex)
})
}, field.name))
}, row.__temp_key__)),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Menu.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Trigger, {
startIcon: /*#__PURE__*/ jsxRuntime.jsx(Icons.Plus, {}),
endIcon: null,
disabled: remainingFields.length === 0,
fullWidth: true,
variant: "secondary",
children: formatMessage({
id: translations.getTranslation('containers.SettingPage.add.field'),
defaultMessage: 'Insert another field'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Content, {
children: remainingFields.map((field)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Menu.Item, {
onSelect: handleAddField(field),
children: field.label
}, field.name))
})
]
})
]
})
})
]
});
};
/**
* @internal
* @description Small abstraction to solve within an array of fields where you can
* add a field to the beginning or start, move back and forth what it's index range
* should be when calculating it's new temp key
*/ const generateNKeysBetween = (field, count, currInd, newInd)=>{
const startKey = currInd > newInd ? field[newInd - 1]?.__temp_key__ : field[newInd]?.__temp_key__;
const endKey = currInd > newInd ? field[newInd]?.__temp_key__ : field[newInd + 1]?.__temp_key__;
return fractionalIndexing.generateNKeysBetween(startKey, endKey, count);
};
/**
* @internal
* @description chunks a row of layouts by the max size we allow, 12. It does not add the
* spacers again, that should be added separately.
*/ const chunkArray = (array)=>{
const result = [];
let temp = [];
array.reduce((acc, field)=>{
if (acc + field.size > 12) {
result.push(temp);
temp = [
field
];
return field.size;
} else {
temp.push(field);
return acc + field.size;
}
}, 0);
if (temp.length > 0) {
result.push(temp);
}
return result;
};
const TEMP_FIELD_NAME = '_TEMP_';
/**
* Displays a field in the layout with drag options, also
* opens a modal to edit the details of said field.
*/ const Field = ({ attribute, components, name, index, onMoveField, onRemoveField })=>{
const [isModalOpen, setIsModalOpen] = React__namespace.useState(false);
const { formatMessage } = reactIntl.useIntl();
const { value } = strapiAdmin.useField(name);
const [{ isDragging }, objectRef, dropRef, dragRef, dragPreviewRef] = useDragAndDrop.useDragAndDrop(true, {
dropSensitivity: 'immediate',
type: dragAndDrop.ItemTypes.EDIT_FIELD,
item: {
index,
label: value?.label,
name
},
index,
onMoveItem: onMoveField
});
React__namespace.useEffect(()=>{
dragPreviewRef(reactDndHtml5Backend.getEmptyImage(), {
captureDraggingState: false
});
}, [
dragPreviewRef
]);
const composedRefs = designSystem.useComposedRefs(dragRef, objectRef);
const handleRemoveField = (e)=>{
e.preventDefault();
e.stopPropagation();
onRemoveField(e);
};
const onEditFieldMeta = (e)=>{
e.preventDefault();
e.stopPropagation();
setIsModalOpen(true);
};
const tempRefs = designSystem.useComposedRefs(dropRef, objectRef);
if (!value) {
return null;
}
if (value.name === TEMP_FIELD_NAME) {
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
tag: "span",
height: "100%",
style: {
opacity: 0
},
ref: tempRefs
});
}
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Modal.Root, {
open: isModalOpen,
onOpenChange: setIsModalOpen,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
borderColor: "neutral150",
background: "neutral100",
hasRadius: true,
style: {
opacity: isDragging ? 0.5 : 1
},
ref: dropRef,
gap: 3,
cursor: "pointer",
onClick: ()=>{
setIsModalOpen(true);
},
children: [
/*#__PURE__*/ jsxRuntime.jsx(DragButton, {
tag: "span",
withTooltip: false,
label: formatMessage({
id: translations.getTranslation('components.DraggableCard.move.field'),
defaultMessage: 'Move {item}'
}, {
item: value.label
}),
onClick: (e)=>e.stopPropagation(),
ref: composedRefs,
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Drag, {})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "flex-start",
grow: 1,
overflow: "hidden",
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 3,
justifyContent: "space-between",
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
ellipsis: true,
fontWeight: "bold",
children: value.label
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
type: "button",
variant: "ghost",
background: "transparent",
onClick: onEditFieldMeta,
withTooltip: false,
label: formatMessage({
id: translations.getTranslation('components.DraggableCard.edit.field'),
defaultMessage: 'Edit {item}'
}, {
item: value.label
}),
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Pencil, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
type: "button",
variant: "ghost",
onClick: handleRemoveField,
background: "transparent",
withTooltip: false,
label: formatMessage({
id: translations.getTranslation('components.DraggableCard.delete.field'),
defaultMessage: 'Delete {item}'
}, {
item: value.label
}),
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Cross, {})
})
]
})
]
}),
attribute?.type === 'component' ? /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
paddingTop: 3,
paddingRight: 3,
paddingBottom: 3,
paddingLeft: 0,
alignItems: "flex-start",
direction: "column",
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 4,
width: "100%",
children: components[attribute.component].layout.map((row)=>row.map(({ size, ...field })=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: size,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
alignItems: "center",
background: "neutral0",
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 3,
paddingRight: 3,
hasRadius: true,
borderColor: "neutral200",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral800",
children: field.name
})
})
}, field.name)))
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Link, {
// used to stop the edit form from appearing when we click here.
onClick: (e)=>e.stopPropagation(),
startIcon: /*#__PURE__*/ jsxRuntime.jsx(Icons.Cog, {}),
tag: reactRouterDom.NavLink,
to: `../components/${attribute.component}/configurations/edit`,
children: formatMessage({
id: translations.getTranslation('components.FieldItem.linkToComponentLayout'),
defaultMessage: "Set the component's layout"
})
})
]
}) : null,
attribute?.type === 'dynamiczone' ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
paddingTop: 3,
paddingRight: 3,
paddingBottom: 3,
paddingLeft: 0,
alignItems: "flex-start",
gap: 2,
width: "100%",
children: attribute?.components.map((uid)=>/*#__PURE__*/ jsxRuntime.jsxs(ComponentLink, {
// used to stop the edit form from appearing when we click here.
onClick: (e)=>e.stopPropagation(),
to: `../components/${uid}/configurations/edit`,
children: [
/*#__PURE__*/ jsxRuntime.jsx(ComponentIcon.ComponentIcon, {
icon: components[uid].settings.icon
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
fontSize: 1,
textColor: "neutral600",
fontWeight: "bold",
children: components[uid].settings.displayName
})
]
}, uid))
}) : null
]
})
]
}),
value.name !== TEMP_FIELD_NAME && /*#__PURE__*/ jsxRuntime.jsx(EditFieldForm.EditFieldForm, {
attribute: attribute,
name: name,
onClose: ()=>setIsModalOpen(false)
})
]
});
};
const DragButton = styledComponents.styled(designSystem.IconButton)`
height: unset;
align-self: stretch;
display: flex;
align-items: center;
padding: 0;
border: none;
background-color: transparent;
border-radius: 0px;
border-right: 1px solid ${({ theme })=>theme.colors.neutral150};
cursor: all-scroll;
svg {
width: 1.2rem;
height: 1.2rem;
}
`;
const ComponentLink = styledComponents.styled(reactRouterDom.NavLink)`
display: flex;
flex-direction: column;
align-items: center;
gap: ${({ theme })=>theme.spaces[1]};
padding: ${(props)=>props.theme.spaces[2]};
border: 1px solid ${({ theme })=>theme.colors.neutral200};
background: ${({ theme })=>theme.colors.neutral0};
width: 14rem;
border-radius: ${({ theme })=>theme.borderRadius};
text-decoration: none;
&:focus,
&:hover {
${({ theme })=>`
background-color: ${theme.colors.primary100};
border-color: ${theme.colors.primary200};
${designSystem.Typography} {
color: ${theme.colors.primary600};
}
`}
/* > ComponentIcon */
> div:first-child {
background: ${({ theme })=>theme.colors.primary200};
color: ${({ theme })=>theme.colors.primary600};
svg {
path {
fill: ${({ theme })=>theme.colors.primary600};
}
}
}
}
`;
exports.Fields = Fields;
exports.TEMP_FIELD_NAME = TEMP_FIELD_NAME;
//# sourceMappingURL=Fields.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,512 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { useForm, useField } from '@strapi/admin/strapi-admin';
import { IconButton, Typography, Flex, Box, Grid, Menu, useComposedRefs, Modal, Link } from '@strapi/design-system';
import { Plus, Drag, Pencil, Cross, Cog } from '@strapi/icons';
import { generateNKeysBetween as generateNKeysBetween$1 } from 'fractional-indexing';
import { getEmptyImage } from 'react-dnd-html5-backend';
import { useIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { ItemTypes } from '../../constants/dragAndDrop.mjs';
import { useDragAndDrop } from '../../hooks/useDragAndDrop.mjs';
import { getTranslation } from '../../utils/translations.mjs';
import { ComponentIcon } from '../ComponentIcon.mjs';
import { EditFieldForm } from './EditFieldForm.mjs';
const Fields = ({ attributes, fieldSizes, components, metadatas = {} })=>{
const { formatMessage } = useIntl();
const layout = useForm('Fields', (state)=>state.values.layout ?? []);
const onChange = useForm('Fields', (state)=>state.onChange);
const addFieldRow = useForm('Fields', (state)=>state.addFieldRow);
const removeFieldRow = useForm('Fields', (state)=>state.removeFieldRow);
const existingFields = layout.map((row)=>row.children.map((field)=>field.name)).flat();
/**
* Get the fields that are not already in the layout
* But also check that they are visible before we give users
* the option to display them. e.g. `id` is not visible.
*/ const remainingFields = Object.entries(metadatas).reduce((acc, current)=>{
const [name, { visible, ...field }] = current;
if (!existingFields.includes(name) && visible === true) {
const type = attributes[name]?.type;
const size = type ? fieldSizes[type] : 12;
acc.push({
...field,
label: field.label ?? name,
name,
size
});
}
return acc;
}, []);
const handleMoveField = ([newRowIndex, newFieldIndex], [currentRowIndex, currentFieldIndex])=>{
/**
* Because this view has the constraint that the sum of field sizes cannot be greater
* than 12, we don't use the form's method to move field rows, instead, we calculate
* the new layout and set the entire form.
*/ const newLayout = structuredClone(layout);
/**
* Remove field from the current layout space using splice so we have the item
*/ const [field] = newLayout[currentRowIndex].children.splice(currentFieldIndex, 1);
if (!field || field.name === TEMP_FIELD_NAME) {
return;
}
const newRow = newLayout[newRowIndex].children;
const [newFieldKey] = generateNKeysBetween(newRow, 1, currentFieldIndex, newFieldIndex);
/**
* Next we inject the field into it's new row at it's specified index, we then remove the spaces
* if they exist and recalculate into potentially two arrays ONLY if the sizing is now over 12,
* the row and the rest of the row that couldn't fit.
*
* for example, if i have a row of `[{size: 4}, {size: 6}]` and i add `{size: 8}` a index 0,
* the new array will look like `[{size: 8}, {size: 4}, {size: 6}]` which breaks the limit of 12,
* so instead we make two arrays for the new rows `[[{size: 8}, {size: 4}], [{size: 6}]]` which we
* then inject at the original row point with spacers included.
*/ newRow.splice(newFieldIndex, 0, {
...field,
__temp_key__: newFieldKey
});
if (newLayout[newRowIndex].children.reduce((acc, curr)=>acc + curr.size, 0) > 12) {
const recalculatedRows = chunkArray(newLayout[newRowIndex].children.filter((field)=>field.name !== TEMP_FIELD_NAME));
const rowKeys = generateNKeysBetween(newLayout, recalculatedRows.length, currentRowIndex, newRowIndex);
newLayout.splice(newRowIndex, 1, ...recalculatedRows.map((row, index)=>({
__temp_key__: rowKeys[index],
children: row
})));
}
/**
* Now we remove our spacers from the rows so we can understand what dead rows exist:
* - if there's only spacers left
* - there's nothing in the row, e.g. a size 12 field left it.
* These rows are then filtered out.
* After that, we recalculate the spacers for the rows that need them.
*/ const newLayoutWithSpacers = newLayout.map((row)=>({
...row,
children: row.children.filter((field)=>field.name !== TEMP_FIELD_NAME)
})).filter((row)=>row.children.length > 0).map((row)=>{
const totalSpaceTaken = row.children.reduce((acc, curr)=>acc + curr.size, 0);
if (totalSpaceTaken < 12) {
const [spacerKey] = generateNKeysBetween$1(row.children.at(-1)?.__temp_key__, undefined, 1);
return {
...row,
children: [
...row.children,
{
name: TEMP_FIELD_NAME,
size: 12 - totalSpaceTaken,
__temp_key__: spacerKey
}
]
};
}
return row;
});
onChange('layout', newLayoutWithSpacers);
};
const handleRemoveField = (rowIndex, fieldIndex)=>()=>{
if (layout[rowIndex].children.length === 1) {
removeFieldRow(`layout`, rowIndex);
} else {
onChange(`layout.${rowIndex}.children`, [
...layout[rowIndex].children.slice(0, fieldIndex),
...layout[rowIndex].children.slice(fieldIndex + 1)
]);
}
};
const handleAddField = (field)=>()=>{
addFieldRow('layout', {
children: [
field
]
});
};
return /*#__PURE__*/ jsxs(Flex, {
paddingTop: 6,
direction: "column",
alignItems: "stretch",
gap: 4,
children: [
/*#__PURE__*/ jsxs(Flex, {
alignItems: "flex-start",
direction: "column",
justifyContent: "space-between",
children: [
/*#__PURE__*/ jsx(Typography, {
fontWeight: "bold",
children: formatMessage({
id: getTranslation('containers.list.displayedFields'),
defaultMessage: 'Displayed fields'
})
}),
/*#__PURE__*/ jsx(Typography, {
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'containers.SettingPage.editSettings.description',
defaultMessage: 'Drag & drop the fields to build the layout'
})
})
]
}),
/*#__PURE__*/ jsx(Box, {
padding: 4,
hasRadius: true,
borderStyle: "dashed",
borderWidth: "1px",
borderColor: "neutral300",
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "stretch",
gap: 2,
children: [
layout.map((row, rowIndex)=>/*#__PURE__*/ jsx(Grid.Root, {
gap: 2,
children: row.children.map(({ size, ...field }, fieldIndex)=>/*#__PURE__*/ jsx(Grid.Item, {
col: size,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(Field, {
attribute: attributes[field.name],
components: components,
index: [
rowIndex,
fieldIndex
],
name: `layout.${rowIndex}.children.${fieldIndex}`,
onMoveField: handleMoveField,
onRemoveField: handleRemoveField(rowIndex, fieldIndex)
})
}, field.name))
}, row.__temp_key__)),
/*#__PURE__*/ jsxs(Menu.Root, {
children: [
/*#__PURE__*/ jsx(Menu.Trigger, {
startIcon: /*#__PURE__*/ jsx(Plus, {}),
endIcon: null,
disabled: remainingFields.length === 0,
fullWidth: true,
variant: "secondary",
children: formatMessage({
id: getTranslation('containers.SettingPage.add.field'),
defaultMessage: 'Insert another field'
})
}),
/*#__PURE__*/ jsx(Menu.Content, {
children: remainingFields.map((field)=>/*#__PURE__*/ jsx(Menu.Item, {
onSelect: handleAddField(field),
children: field.label
}, field.name))
})
]
})
]
})
})
]
});
};
/**
* @internal
* @description Small abstraction to solve within an array of fields where you can
* add a field to the beginning or start, move back and forth what it's index range
* should be when calculating it's new temp key
*/ const generateNKeysBetween = (field, count, currInd, newInd)=>{
const startKey = currInd > newInd ? field[newInd - 1]?.__temp_key__ : field[newInd]?.__temp_key__;
const endKey = currInd > newInd ? field[newInd]?.__temp_key__ : field[newInd + 1]?.__temp_key__;
return generateNKeysBetween$1(startKey, endKey, count);
};
/**
* @internal
* @description chunks a row of layouts by the max size we allow, 12. It does not add the
* spacers again, that should be added separately.
*/ const chunkArray = (array)=>{
const result = [];
let temp = [];
array.reduce((acc, field)=>{
if (acc + field.size > 12) {
result.push(temp);
temp = [
field
];
return field.size;
} else {
temp.push(field);
return acc + field.size;
}
}, 0);
if (temp.length > 0) {
result.push(temp);
}
return result;
};
const TEMP_FIELD_NAME = '_TEMP_';
/**
* Displays a field in the layout with drag options, also
* opens a modal to edit the details of said field.
*/ const Field = ({ attribute, components, name, index, onMoveField, onRemoveField })=>{
const [isModalOpen, setIsModalOpen] = React.useState(false);
const { formatMessage } = useIntl();
const { value } = useField(name);
const [{ isDragging }, objectRef, dropRef, dragRef, dragPreviewRef] = useDragAndDrop(true, {
dropSensitivity: 'immediate',
type: ItemTypes.EDIT_FIELD,
item: {
index,
label: value?.label,
name
},
index,
onMoveItem: onMoveField
});
React.useEffect(()=>{
dragPreviewRef(getEmptyImage(), {
captureDraggingState: false
});
}, [
dragPreviewRef
]);
const composedRefs = useComposedRefs(dragRef, objectRef);
const handleRemoveField = (e)=>{
e.preventDefault();
e.stopPropagation();
onRemoveField(e);
};
const onEditFieldMeta = (e)=>{
e.preventDefault();
e.stopPropagation();
setIsModalOpen(true);
};
const tempRefs = useComposedRefs(dropRef, objectRef);
if (!value) {
return null;
}
if (value.name === TEMP_FIELD_NAME) {
return /*#__PURE__*/ jsx(Flex, {
tag: "span",
height: "100%",
style: {
opacity: 0
},
ref: tempRefs
});
}
return /*#__PURE__*/ jsxs(Modal.Root, {
open: isModalOpen,
onOpenChange: setIsModalOpen,
children: [
/*#__PURE__*/ jsxs(Flex, {
borderColor: "neutral150",
background: "neutral100",
hasRadius: true,
style: {
opacity: isDragging ? 0.5 : 1
},
ref: dropRef,
gap: 3,
cursor: "pointer",
onClick: ()=>{
setIsModalOpen(true);
},
children: [
/*#__PURE__*/ jsx(DragButton, {
tag: "span",
withTooltip: false,
label: formatMessage({
id: getTranslation('components.DraggableCard.move.field'),
defaultMessage: 'Move {item}'
}, {
item: value.label
}),
onClick: (e)=>e.stopPropagation(),
ref: composedRefs,
children: /*#__PURE__*/ jsx(Drag, {})
}),
/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "flex-start",
grow: 1,
overflow: "hidden",
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 3,
justifyContent: "space-between",
width: "100%",
children: [
/*#__PURE__*/ jsx(Typography, {
ellipsis: true,
fontWeight: "bold",
children: value.label
}),
/*#__PURE__*/ jsxs(Flex, {
children: [
/*#__PURE__*/ jsx(IconButton, {
type: "button",
variant: "ghost",
background: "transparent",
onClick: onEditFieldMeta,
withTooltip: false,
label: formatMessage({
id: getTranslation('components.DraggableCard.edit.field'),
defaultMessage: 'Edit {item}'
}, {
item: value.label
}),
children: /*#__PURE__*/ jsx(Pencil, {})
}),
/*#__PURE__*/ jsx(IconButton, {
type: "button",
variant: "ghost",
onClick: handleRemoveField,
background: "transparent",
withTooltip: false,
label: formatMessage({
id: getTranslation('components.DraggableCard.delete.field'),
defaultMessage: 'Delete {item}'
}, {
item: value.label
}),
children: /*#__PURE__*/ jsx(Cross, {})
})
]
})
]
}),
attribute?.type === 'component' ? /*#__PURE__*/ jsxs(Flex, {
paddingTop: 3,
paddingRight: 3,
paddingBottom: 3,
paddingLeft: 0,
alignItems: "flex-start",
direction: "column",
gap: 2,
width: "100%",
children: [
/*#__PURE__*/ jsx(Grid.Root, {
gap: 4,
width: "100%",
children: components[attribute.component].layout.map((row)=>row.map(({ size, ...field })=>/*#__PURE__*/ jsx(Grid.Item, {
col: size,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(Flex, {
alignItems: "center",
background: "neutral0",
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 3,
paddingRight: 3,
hasRadius: true,
borderColor: "neutral200",
children: /*#__PURE__*/ jsx(Typography, {
textColor: "neutral800",
children: field.name
})
})
}, field.name)))
}),
/*#__PURE__*/ jsx(Link, {
// used to stop the edit form from appearing when we click here.
onClick: (e)=>e.stopPropagation(),
startIcon: /*#__PURE__*/ jsx(Cog, {}),
tag: NavLink,
to: `../components/${attribute.component}/configurations/edit`,
children: formatMessage({
id: getTranslation('components.FieldItem.linkToComponentLayout'),
defaultMessage: "Set the component's layout"
})
})
]
}) : null,
attribute?.type === 'dynamiczone' ? /*#__PURE__*/ jsx(Flex, {
paddingTop: 3,
paddingRight: 3,
paddingBottom: 3,
paddingLeft: 0,
alignItems: "flex-start",
gap: 2,
width: "100%",
children: attribute?.components.map((uid)=>/*#__PURE__*/ jsxs(ComponentLink, {
// used to stop the edit form from appearing when we click here.
onClick: (e)=>e.stopPropagation(),
to: `../components/${uid}/configurations/edit`,
children: [
/*#__PURE__*/ jsx(ComponentIcon, {
icon: components[uid].settings.icon
}),
/*#__PURE__*/ jsx(Typography, {
fontSize: 1,
textColor: "neutral600",
fontWeight: "bold",
children: components[uid].settings.displayName
})
]
}, uid))
}) : null
]
})
]
}),
value.name !== TEMP_FIELD_NAME && /*#__PURE__*/ jsx(EditFieldForm, {
attribute: attribute,
name: name,
onClose: ()=>setIsModalOpen(false)
})
]
});
};
const DragButton = styled(IconButton)`
height: unset;
align-self: stretch;
display: flex;
align-items: center;
padding: 0;
border: none;
background-color: transparent;
border-radius: 0px;
border-right: 1px solid ${({ theme })=>theme.colors.neutral150};
cursor: all-scroll;
svg {
width: 1.2rem;
height: 1.2rem;
}
`;
const ComponentLink = styled(NavLink)`
display: flex;
flex-direction: column;
align-items: center;
gap: ${({ theme })=>theme.spaces[1]};
padding: ${(props)=>props.theme.spaces[2]};
border: 1px solid ${({ theme })=>theme.colors.neutral200};
background: ${({ theme })=>theme.colors.neutral0};
width: 14rem;
border-radius: ${({ theme })=>theme.borderRadius};
text-decoration: none;
&:focus,
&:hover {
${({ theme })=>`
background-color: ${theme.colors.primary100};
border-color: ${theme.colors.primary200};
${Typography} {
color: ${theme.colors.primary600};
}
`}
/* > ComponentIcon */
> div:first-child {
background: ${({ theme })=>theme.colors.primary200};
color: ${({ theme })=>theme.colors.primary600};
svg {
path {
fill: ${({ theme })=>theme.colors.primary600};
}
}
}
}
`;
export { Fields, TEMP_FIELD_NAME };
//# sourceMappingURL=Fields.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,253 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var fractionalIndexing = require('fractional-indexing');
var pipe = require('lodash/fp/pipe');
var reactIntl = require('react-intl');
var attributes = require('../../constants/attributes.js');
var strings = require('../../utils/strings.js');
var translations = require('../../utils/translations.js');
var Fields = require('./Fields.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const ConfigurationForm = ({ attributes: attributes$1, fieldSizes, layout: editLayout, onSubmit })=>{
const { components, settings, layout, metadatas } = editLayout;
const { formatMessage } = reactIntl.useIntl();
const initialValues = React__namespace.useMemo(()=>{
const transformations = pipe(flattenPanels, replaceMainFieldWithNameOnly, extractMetadata, addTmpSpaceToLayout, addTmpKeysToLayout);
return {
layout: transformations(layout),
settings
};
}, [
layout,
settings
]);
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Root, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Main, {
children: /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Form, {
initialValues: initialValues,
onSubmit: onSubmit,
method: "PUT",
children: [
/*#__PURE__*/ jsxRuntime.jsx(Header, {
name: settings.displayName ?? ''
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Content, {
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
alignItems: "stretch",
background: "neutral0",
direction: "column",
gap: 6,
hasRadius: true,
shadow: "tableShadow",
paddingTop: 6,
paddingBottom: 6,
paddingLeft: 7,
paddingRight: 7,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "delta",
tag: "h2",
children: formatMessage({
id: translations.getTranslation('containers.SettingPage.settings'),
defaultMessage: 'Settings'
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Grid.Root, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 6,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.InputRenderer, {
type: "enumeration",
label: formatMessage({
id: translations.getTranslation('containers.SettingPage.editSettings.entry.title'),
defaultMessage: 'Entry title'
}),
hint: formatMessage({
id: translations.getTranslation('containers.SettingPage.editSettings.entry.title.description'),
defaultMessage: 'Set the display field of your entry'
}),
name: "settings.mainField",
options: Object.entries(attributes$1).reduce((acc, [key, attribute])=>{
if (!attribute) {
return acc;
}
/**
* Create the list of attributes from the schema as to which can
* be our `mainField` and dictate the display name of the schema
* we're editing.
*/ if (!attributes.ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD.includes(attribute.type)) {
acc.push({
label: key,
value: key
});
}
return acc;
}, [])
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
paddingTop: 6,
paddingBottom: 6,
col: 12,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Divider, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 12,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "delta",
tag: "h3",
children: formatMessage({
id: translations.getTranslation('containers.SettingPage.view'),
defaultMessage: 'View'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 12,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(Fields.Fields, {
attributes: attributes$1,
components: components,
fieldSizes: fieldSizes,
metadatas: metadatas
})
})
]
})
]
})
})
]
})
})
});
};
/**
* @internal
* @description Panels don't exist in the layout, so we flatten by one.
*/ const flattenPanels = (layout)=>layout.flat(1);
/**
* @internal
* @description We don't need the mainField object in the layout, we only need the name.
*/ const replaceMainFieldWithNameOnly = (layout)=>layout.map((row)=>row.map((field)=>({
...field,
mainField: field.mainField?.name
})));
/**
* @internal
* @description We extract the metadata values from the field layout, because these are editable by the user.
*/ const extractMetadata = (layout)=>{
return layout.map((row)=>row.map(({ label, disabled, hint, placeholder, size, name, mainField })=>({
label,
editable: !disabled,
description: hint,
mainField,
placeholder,
size,
name,
__temp_key__: ''
})));
};
/**
* @internal
* @description Each row of the layout has a max size of 12 (based on bootstrap grid system)
* So in order to offer a better drop zone we add the _TEMP_ div to complete the remaining substract (12 - existing)
*/ const addTmpSpaceToLayout = (layout)=>[
...layout.map((row)=>{
const totalSpaceTaken = row.reduce((acc, field)=>acc + field.size, 0);
if (totalSpaceTaken < 12) {
return [
...row,
{
name: Fields.TEMP_FIELD_NAME,
size: 12 - totalSpaceTaken,
__temp_key__: ''
}
];
}
return row;
})
];
/**
* @internal
* @description At this point of the transformations we have Field[][], but each row for the form should have a __temp_key__
* applied. This means we need to change it so `Field` is nested under the children property.
*/ const addTmpKeysToLayout = (layout)=>{
const keys = fractionalIndexing.generateNKeysBetween(undefined, undefined, layout.length);
return layout.map((row, rowIndex)=>{
const fieldKeys = fractionalIndexing.generateNKeysBetween(undefined, undefined, row.length);
return {
__temp_key__: keys[rowIndex],
children: row.map((field, fieldIndex)=>{
return {
...field,
__temp_key__: fieldKeys[fieldIndex]
};
})
};
});
};
const Header = ({ name })=>{
const { formatMessage } = reactIntl.useIntl();
const modified = strapiAdmin.useForm('Header', (state)=>state.modified);
const isSubmitting = strapiAdmin.useForm('Header', (state)=>state.isSubmitting);
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.Header, {
title: formatMessage({
id: translations.getTranslation('components.SettingsViewWrapper.pluginHeader.title'),
defaultMessage: `Configure the view - {name}`
}, {
name: strings.capitalise(name)
}),
subtitle: formatMessage({
id: translations.getTranslation('components.SettingsViewWrapper.pluginHeader.description.edit-settings'),
defaultMessage: 'Customize how the edit view will look like.'
}),
navigationAction: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.BackButton, {}),
primaryAction: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
disabled: !modified,
loading: isSubmitting,
type: "submit",
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
})
});
};
exports.ConfigurationForm = ConfigurationForm;
//# sourceMappingURL=Form.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,232 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import * as React from 'react';
import { Layouts, Form, InputRenderer, useForm, BackButton } from '@strapi/admin/strapi-admin';
import { Main, Flex, Typography, Grid, Divider, Button } from '@strapi/design-system';
import { generateNKeysBetween } from 'fractional-indexing';
import pipe from 'lodash/fp/pipe';
import { useIntl } from 'react-intl';
import { ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD } from '../../constants/attributes.mjs';
import { capitalise } from '../../utils/strings.mjs';
import { getTranslation } from '../../utils/translations.mjs';
import { Fields, TEMP_FIELD_NAME } from './Fields.mjs';
const ConfigurationForm = ({ attributes, fieldSizes, layout: editLayout, onSubmit })=>{
const { components, settings, layout, metadatas } = editLayout;
const { formatMessage } = useIntl();
const initialValues = React.useMemo(()=>{
const transformations = pipe(flattenPanels, replaceMainFieldWithNameOnly, extractMetadata, addTmpSpaceToLayout, addTmpKeysToLayout);
return {
layout: transformations(layout),
settings
};
}, [
layout,
settings
]);
return /*#__PURE__*/ jsx(Layouts.Root, {
children: /*#__PURE__*/ jsx(Main, {
children: /*#__PURE__*/ jsxs(Form, {
initialValues: initialValues,
onSubmit: onSubmit,
method: "PUT",
children: [
/*#__PURE__*/ jsx(Header, {
name: settings.displayName ?? ''
}),
/*#__PURE__*/ jsx(Layouts.Content, {
children: /*#__PURE__*/ jsxs(Flex, {
alignItems: "stretch",
background: "neutral0",
direction: "column",
gap: 6,
hasRadius: true,
shadow: "tableShadow",
paddingTop: 6,
paddingBottom: 6,
paddingLeft: 7,
paddingRight: 7,
children: [
/*#__PURE__*/ jsx(Typography, {
variant: "delta",
tag: "h2",
children: formatMessage({
id: getTranslation('containers.SettingPage.settings'),
defaultMessage: 'Settings'
})
}),
/*#__PURE__*/ jsxs(Grid.Root, {
children: [
/*#__PURE__*/ jsx(Grid.Item, {
col: 6,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(InputRenderer, {
type: "enumeration",
label: formatMessage({
id: getTranslation('containers.SettingPage.editSettings.entry.title'),
defaultMessage: 'Entry title'
}),
hint: formatMessage({
id: getTranslation('containers.SettingPage.editSettings.entry.title.description'),
defaultMessage: 'Set the display field of your entry'
}),
name: "settings.mainField",
options: Object.entries(attributes).reduce((acc, [key, attribute])=>{
if (!attribute) {
return acc;
}
/**
* Create the list of attributes from the schema as to which can
* be our `mainField` and dictate the display name of the schema
* we're editing.
*/ if (!ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD.includes(attribute.type)) {
acc.push({
label: key,
value: key
});
}
return acc;
}, [])
})
}),
/*#__PURE__*/ jsx(Grid.Item, {
paddingTop: 6,
paddingBottom: 6,
col: 12,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(Divider, {})
}),
/*#__PURE__*/ jsx(Grid.Item, {
col: 12,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(Typography, {
variant: "delta",
tag: "h3",
children: formatMessage({
id: getTranslation('containers.SettingPage.view'),
defaultMessage: 'View'
})
})
}),
/*#__PURE__*/ jsx(Grid.Item, {
col: 12,
s: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(Fields, {
attributes: attributes,
components: components,
fieldSizes: fieldSizes,
metadatas: metadatas
})
})
]
})
]
})
})
]
})
})
});
};
/**
* @internal
* @description Panels don't exist in the layout, so we flatten by one.
*/ const flattenPanels = (layout)=>layout.flat(1);
/**
* @internal
* @description We don't need the mainField object in the layout, we only need the name.
*/ const replaceMainFieldWithNameOnly = (layout)=>layout.map((row)=>row.map((field)=>({
...field,
mainField: field.mainField?.name
})));
/**
* @internal
* @description We extract the metadata values from the field layout, because these are editable by the user.
*/ const extractMetadata = (layout)=>{
return layout.map((row)=>row.map(({ label, disabled, hint, placeholder, size, name, mainField })=>({
label,
editable: !disabled,
description: hint,
mainField,
placeholder,
size,
name,
__temp_key__: ''
})));
};
/**
* @internal
* @description Each row of the layout has a max size of 12 (based on bootstrap grid system)
* So in order to offer a better drop zone we add the _TEMP_ div to complete the remaining substract (12 - existing)
*/ const addTmpSpaceToLayout = (layout)=>[
...layout.map((row)=>{
const totalSpaceTaken = row.reduce((acc, field)=>acc + field.size, 0);
if (totalSpaceTaken < 12) {
return [
...row,
{
name: TEMP_FIELD_NAME,
size: 12 - totalSpaceTaken,
__temp_key__: ''
}
];
}
return row;
})
];
/**
* @internal
* @description At this point of the transformations we have Field[][], but each row for the form should have a __temp_key__
* applied. This means we need to change it so `Field` is nested under the children property.
*/ const addTmpKeysToLayout = (layout)=>{
const keys = generateNKeysBetween(undefined, undefined, layout.length);
return layout.map((row, rowIndex)=>{
const fieldKeys = generateNKeysBetween(undefined, undefined, row.length);
return {
__temp_key__: keys[rowIndex],
children: row.map((field, fieldIndex)=>{
return {
...field,
__temp_key__: fieldKeys[fieldIndex]
};
})
};
});
};
const Header = ({ name })=>{
const { formatMessage } = useIntl();
const modified = useForm('Header', (state)=>state.modified);
const isSubmitting = useForm('Header', (state)=>state.isSubmitting);
return /*#__PURE__*/ jsx(Layouts.Header, {
title: formatMessage({
id: getTranslation('components.SettingsViewWrapper.pluginHeader.title'),
defaultMessage: `Configure the view - {name}`
}, {
name: capitalise(name)
}),
subtitle: formatMessage({
id: getTranslation('components.SettingsViewWrapper.pluginHeader.description.edit-settings'),
defaultMessage: 'Customize how the edit view will look like.'
}),
navigationAction: /*#__PURE__*/ jsx(BackButton, {}),
primaryAction: /*#__PURE__*/ jsx(Button, {
disabled: !modified,
loading: isSubmitting,
type: "submit",
children: formatMessage({
id: 'global.save',
defaultMessage: 'Save'
})
})
});
};
export { ConfigurationForm };
//# sourceMappingURL=Form.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,50 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var designSystem = require('@strapi/design-system');
var reactDnd = require('react-dnd');
function getStyle(initialOffset, currentOffset, mouseOffset) {
if (!initialOffset || !currentOffset || !mouseOffset) {
return {
display: 'none'
};
}
const { x, y } = mouseOffset;
return {
transform: `translate(${x}px, ${y}px)`
};
}
const DragLayer = ({ renderItem })=>{
const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = reactDnd.useDragLayer((monitor)=>({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
mouseOffset: monitor.getClientOffset()
}));
if (!isDragging) {
return null;
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
height: "100%",
left: 0,
position: "fixed",
pointerEvents: "none",
top: 0,
zIndex: 100,
width: "100%",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
style: getStyle(initialOffset, currentOffset, mouseOffset),
children: renderItem({
type: itemType,
item
})
})
});
};
exports.DragLayer = DragLayer;
//# sourceMappingURL=DragLayer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DragLayer.js","sources":["../../../admin/src/components/DragLayer.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Box } from '@strapi/design-system';\nimport { DragLayerMonitor, XYCoord, useDragLayer } from 'react-dnd';\n\nfunction getStyle(\n initialOffset: XYCoord | null,\n currentOffset: XYCoord | null,\n mouseOffset: XYCoord | null\n) {\n if (!initialOffset || !currentOffset || !mouseOffset) {\n return { display: 'none' };\n }\n\n const { x, y } = mouseOffset;\n\n return {\n transform: `translate(${x}px, ${y}px)`,\n };\n}\n\nexport interface DragLayerProps {\n renderItem: (item: {\n /**\n * TODO: it'd be great if we could make this a union where the type infers the item.\n */\n item: any;\n type: ReturnType<DragLayerMonitor['getItemType']>;\n }) => React.ReactNode;\n}\n\nconst DragLayer = ({ renderItem }: DragLayerProps) => {\n const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer(\n (monitor) => ({\n item: monitor.getItem(),\n itemType: monitor.getItemType(),\n initialOffset: monitor.getInitialSourceClientOffset(),\n currentOffset: monitor.getSourceClientOffset(),\n isDragging: monitor.isDragging(),\n mouseOffset: monitor.getClientOffset(),\n })\n );\n\n if (!isDragging) {\n return null;\n }\n\n return (\n <Box\n height=\"100%\"\n left={0}\n position=\"fixed\"\n pointerEvents=\"none\"\n top={0}\n zIndex={100}\n width=\"100%\"\n >\n <Box style={getStyle(initialOffset, currentOffset, mouseOffset)}>\n {renderItem({ type: itemType, item })}\n </Box>\n </Box>\n );\n};\n\nexport { DragLayer };\n"],"names":["getStyle","initialOffset","currentOffset","mouseOffset","display","x","y","transform","DragLayer","renderItem","itemType","isDragging","item","useDragLayer","monitor","getItem","getItemType","getInitialSourceClientOffset","getSourceClientOffset","getClientOffset","_jsx","Box","height","left","position","pointerEvents","top","zIndex","width","style","type"],"mappings":";;;;;;;AAKA,SAASA,QACPC,CAAAA,aAA6B,EAC7BC,aAA6B,EAC7BC,WAA2B,EAAA;AAE3B,IAAA,IAAI,CAACF,aAAAA,IAAiB,CAACC,aAAAA,IAAiB,CAACC,WAAa,EAAA;QACpD,OAAO;YAAEC,OAAS,EAAA;AAAO,SAAA;AAC3B;AAEA,IAAA,MAAM,EAAEC,CAAC,EAAEC,CAAC,EAAE,GAAGH,WAAAA;IAEjB,OAAO;QACLI,SAAW,EAAA,CAAC,UAAU,EAAEF,CAAAA,CAAE,IAAI,EAAEC,CAAAA,CAAE,GAAG;AACvC,KAAA;AACF;AAYA,MAAME,SAAY,GAAA,CAAC,EAAEC,UAAU,EAAkB,GAAA;AAC/C,IAAA,MAAM,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,IAAI,EAAEX,aAAa,EAAEC,aAAa,EAAEC,WAAW,EAAE,GAAGU,qBAChF,CAAA,CAACC,WAAa;AACZF,YAAAA,IAAAA,EAAME,QAAQC,OAAO,EAAA;AACrBL,YAAAA,QAAAA,EAAUI,QAAQE,WAAW,EAAA;AAC7Bf,YAAAA,aAAAA,EAAea,QAAQG,4BAA4B,EAAA;AACnDf,YAAAA,aAAAA,EAAeY,QAAQI,qBAAqB,EAAA;AAC5CP,YAAAA,UAAAA,EAAYG,QAAQH,UAAU,EAAA;AAC9BR,YAAAA,WAAAA,EAAaW,QAAQK,eAAe;SACtC,CAAA,CAAA;AAGF,IAAA,IAAI,CAACR,UAAY,EAAA;QACf,OAAO,IAAA;AACT;AAEA,IAAA,qBACES,cAACC,CAAAA,gBAAAA,EAAAA;QACCC,MAAO,EAAA,MAAA;QACPC,IAAM,EAAA,CAAA;QACNC,QAAS,EAAA,OAAA;QACTC,aAAc,EAAA,MAAA;QACdC,GAAK,EAAA,CAAA;QACLC,MAAQ,EAAA,GAAA;QACRC,KAAM,EAAA,MAAA;AAEN,QAAA,QAAA,gBAAAR,cAACC,CAAAA,gBAAAA,EAAAA;YAAIQ,KAAO7B,EAAAA,QAAAA,CAASC,eAAeC,aAAeC,EAAAA,WAAAA,CAAAA;sBAChDM,UAAW,CAAA;gBAAEqB,IAAMpB,EAAAA,QAAAA;AAAUE,gBAAAA;AAAK,aAAA;;;AAI3C;;;;"}

View File

@@ -0,0 +1,48 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { Box } from '@strapi/design-system';
import { useDragLayer } from 'react-dnd';
function getStyle(initialOffset, currentOffset, mouseOffset) {
if (!initialOffset || !currentOffset || !mouseOffset) {
return {
display: 'none'
};
}
const { x, y } = mouseOffset;
return {
transform: `translate(${x}px, ${y}px)`
};
}
const DragLayer = ({ renderItem })=>{
const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer((monitor)=>({
item: monitor.getItem(),
itemType: monitor.getItemType(),
initialOffset: monitor.getInitialSourceClientOffset(),
currentOffset: monitor.getSourceClientOffset(),
isDragging: monitor.isDragging(),
mouseOffset: monitor.getClientOffset()
}));
if (!isDragging) {
return null;
}
return /*#__PURE__*/ jsx(Box, {
height: "100%",
left: 0,
position: "fixed",
pointerEvents: "none",
top: 0,
zIndex: 100,
width: "100%",
children: /*#__PURE__*/ jsx(Box, {
style: getStyle(initialOffset, currentOffset, mouseOffset),
children: renderItem({
type: itemType,
item
})
})
});
};
export { DragLayer };
//# sourceMappingURL=DragLayer.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DragLayer.mjs","sources":["../../../admin/src/components/DragLayer.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Box } from '@strapi/design-system';\nimport { DragLayerMonitor, XYCoord, useDragLayer } from 'react-dnd';\n\nfunction getStyle(\n initialOffset: XYCoord | null,\n currentOffset: XYCoord | null,\n mouseOffset: XYCoord | null\n) {\n if (!initialOffset || !currentOffset || !mouseOffset) {\n return { display: 'none' };\n }\n\n const { x, y } = mouseOffset;\n\n return {\n transform: `translate(${x}px, ${y}px)`,\n };\n}\n\nexport interface DragLayerProps {\n renderItem: (item: {\n /**\n * TODO: it'd be great if we could make this a union where the type infers the item.\n */\n item: any;\n type: ReturnType<DragLayerMonitor['getItemType']>;\n }) => React.ReactNode;\n}\n\nconst DragLayer = ({ renderItem }: DragLayerProps) => {\n const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer(\n (monitor) => ({\n item: monitor.getItem(),\n itemType: monitor.getItemType(),\n initialOffset: monitor.getInitialSourceClientOffset(),\n currentOffset: monitor.getSourceClientOffset(),\n isDragging: monitor.isDragging(),\n mouseOffset: monitor.getClientOffset(),\n })\n );\n\n if (!isDragging) {\n return null;\n }\n\n return (\n <Box\n height=\"100%\"\n left={0}\n position=\"fixed\"\n pointerEvents=\"none\"\n top={0}\n zIndex={100}\n width=\"100%\"\n >\n <Box style={getStyle(initialOffset, currentOffset, mouseOffset)}>\n {renderItem({ type: itemType, item })}\n </Box>\n </Box>\n );\n};\n\nexport { DragLayer };\n"],"names":["getStyle","initialOffset","currentOffset","mouseOffset","display","x","y","transform","DragLayer","renderItem","itemType","isDragging","item","useDragLayer","monitor","getItem","getItemType","getInitialSourceClientOffset","getSourceClientOffset","getClientOffset","_jsx","Box","height","left","position","pointerEvents","top","zIndex","width","style","type"],"mappings":";;;;;AAKA,SAASA,QACPC,CAAAA,aAA6B,EAC7BC,aAA6B,EAC7BC,WAA2B,EAAA;AAE3B,IAAA,IAAI,CAACF,aAAAA,IAAiB,CAACC,aAAAA,IAAiB,CAACC,WAAa,EAAA;QACpD,OAAO;YAAEC,OAAS,EAAA;AAAO,SAAA;AAC3B;AAEA,IAAA,MAAM,EAAEC,CAAC,EAAEC,CAAC,EAAE,GAAGH,WAAAA;IAEjB,OAAO;QACLI,SAAW,EAAA,CAAC,UAAU,EAAEF,CAAAA,CAAE,IAAI,EAAEC,CAAAA,CAAE,GAAG;AACvC,KAAA;AACF;AAYA,MAAME,SAAY,GAAA,CAAC,EAAEC,UAAU,EAAkB,GAAA;AAC/C,IAAA,MAAM,EAAEC,QAAQ,EAAEC,UAAU,EAAEC,IAAI,EAAEX,aAAa,EAAEC,aAAa,EAAEC,WAAW,EAAE,GAAGU,YAChF,CAAA,CAACC,WAAa;AACZF,YAAAA,IAAAA,EAAME,QAAQC,OAAO,EAAA;AACrBL,YAAAA,QAAAA,EAAUI,QAAQE,WAAW,EAAA;AAC7Bf,YAAAA,aAAAA,EAAea,QAAQG,4BAA4B,EAAA;AACnDf,YAAAA,aAAAA,EAAeY,QAAQI,qBAAqB,EAAA;AAC5CP,YAAAA,UAAAA,EAAYG,QAAQH,UAAU,EAAA;AAC9BR,YAAAA,WAAAA,EAAaW,QAAQK,eAAe;SACtC,CAAA,CAAA;AAGF,IAAA,IAAI,CAACR,UAAY,EAAA;QACf,OAAO,IAAA;AACT;AAEA,IAAA,qBACES,GAACC,CAAAA,GAAAA,EAAAA;QACCC,MAAO,EAAA,MAAA;QACPC,IAAM,EAAA,CAAA;QACNC,QAAS,EAAA,OAAA;QACTC,aAAc,EAAA,MAAA;QACdC,GAAK,EAAA,CAAA;QACLC,MAAQ,EAAA,GAAA;QACRC,KAAM,EAAA,MAAA;AAEN,QAAA,QAAA,gBAAAR,GAACC,CAAAA,GAAAA,EAAAA;YAAIQ,KAAO7B,EAAAA,QAAAA,CAASC,eAAeC,aAAeC,EAAAA,WAAAA,CAAAA;sBAChDM,UAAW,CAAA;gBAAEqB,IAAMpB,EAAAA,QAAAA;AAAUE,gBAAAA;AAAK,aAAA;;;AAI3C;;;;"}

View File

@@ -0,0 +1,82 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var styledComponents = require('styled-components');
const CardDragPreview = ({ label, isSibling = false })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(FieldContainer, {
background: isSibling ? 'neutral100' : 'primary100',
display: "inline-flex",
gap: 3,
hasRadius: true,
justifyContent: "space-between",
$isSibling: isSibling,
"max-height": `3.2rem`,
maxWidth: "min-content",
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 3,
children: [
/*#__PURE__*/ jsxRuntime.jsx(DragButton, {
alignItems: "center",
cursor: "all-scroll",
padding: 3,
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Drag, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: isSibling ? undefined : 'primary600',
fontWeight: "bold",
ellipsis: true,
maxWidth: "7.2rem",
children: label
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(ActionBox, {
alignItems: "center",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Pencil, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(ActionBox, {
alignItems: "center",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Cross, {})
})
]
})
]
});
};
const ActionBox = styledComponents.styled(designSystem.Flex)`
height: ${({ theme })=>theme.spaces[7]};
&:last-child {
padding: 0 ${({ theme })=>theme.spaces[3]};
}
`;
const DragButton = styledComponents.styled(ActionBox)`
border-right: 1px solid ${({ theme })=>theme.colors.primary200};
svg {
width: 1.2rem;
height: 1.2rem;
}
`;
const FieldContainer = styledComponents.styled(designSystem.Flex)`
border: 1px solid
${({ theme, $isSibling })=>$isSibling ? theme.colors.neutral150 : theme.colors.primary200};
svg {
width: 1rem;
height: 1rem;
path {
fill: ${({ theme, $isSibling })=>$isSibling ? undefined : theme.colors.primary600};
}
}
`;
exports.CardDragPreview = CardDragPreview;
//# sourceMappingURL=CardDragPreview.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"CardDragPreview.js","sources":["../../../../admin/src/components/DragPreviews/CardDragPreview.tsx"],"sourcesContent":["import { Flex, FlexComponent, Typography } from '@strapi/design-system';\nimport { Cross, Drag, Pencil } from '@strapi/icons';\nimport { styled } from 'styled-components';\n\ninterface CardDragPreviewProps {\n label: string;\n isSibling?: boolean;\n}\n\nconst CardDragPreview = ({ label, isSibling = false }: CardDragPreviewProps) => {\n return (\n <FieldContainer\n background={isSibling ? 'neutral100' : 'primary100'}\n display=\"inline-flex\"\n gap={3}\n hasRadius\n justifyContent=\"space-between\"\n $isSibling={isSibling}\n max-height={`3.2rem`}\n maxWidth=\"min-content\"\n >\n <Flex gap={3}>\n <DragButton alignItems=\"center\" cursor=\"all-scroll\" padding={3}>\n <Drag />\n </DragButton>\n\n <Typography\n textColor={isSibling ? undefined : 'primary600'}\n fontWeight=\"bold\"\n ellipsis\n maxWidth=\"7.2rem\"\n >\n {label}\n </Typography>\n </Flex>\n\n <Flex>\n <ActionBox alignItems=\"center\">\n <Pencil />\n </ActionBox>\n\n <ActionBox alignItems=\"center\">\n <Cross />\n </ActionBox>\n </Flex>\n </FieldContainer>\n );\n};\n\nconst ActionBox = styled<FlexComponent>(Flex)`\n height: ${({ theme }) => theme.spaces[7]};\n\n &:last-child {\n padding: 0 ${({ theme }) => theme.spaces[3]};\n }\n`;\n\nconst DragButton = styled(ActionBox)`\n border-right: 1px solid ${({ theme }) => theme.colors.primary200};\n\n svg {\n width: 1.2rem;\n height: 1.2rem;\n }\n`;\n\nconst FieldContainer = styled<FlexComponent>(Flex)<{ $isSibling: boolean }>`\n border: 1px solid\n ${({ theme, $isSibling }) => ($isSibling ? theme.colors.neutral150 : theme.colors.primary200)};\n\n svg {\n width: 1rem;\n height: 1rem;\n\n path {\n fill: ${({ theme, $isSibling }) => ($isSibling ? undefined : theme.colors.primary600)};\n }\n }\n`;\n\nexport { CardDragPreview };\nexport type { CardDragPreviewProps };\n"],"names":["CardDragPreview","label","isSibling","_jsxs","FieldContainer","background","display","gap","hasRadius","justifyContent","$isSibling","max-height","maxWidth","Flex","_jsx","DragButton","alignItems","cursor","padding","Drag","Typography","textColor","undefined","fontWeight","ellipsis","ActionBox","Pencil","Cross","styled","theme","spaces","colors","primary200","neutral150","primary600"],"mappings":";;;;;;;AASA,MAAMA,kBAAkB,CAAC,EAAEC,KAAK,EAAEC,SAAAA,GAAY,KAAK,EAAwB,GAAA;AACzE,IAAA,qBACEC,eAACC,CAAAA,cAAAA,EAAAA;AACCC,QAAAA,UAAAA,EAAYH,YAAY,YAAe,GAAA,YAAA;QACvCI,OAAQ,EAAA,aAAA;QACRC,GAAK,EAAA,CAAA;QACLC,SAAS,EAAA,IAAA;QACTC,cAAe,EAAA,eAAA;QACfC,UAAYR,EAAAA,SAAAA;QACZS,YAAY,EAAA,CAAC,MAAM,CAAC;QACpBC,QAAS,EAAA,aAAA;;0BAETT,eAACU,CAAAA,iBAAAA,EAAAA;gBAAKN,GAAK,EAAA,CAAA;;kCACTO,cAACC,CAAAA,UAAAA,EAAAA;wBAAWC,UAAW,EAAA,QAAA;wBAASC,MAAO,EAAA,YAAA;wBAAaC,OAAS,EAAA,CAAA;AAC3D,wBAAA,QAAA,gBAAAJ,cAACK,CAAAA,UAAAA,EAAAA,EAAAA;;kCAGHL,cAACM,CAAAA,uBAAAA,EAAAA;AACCC,wBAAAA,SAAAA,EAAWnB,YAAYoB,SAAY,GAAA,YAAA;wBACnCC,UAAW,EAAA,MAAA;wBACXC,QAAQ,EAAA,IAAA;wBACRZ,QAAS,EAAA,QAAA;AAERX,wBAAAA,QAAAA,EAAAA;;;;0BAILE,eAACU,CAAAA,iBAAAA,EAAAA;;kCACCC,cAACW,CAAAA,SAAAA,EAAAA;wBAAUT,UAAW,EAAA,QAAA;AACpB,wBAAA,QAAA,gBAAAF,cAACY,CAAAA,YAAAA,EAAAA,EAAAA;;kCAGHZ,cAACW,CAAAA,SAAAA,EAAAA;wBAAUT,UAAW,EAAA,QAAA;AACpB,wBAAA,QAAA,gBAAAF,cAACa,CAAAA,WAAAA,EAAAA,EAAAA;;;;;;AAKX;AAEA,MAAMF,SAAAA,GAAYG,uBAAsBf,CAAAA,iBAAAA,CAAK;UACnC,EAAE,CAAC,EAAEgB,KAAK,EAAE,GAAKA,KAAMC,CAAAA,MAAM,CAAC,CAAA,CAAE,CAAC;;;eAG5B,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAMC,CAAAA,MAAM,CAAC,CAAA,CAAE,CAAC;;AAEhD,CAAC;AAED,MAAMf,UAAAA,GAAaa,uBAAOH,CAAAA,SAAAA,CAAU;0BACV,EAAE,CAAC,EAAEI,KAAK,EAAE,GAAKA,KAAME,CAAAA,MAAM,CAACC,UAAU,CAAC;;;;;;AAMnE,CAAC;AAED,MAAM5B,cAAAA,GAAiBwB,uBAAsBf,CAAAA,iBAAAA,CAA8B;;AAEvE,IAAA,EAAE,CAAC,EAAEgB,KAAK,EAAEnB,UAAU,EAAE,GAAMA,UAAAA,GAAamB,KAAME,CAAAA,MAAM,CAACE,UAAU,GAAGJ,MAAME,MAAM,CAACC,UAAU,CAAE;;;;;;;AAOtF,YAAA,EAAE,CAAC,EAAEH,KAAK,EAAEnB,UAAU,EAAE,GAAMA,UAAAA,GAAaY,SAAYO,GAAAA,KAAAA,CAAME,MAAM,CAACG,UAAU,CAAE;;;AAG5F,CAAC;;;;"}

View File

@@ -0,0 +1,80 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import { Flex, Typography } from '@strapi/design-system';
import { Drag, Pencil, Cross } from '@strapi/icons';
import { styled } from 'styled-components';
const CardDragPreview = ({ label, isSibling = false })=>{
return /*#__PURE__*/ jsxs(FieldContainer, {
background: isSibling ? 'neutral100' : 'primary100',
display: "inline-flex",
gap: 3,
hasRadius: true,
justifyContent: "space-between",
$isSibling: isSibling,
"max-height": `3.2rem`,
maxWidth: "min-content",
children: [
/*#__PURE__*/ jsxs(Flex, {
gap: 3,
children: [
/*#__PURE__*/ jsx(DragButton, {
alignItems: "center",
cursor: "all-scroll",
padding: 3,
children: /*#__PURE__*/ jsx(Drag, {})
}),
/*#__PURE__*/ jsx(Typography, {
textColor: isSibling ? undefined : 'primary600',
fontWeight: "bold",
ellipsis: true,
maxWidth: "7.2rem",
children: label
})
]
}),
/*#__PURE__*/ jsxs(Flex, {
children: [
/*#__PURE__*/ jsx(ActionBox, {
alignItems: "center",
children: /*#__PURE__*/ jsx(Pencil, {})
}),
/*#__PURE__*/ jsx(ActionBox, {
alignItems: "center",
children: /*#__PURE__*/ jsx(Cross, {})
})
]
})
]
});
};
const ActionBox = styled(Flex)`
height: ${({ theme })=>theme.spaces[7]};
&:last-child {
padding: 0 ${({ theme })=>theme.spaces[3]};
}
`;
const DragButton = styled(ActionBox)`
border-right: 1px solid ${({ theme })=>theme.colors.primary200};
svg {
width: 1.2rem;
height: 1.2rem;
}
`;
const FieldContainer = styled(Flex)`
border: 1px solid
${({ theme, $isSibling })=>$isSibling ? theme.colors.neutral150 : theme.colors.primary200};
svg {
width: 1rem;
height: 1rem;
path {
fill: ${({ theme, $isSibling })=>$isSibling ? undefined : theme.colors.primary600};
}
}
`;
export { CardDragPreview };
//# sourceMappingURL=CardDragPreview.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"CardDragPreview.mjs","sources":["../../../../admin/src/components/DragPreviews/CardDragPreview.tsx"],"sourcesContent":["import { Flex, FlexComponent, Typography } from '@strapi/design-system';\nimport { Cross, Drag, Pencil } from '@strapi/icons';\nimport { styled } from 'styled-components';\n\ninterface CardDragPreviewProps {\n label: string;\n isSibling?: boolean;\n}\n\nconst CardDragPreview = ({ label, isSibling = false }: CardDragPreviewProps) => {\n return (\n <FieldContainer\n background={isSibling ? 'neutral100' : 'primary100'}\n display=\"inline-flex\"\n gap={3}\n hasRadius\n justifyContent=\"space-between\"\n $isSibling={isSibling}\n max-height={`3.2rem`}\n maxWidth=\"min-content\"\n >\n <Flex gap={3}>\n <DragButton alignItems=\"center\" cursor=\"all-scroll\" padding={3}>\n <Drag />\n </DragButton>\n\n <Typography\n textColor={isSibling ? undefined : 'primary600'}\n fontWeight=\"bold\"\n ellipsis\n maxWidth=\"7.2rem\"\n >\n {label}\n </Typography>\n </Flex>\n\n <Flex>\n <ActionBox alignItems=\"center\">\n <Pencil />\n </ActionBox>\n\n <ActionBox alignItems=\"center\">\n <Cross />\n </ActionBox>\n </Flex>\n </FieldContainer>\n );\n};\n\nconst ActionBox = styled<FlexComponent>(Flex)`\n height: ${({ theme }) => theme.spaces[7]};\n\n &:last-child {\n padding: 0 ${({ theme }) => theme.spaces[3]};\n }\n`;\n\nconst DragButton = styled(ActionBox)`\n border-right: 1px solid ${({ theme }) => theme.colors.primary200};\n\n svg {\n width: 1.2rem;\n height: 1.2rem;\n }\n`;\n\nconst FieldContainer = styled<FlexComponent>(Flex)<{ $isSibling: boolean }>`\n border: 1px solid\n ${({ theme, $isSibling }) => ($isSibling ? theme.colors.neutral150 : theme.colors.primary200)};\n\n svg {\n width: 1rem;\n height: 1rem;\n\n path {\n fill: ${({ theme, $isSibling }) => ($isSibling ? undefined : theme.colors.primary600)};\n }\n }\n`;\n\nexport { CardDragPreview };\nexport type { CardDragPreviewProps };\n"],"names":["CardDragPreview","label","isSibling","_jsxs","FieldContainer","background","display","gap","hasRadius","justifyContent","$isSibling","max-height","maxWidth","Flex","_jsx","DragButton","alignItems","cursor","padding","Drag","Typography","textColor","undefined","fontWeight","ellipsis","ActionBox","Pencil","Cross","styled","theme","spaces","colors","primary200","neutral150","primary600"],"mappings":";;;;;AASA,MAAMA,kBAAkB,CAAC,EAAEC,KAAK,EAAEC,SAAAA,GAAY,KAAK,EAAwB,GAAA;AACzE,IAAA,qBACEC,IAACC,CAAAA,cAAAA,EAAAA;AACCC,QAAAA,UAAAA,EAAYH,YAAY,YAAe,GAAA,YAAA;QACvCI,OAAQ,EAAA,aAAA;QACRC,GAAK,EAAA,CAAA;QACLC,SAAS,EAAA,IAAA;QACTC,cAAe,EAAA,eAAA;QACfC,UAAYR,EAAAA,SAAAA;QACZS,YAAY,EAAA,CAAC,MAAM,CAAC;QACpBC,QAAS,EAAA,aAAA;;0BAETT,IAACU,CAAAA,IAAAA,EAAAA;gBAAKN,GAAK,EAAA,CAAA;;kCACTO,GAACC,CAAAA,UAAAA,EAAAA;wBAAWC,UAAW,EAAA,QAAA;wBAASC,MAAO,EAAA,YAAA;wBAAaC,OAAS,EAAA,CAAA;AAC3D,wBAAA,QAAA,gBAAAJ,GAACK,CAAAA,IAAAA,EAAAA,EAAAA;;kCAGHL,GAACM,CAAAA,UAAAA,EAAAA;AACCC,wBAAAA,SAAAA,EAAWnB,YAAYoB,SAAY,GAAA,YAAA;wBACnCC,UAAW,EAAA,MAAA;wBACXC,QAAQ,EAAA,IAAA;wBACRZ,QAAS,EAAA,QAAA;AAERX,wBAAAA,QAAAA,EAAAA;;;;0BAILE,IAACU,CAAAA,IAAAA,EAAAA;;kCACCC,GAACW,CAAAA,SAAAA,EAAAA;wBAAUT,UAAW,EAAA,QAAA;AACpB,wBAAA,QAAA,gBAAAF,GAACY,CAAAA,MAAAA,EAAAA,EAAAA;;kCAGHZ,GAACW,CAAAA,SAAAA,EAAAA;wBAAUT,UAAW,EAAA,QAAA;AACpB,wBAAA,QAAA,gBAAAF,GAACa,CAAAA,KAAAA,EAAAA,EAAAA;;;;;;AAKX;AAEA,MAAMF,SAAAA,GAAYG,MAAsBf,CAAAA,IAAAA,CAAK;UACnC,EAAE,CAAC,EAAEgB,KAAK,EAAE,GAAKA,KAAMC,CAAAA,MAAM,CAAC,CAAA,CAAE,CAAC;;;eAG5B,EAAE,CAAC,EAAED,KAAK,EAAE,GAAKA,KAAMC,CAAAA,MAAM,CAAC,CAAA,CAAE,CAAC;;AAEhD,CAAC;AAED,MAAMf,UAAAA,GAAaa,MAAOH,CAAAA,SAAAA,CAAU;0BACV,EAAE,CAAC,EAAEI,KAAK,EAAE,GAAKA,KAAME,CAAAA,MAAM,CAACC,UAAU,CAAC;;;;;;AAMnE,CAAC;AAED,MAAM5B,cAAAA,GAAiBwB,MAAsBf,CAAAA,IAAAA,CAA8B;;AAEvE,IAAA,EAAE,CAAC,EAAEgB,KAAK,EAAEnB,UAAU,EAAE,GAAMA,UAAAA,GAAamB,KAAME,CAAAA,MAAM,CAACE,UAAU,GAAGJ,MAAME,MAAM,CAACC,UAAU,CAAE;;;;;;;AAOtF,YAAA,EAAE,CAAC,EAAEH,KAAK,EAAEnB,UAAU,EAAE,GAAMA,UAAAA,GAAaY,SAAYO,GAAAA,KAAAA,CAAME,MAAM,CAACG,UAAU,CAAE;;;AAG5F,CAAC;;;;"}

View File

@@ -0,0 +1,83 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var styledComponents = require('styled-components');
const ComponentDragPreview = ({ displayedValue })=>{
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
background: "neutral0",
borderColor: "neutral200",
justifyContent: "space-between",
gap: 3,
padding: 3,
width: "30rem",
children: [
/*#__PURE__*/ jsxRuntime.jsx(ToggleButton, {
type: "button",
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 6,
children: [
/*#__PURE__*/ jsxRuntime.jsx(DropdownIconWrapper, {
alignItems: "center",
justifyContent: "center",
background: "neutral200",
height: "3.2rem",
width: "3.2rem",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.CaretDown, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
maxWidth: "15rem",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral700",
ellipsis: true,
children: displayedValue
})
})
]
})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
withTooltip: false,
label: "",
variant: "ghost",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Trash, {})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
withTooltip: false,
label: "",
variant: "ghost",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Drag, {})
})
]
})
]
});
};
const DropdownIconWrapper = styledComponents.styled(designSystem.Flex)`
border-radius: 50%;
svg {
height: 0.6rem;
width: 1.1rem;
> path {
fill: ${({ theme })=>theme.colors.neutral600};
}
}
`;
// TODO: we shouldn't have to reset a whole button
const ToggleButton = styledComponents.styled.button`
border: none;
background: transparent;
display: block;
width: 100%;
text-align: unset;
padding: 0;
`;
exports.ComponentDragPreview = ComponentDragPreview;
//# sourceMappingURL=ComponentDragPreview.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ComponentDragPreview.js","sources":["../../../../admin/src/components/DragPreviews/ComponentDragPreview.tsx"],"sourcesContent":["import { Flex, FlexComponent, IconButton, Typography } from '@strapi/design-system';\nimport { CaretDown, Drag, Trash } from '@strapi/icons';\nimport { styled } from 'styled-components';\n\ninterface ComponentDragPreviewProps {\n displayedValue: string;\n}\n\nconst ComponentDragPreview = ({ displayedValue }: ComponentDragPreviewProps) => {\n return (\n <Flex\n background=\"neutral0\"\n borderColor=\"neutral200\"\n justifyContent=\"space-between\"\n gap={3}\n padding={3}\n width=\"30rem\"\n >\n <ToggleButton type=\"button\">\n <Flex gap={6}>\n <DropdownIconWrapper\n alignItems=\"center\"\n justifyContent=\"center\"\n background=\"neutral200\"\n height=\"3.2rem\"\n width=\"3.2rem\"\n >\n <CaretDown />\n </DropdownIconWrapper>\n\n <Flex maxWidth=\"15rem\">\n <Typography textColor=\"neutral700\" ellipsis>\n {displayedValue}\n </Typography>\n </Flex>\n </Flex>\n </ToggleButton>\n\n <Flex gap={2}>\n <IconButton withTooltip={false} label=\"\" variant=\"ghost\">\n <Trash />\n </IconButton>\n\n <IconButton withTooltip={false} label=\"\" variant=\"ghost\">\n <Drag />\n </IconButton>\n </Flex>\n </Flex>\n );\n};\n\nconst DropdownIconWrapper = styled<FlexComponent>(Flex)`\n border-radius: 50%;\n\n svg {\n height: 0.6rem;\n width: 1.1rem;\n > path {\n fill: ${({ theme }) => theme.colors.neutral600};\n }\n }\n`;\n\n// TODO: we shouldn't have to reset a whole button\nconst ToggleButton = styled.button`\n border: none;\n background: transparent;\n display: block;\n width: 100%;\n text-align: unset;\n padding: 0;\n`;\n\nexport { ComponentDragPreview };\nexport type { ComponentDragPreviewProps };\n"],"names":["ComponentDragPreview","displayedValue","_jsxs","Flex","background","borderColor","justifyContent","gap","padding","width","_jsx","ToggleButton","type","DropdownIconWrapper","alignItems","height","CaretDown","maxWidth","Typography","textColor","ellipsis","IconButton","withTooltip","label","variant","Trash","Drag","styled","theme","colors","neutral600","button"],"mappings":";;;;;;;AAQA,MAAMA,oBAAuB,GAAA,CAAC,EAAEC,cAAc,EAA6B,GAAA;AACzE,IAAA,qBACEC,eAACC,CAAAA,iBAAAA,EAAAA;QACCC,UAAW,EAAA,UAAA;QACXC,WAAY,EAAA,YAAA;QACZC,cAAe,EAAA,eAAA;QACfC,GAAK,EAAA,CAAA;QACLC,OAAS,EAAA,CAAA;QACTC,KAAM,EAAA,OAAA;;0BAENC,cAACC,CAAAA,YAAAA,EAAAA;gBAAaC,IAAK,EAAA,QAAA;AACjB,gBAAA,QAAA,gBAAAV,eAACC,CAAAA,iBAAAA,EAAAA;oBAAKI,GAAK,EAAA,CAAA;;sCACTG,cAACG,CAAAA,mBAAAA,EAAAA;4BACCC,UAAW,EAAA,QAAA;4BACXR,cAAe,EAAA,QAAA;4BACfF,UAAW,EAAA,YAAA;4BACXW,MAAO,EAAA,QAAA;4BACPN,KAAM,EAAA,QAAA;AAEN,4BAAA,QAAA,gBAAAC,cAACM,CAAAA,eAAAA,EAAAA,EAAAA;;sCAGHN,cAACP,CAAAA,iBAAAA,EAAAA;4BAAKc,QAAS,EAAA,OAAA;AACb,4BAAA,QAAA,gBAAAP,cAACQ,CAAAA,uBAAAA,EAAAA;gCAAWC,SAAU,EAAA,YAAA;gCAAaC,QAAQ,EAAA,IAAA;AACxCnB,gCAAAA,QAAAA,EAAAA;;;;;;0BAMTC,eAACC,CAAAA,iBAAAA,EAAAA;gBAAKI,GAAK,EAAA,CAAA;;kCACTG,cAACW,CAAAA,uBAAAA,EAAAA;wBAAWC,WAAa,EAAA,KAAA;wBAAOC,KAAM,EAAA,EAAA;wBAAGC,OAAQ,EAAA,OAAA;AAC/C,wBAAA,QAAA,gBAAAd,cAACe,CAAAA,WAAAA,EAAAA,EAAAA;;kCAGHf,cAACW,CAAAA,uBAAAA,EAAAA;wBAAWC,WAAa,EAAA,KAAA;wBAAOC,KAAM,EAAA,EAAA;wBAAGC,OAAQ,EAAA,OAAA;AAC/C,wBAAA,QAAA,gBAAAd,cAACgB,CAAAA,UAAAA,EAAAA,EAAAA;;;;;;AAKX;AAEA,MAAMb,mBAAAA,GAAsBc,uBAAsBxB,CAAAA,iBAAAA,CAAK;;;;;;;YAO3C,EAAE,CAAC,EAAEyB,KAAK,EAAE,GAAKA,KAAMC,CAAAA,MAAM,CAACC,UAAU,CAAC;;;AAGrD,CAAC;AAED;AACA,MAAMnB,YAAAA,GAAegB,uBAAOI,CAAAA,MAAM;;;;;;;AAOlC,CAAC;;;;"}

View File

@@ -0,0 +1,81 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import { Flex, Typography, IconButton } from '@strapi/design-system';
import { CaretDown, Trash, Drag } from '@strapi/icons';
import { styled } from 'styled-components';
const ComponentDragPreview = ({ displayedValue })=>{
return /*#__PURE__*/ jsxs(Flex, {
background: "neutral0",
borderColor: "neutral200",
justifyContent: "space-between",
gap: 3,
padding: 3,
width: "30rem",
children: [
/*#__PURE__*/ jsx(ToggleButton, {
type: "button",
children: /*#__PURE__*/ jsxs(Flex, {
gap: 6,
children: [
/*#__PURE__*/ jsx(DropdownIconWrapper, {
alignItems: "center",
justifyContent: "center",
background: "neutral200",
height: "3.2rem",
width: "3.2rem",
children: /*#__PURE__*/ jsx(CaretDown, {})
}),
/*#__PURE__*/ jsx(Flex, {
maxWidth: "15rem",
children: /*#__PURE__*/ jsx(Typography, {
textColor: "neutral700",
ellipsis: true,
children: displayedValue
})
})
]
})
}),
/*#__PURE__*/ jsxs(Flex, {
gap: 2,
children: [
/*#__PURE__*/ jsx(IconButton, {
withTooltip: false,
label: "",
variant: "ghost",
children: /*#__PURE__*/ jsx(Trash, {})
}),
/*#__PURE__*/ jsx(IconButton, {
withTooltip: false,
label: "",
variant: "ghost",
children: /*#__PURE__*/ jsx(Drag, {})
})
]
})
]
});
};
const DropdownIconWrapper = styled(Flex)`
border-radius: 50%;
svg {
height: 0.6rem;
width: 1.1rem;
> path {
fill: ${({ theme })=>theme.colors.neutral600};
}
}
`;
// TODO: we shouldn't have to reset a whole button
const ToggleButton = styled.button`
border: none;
background: transparent;
display: block;
width: 100%;
text-align: unset;
padding: 0;
`;
export { ComponentDragPreview };
//# sourceMappingURL=ComponentDragPreview.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ComponentDragPreview.mjs","sources":["../../../../admin/src/components/DragPreviews/ComponentDragPreview.tsx"],"sourcesContent":["import { Flex, FlexComponent, IconButton, Typography } from '@strapi/design-system';\nimport { CaretDown, Drag, Trash } from '@strapi/icons';\nimport { styled } from 'styled-components';\n\ninterface ComponentDragPreviewProps {\n displayedValue: string;\n}\n\nconst ComponentDragPreview = ({ displayedValue }: ComponentDragPreviewProps) => {\n return (\n <Flex\n background=\"neutral0\"\n borderColor=\"neutral200\"\n justifyContent=\"space-between\"\n gap={3}\n padding={3}\n width=\"30rem\"\n >\n <ToggleButton type=\"button\">\n <Flex gap={6}>\n <DropdownIconWrapper\n alignItems=\"center\"\n justifyContent=\"center\"\n background=\"neutral200\"\n height=\"3.2rem\"\n width=\"3.2rem\"\n >\n <CaretDown />\n </DropdownIconWrapper>\n\n <Flex maxWidth=\"15rem\">\n <Typography textColor=\"neutral700\" ellipsis>\n {displayedValue}\n </Typography>\n </Flex>\n </Flex>\n </ToggleButton>\n\n <Flex gap={2}>\n <IconButton withTooltip={false} label=\"\" variant=\"ghost\">\n <Trash />\n </IconButton>\n\n <IconButton withTooltip={false} label=\"\" variant=\"ghost\">\n <Drag />\n </IconButton>\n </Flex>\n </Flex>\n );\n};\n\nconst DropdownIconWrapper = styled<FlexComponent>(Flex)`\n border-radius: 50%;\n\n svg {\n height: 0.6rem;\n width: 1.1rem;\n > path {\n fill: ${({ theme }) => theme.colors.neutral600};\n }\n }\n`;\n\n// TODO: we shouldn't have to reset a whole button\nconst ToggleButton = styled.button`\n border: none;\n background: transparent;\n display: block;\n width: 100%;\n text-align: unset;\n padding: 0;\n`;\n\nexport { ComponentDragPreview };\nexport type { ComponentDragPreviewProps };\n"],"names":["ComponentDragPreview","displayedValue","_jsxs","Flex","background","borderColor","justifyContent","gap","padding","width","_jsx","ToggleButton","type","DropdownIconWrapper","alignItems","height","CaretDown","maxWidth","Typography","textColor","ellipsis","IconButton","withTooltip","label","variant","Trash","Drag","styled","theme","colors","neutral600","button"],"mappings":";;;;;AAQA,MAAMA,oBAAuB,GAAA,CAAC,EAAEC,cAAc,EAA6B,GAAA;AACzE,IAAA,qBACEC,IAACC,CAAAA,IAAAA,EAAAA;QACCC,UAAW,EAAA,UAAA;QACXC,WAAY,EAAA,YAAA;QACZC,cAAe,EAAA,eAAA;QACfC,GAAK,EAAA,CAAA;QACLC,OAAS,EAAA,CAAA;QACTC,KAAM,EAAA,OAAA;;0BAENC,GAACC,CAAAA,YAAAA,EAAAA;gBAAaC,IAAK,EAAA,QAAA;AACjB,gBAAA,QAAA,gBAAAV,IAACC,CAAAA,IAAAA,EAAAA;oBAAKI,GAAK,EAAA,CAAA;;sCACTG,GAACG,CAAAA,mBAAAA,EAAAA;4BACCC,UAAW,EAAA,QAAA;4BACXR,cAAe,EAAA,QAAA;4BACfF,UAAW,EAAA,YAAA;4BACXW,MAAO,EAAA,QAAA;4BACPN,KAAM,EAAA,QAAA;AAEN,4BAAA,QAAA,gBAAAC,GAACM,CAAAA,SAAAA,EAAAA,EAAAA;;sCAGHN,GAACP,CAAAA,IAAAA,EAAAA;4BAAKc,QAAS,EAAA,OAAA;AACb,4BAAA,QAAA,gBAAAP,GAACQ,CAAAA,UAAAA,EAAAA;gCAAWC,SAAU,EAAA,YAAA;gCAAaC,QAAQ,EAAA,IAAA;AACxCnB,gCAAAA,QAAAA,EAAAA;;;;;;0BAMTC,IAACC,CAAAA,IAAAA,EAAAA;gBAAKI,GAAK,EAAA,CAAA;;kCACTG,GAACW,CAAAA,UAAAA,EAAAA;wBAAWC,WAAa,EAAA,KAAA;wBAAOC,KAAM,EAAA,EAAA;wBAAGC,OAAQ,EAAA,OAAA;AAC/C,wBAAA,QAAA,gBAAAd,GAACe,CAAAA,KAAAA,EAAAA,EAAAA;;kCAGHf,GAACW,CAAAA,UAAAA,EAAAA;wBAAWC,WAAa,EAAA,KAAA;wBAAOC,KAAM,EAAA,EAAA;wBAAGC,OAAQ,EAAA,OAAA;AAC/C,wBAAA,QAAA,gBAAAd,GAACgB,CAAAA,IAAAA,EAAAA,EAAAA;;;;;;AAKX;AAEA,MAAMb,mBAAAA,GAAsBc,MAAsBxB,CAAAA,IAAAA,CAAK;;;;;;;YAO3C,EAAE,CAAC,EAAEyB,KAAK,EAAE,GAAKA,KAAMC,CAAAA,MAAM,CAACC,UAAU,CAAC;;;AAGrD,CAAC;AAED;AACA,MAAMnB,YAAAA,GAAegB,MAAOI,CAAAA,MAAM;;;;;;;AAOlC,CAAC;;;;"}

View File

@@ -0,0 +1,73 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var DocumentStatus = require('../../pages/EditView/components/DocumentStatus.js');
var Relations = require('../../pages/EditView/components/FormInputs/Relations/Relations.js');
const RelationDragPreview = ({ status, displayedValue, width })=>{
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
style: {
width
},
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 2,
paddingRight: 4,
hasRadius: true,
borderWidth: 1,
background: "neutral0",
borderColor: "neutral200",
justifyContent: "space-between",
gap: 4,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(Relations.FlexWrapper, {
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
withTooltip: false,
label: "",
variant: "ghost",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Drag, {})
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
width: "100%",
minWidth: 0,
justifyContent: "space-between",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
minWidth: 0,
paddingTop: 1,
paddingBottom: 1,
paddingRight: 4,
children: /*#__PURE__*/ jsxRuntime.jsx(Relations.LinkEllipsis, {
href: "",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "primary600",
ellipsis: true,
children: displayedValue
})
})
}),
status ? /*#__PURE__*/ jsxRuntime.jsx(DocumentStatus.DocumentStatus, {
status: status
}) : null
]
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(Relations.DisconnectButton, {
type: "button",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Cross, {
width: "12px"
})
})
]
})
});
};
exports.RelationDragPreview = RelationDragPreview;
//# sourceMappingURL=RelationDragPreview.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RelationDragPreview.js","sources":["../../../../admin/src/components/DragPreviews/RelationDragPreview.tsx"],"sourcesContent":["import { Box, Flex, IconButton, Typography } from '@strapi/design-system';\nimport { Cross, Drag } from '@strapi/icons';\n\nimport { DocumentStatus } from '../../pages/EditView/components/DocumentStatus';\nimport {\n DisconnectButton,\n LinkEllipsis,\n FlexWrapper,\n} from '../../pages/EditView/components/FormInputs/Relations/Relations';\n\nimport type { Data } from '@strapi/types';\n\ninterface RelationDragPreviewProps {\n status?: string;\n displayedValue: string;\n id: Data.ID;\n index: number;\n width: number;\n}\n\nconst RelationDragPreview = ({ status, displayedValue, width }: RelationDragPreviewProps) => {\n return (\n <Box style={{ width }}>\n <Flex\n paddingTop={2}\n paddingBottom={2}\n paddingLeft={2}\n paddingRight={4}\n hasRadius\n borderWidth={1}\n background=\"neutral0\"\n borderColor=\"neutral200\"\n justifyContent=\"space-between\"\n gap={4}\n >\n <FlexWrapper gap={1}>\n <IconButton withTooltip={false} label=\"\" variant=\"ghost\">\n <Drag />\n </IconButton>\n <Flex width=\"100%\" minWidth={0} justifyContent=\"space-between\">\n <Box minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>\n <LinkEllipsis href=\"\">\n <Typography textColor=\"primary600\" ellipsis>\n {displayedValue}\n </Typography>\n </LinkEllipsis>\n </Box>\n {status ? <DocumentStatus status={status} /> : null}\n </Flex>\n </FlexWrapper>\n <DisconnectButton type=\"button\">\n <Cross width=\"12px\" />\n </DisconnectButton>\n </Flex>\n </Box>\n );\n};\n\nexport { RelationDragPreview };\nexport type { RelationDragPreviewProps };\n"],"names":["RelationDragPreview","status","displayedValue","width","_jsx","Box","style","_jsxs","Flex","paddingTop","paddingBottom","paddingLeft","paddingRight","hasRadius","borderWidth","background","borderColor","justifyContent","gap","FlexWrapper","IconButton","withTooltip","label","variant","Drag","minWidth","LinkEllipsis","href","Typography","textColor","ellipsis","DocumentStatus","DisconnectButton","type","Cross"],"mappings":";;;;;;;;AAoBMA,MAAAA,mBAAAA,GAAsB,CAAC,EAAEC,MAAM,EAAEC,cAAc,EAAEC,KAAK,EAA4B,GAAA;AACtF,IAAA,qBACEC,cAACC,CAAAA,gBAAAA,EAAAA;QAAIC,KAAO,EAAA;AAAEH,YAAAA;AAAM,SAAA;AAClB,QAAA,QAAA,gBAAAI,eAACC,CAAAA,iBAAAA,EAAAA;YACCC,UAAY,EAAA,CAAA;YACZC,aAAe,EAAA,CAAA;YACfC,WAAa,EAAA,CAAA;YACbC,YAAc,EAAA,CAAA;YACdC,SAAS,EAAA,IAAA;YACTC,WAAa,EAAA,CAAA;YACbC,UAAW,EAAA,UAAA;YACXC,WAAY,EAAA,YAAA;YACZC,cAAe,EAAA,eAAA;YACfC,GAAK,EAAA,CAAA;;8BAELX,eAACY,CAAAA,qBAAAA,EAAAA;oBAAYD,GAAK,EAAA,CAAA;;sCAChBd,cAACgB,CAAAA,uBAAAA,EAAAA;4BAAWC,WAAa,EAAA,KAAA;4BAAOC,KAAM,EAAA,EAAA;4BAAGC,OAAQ,EAAA,OAAA;AAC/C,4BAAA,QAAA,gBAAAnB,cAACoB,CAAAA,UAAAA,EAAAA,EAAAA;;sCAEHjB,eAACC,CAAAA,iBAAAA,EAAAA;4BAAKL,KAAM,EAAA,MAAA;4BAAOsB,QAAU,EAAA,CAAA;4BAAGR,cAAe,EAAA,eAAA;;8CAC7Cb,cAACC,CAAAA,gBAAAA,EAAAA;oCAAIoB,QAAU,EAAA,CAAA;oCAAGhB,UAAY,EAAA,CAAA;oCAAGC,aAAe,EAAA,CAAA;oCAAGE,YAAc,EAAA,CAAA;AAC/D,oCAAA,QAAA,gBAAAR,cAACsB,CAAAA,sBAAAA,EAAAA;wCAAaC,IAAK,EAAA,EAAA;AACjB,wCAAA,QAAA,gBAAAvB,cAACwB,CAAAA,uBAAAA,EAAAA;4CAAWC,SAAU,EAAA,YAAA;4CAAaC,QAAQ,EAAA,IAAA;AACxC5B,4CAAAA,QAAAA,EAAAA;;;;AAIND,gCAAAA,MAAAA,iBAASG,cAAC2B,CAAAA,6BAAAA,EAAAA;oCAAe9B,MAAQA,EAAAA;AAAa,iCAAA,CAAA,GAAA;;;;;8BAGnDG,cAAC4B,CAAAA,0BAAAA,EAAAA;oBAAiBC,IAAK,EAAA,QAAA;AACrB,oBAAA,QAAA,gBAAA7B,cAAC8B,CAAAA,WAAAA,EAAAA;wBAAM/B,KAAM,EAAA;;;;;;AAKvB;;;;"}

View File

@@ -0,0 +1,71 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { Box, Flex, IconButton, Typography } from '@strapi/design-system';
import { Drag, Cross } from '@strapi/icons';
import { DocumentStatus } from '../../pages/EditView/components/DocumentStatus.mjs';
import { FlexWrapper, LinkEllipsis, DisconnectButton } from '../../pages/EditView/components/FormInputs/Relations/Relations.mjs';
const RelationDragPreview = ({ status, displayedValue, width })=>{
return /*#__PURE__*/ jsx(Box, {
style: {
width
},
children: /*#__PURE__*/ jsxs(Flex, {
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 2,
paddingRight: 4,
hasRadius: true,
borderWidth: 1,
background: "neutral0",
borderColor: "neutral200",
justifyContent: "space-between",
gap: 4,
children: [
/*#__PURE__*/ jsxs(FlexWrapper, {
gap: 1,
children: [
/*#__PURE__*/ jsx(IconButton, {
withTooltip: false,
label: "",
variant: "ghost",
children: /*#__PURE__*/ jsx(Drag, {})
}),
/*#__PURE__*/ jsxs(Flex, {
width: "100%",
minWidth: 0,
justifyContent: "space-between",
children: [
/*#__PURE__*/ jsx(Box, {
minWidth: 0,
paddingTop: 1,
paddingBottom: 1,
paddingRight: 4,
children: /*#__PURE__*/ jsx(LinkEllipsis, {
href: "",
children: /*#__PURE__*/ jsx(Typography, {
textColor: "primary600",
ellipsis: true,
children: displayedValue
})
})
}),
status ? /*#__PURE__*/ jsx(DocumentStatus, {
status: status
}) : null
]
})
]
}),
/*#__PURE__*/ jsx(DisconnectButton, {
type: "button",
children: /*#__PURE__*/ jsx(Cross, {
width: "12px"
})
})
]
})
});
};
export { RelationDragPreview };
//# sourceMappingURL=RelationDragPreview.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RelationDragPreview.mjs","sources":["../../../../admin/src/components/DragPreviews/RelationDragPreview.tsx"],"sourcesContent":["import { Box, Flex, IconButton, Typography } from '@strapi/design-system';\nimport { Cross, Drag } from '@strapi/icons';\n\nimport { DocumentStatus } from '../../pages/EditView/components/DocumentStatus';\nimport {\n DisconnectButton,\n LinkEllipsis,\n FlexWrapper,\n} from '../../pages/EditView/components/FormInputs/Relations/Relations';\n\nimport type { Data } from '@strapi/types';\n\ninterface RelationDragPreviewProps {\n status?: string;\n displayedValue: string;\n id: Data.ID;\n index: number;\n width: number;\n}\n\nconst RelationDragPreview = ({ status, displayedValue, width }: RelationDragPreviewProps) => {\n return (\n <Box style={{ width }}>\n <Flex\n paddingTop={2}\n paddingBottom={2}\n paddingLeft={2}\n paddingRight={4}\n hasRadius\n borderWidth={1}\n background=\"neutral0\"\n borderColor=\"neutral200\"\n justifyContent=\"space-between\"\n gap={4}\n >\n <FlexWrapper gap={1}>\n <IconButton withTooltip={false} label=\"\" variant=\"ghost\">\n <Drag />\n </IconButton>\n <Flex width=\"100%\" minWidth={0} justifyContent=\"space-between\">\n <Box minWidth={0} paddingTop={1} paddingBottom={1} paddingRight={4}>\n <LinkEllipsis href=\"\">\n <Typography textColor=\"primary600\" ellipsis>\n {displayedValue}\n </Typography>\n </LinkEllipsis>\n </Box>\n {status ? <DocumentStatus status={status} /> : null}\n </Flex>\n </FlexWrapper>\n <DisconnectButton type=\"button\">\n <Cross width=\"12px\" />\n </DisconnectButton>\n </Flex>\n </Box>\n );\n};\n\nexport { RelationDragPreview };\nexport type { RelationDragPreviewProps };\n"],"names":["RelationDragPreview","status","displayedValue","width","_jsx","Box","style","_jsxs","Flex","paddingTop","paddingBottom","paddingLeft","paddingRight","hasRadius","borderWidth","background","borderColor","justifyContent","gap","FlexWrapper","IconButton","withTooltip","label","variant","Drag","minWidth","LinkEllipsis","href","Typography","textColor","ellipsis","DocumentStatus","DisconnectButton","type","Cross"],"mappings":";;;;;;AAoBMA,MAAAA,mBAAAA,GAAsB,CAAC,EAAEC,MAAM,EAAEC,cAAc,EAAEC,KAAK,EAA4B,GAAA;AACtF,IAAA,qBACEC,GAACC,CAAAA,GAAAA,EAAAA;QAAIC,KAAO,EAAA;AAAEH,YAAAA;AAAM,SAAA;AAClB,QAAA,QAAA,gBAAAI,IAACC,CAAAA,IAAAA,EAAAA;YACCC,UAAY,EAAA,CAAA;YACZC,aAAe,EAAA,CAAA;YACfC,WAAa,EAAA,CAAA;YACbC,YAAc,EAAA,CAAA;YACdC,SAAS,EAAA,IAAA;YACTC,WAAa,EAAA,CAAA;YACbC,UAAW,EAAA,UAAA;YACXC,WAAY,EAAA,YAAA;YACZC,cAAe,EAAA,eAAA;YACfC,GAAK,EAAA,CAAA;;8BAELX,IAACY,CAAAA,WAAAA,EAAAA;oBAAYD,GAAK,EAAA,CAAA;;sCAChBd,GAACgB,CAAAA,UAAAA,EAAAA;4BAAWC,WAAa,EAAA,KAAA;4BAAOC,KAAM,EAAA,EAAA;4BAAGC,OAAQ,EAAA,OAAA;AAC/C,4BAAA,QAAA,gBAAAnB,GAACoB,CAAAA,IAAAA,EAAAA,EAAAA;;sCAEHjB,IAACC,CAAAA,IAAAA,EAAAA;4BAAKL,KAAM,EAAA,MAAA;4BAAOsB,QAAU,EAAA,CAAA;4BAAGR,cAAe,EAAA,eAAA;;8CAC7Cb,GAACC,CAAAA,GAAAA,EAAAA;oCAAIoB,QAAU,EAAA,CAAA;oCAAGhB,UAAY,EAAA,CAAA;oCAAGC,aAAe,EAAA,CAAA;oCAAGE,YAAc,EAAA,CAAA;AAC/D,oCAAA,QAAA,gBAAAR,GAACsB,CAAAA,YAAAA,EAAAA;wCAAaC,IAAK,EAAA,EAAA;AACjB,wCAAA,QAAA,gBAAAvB,GAACwB,CAAAA,UAAAA,EAAAA;4CAAWC,SAAU,EAAA,YAAA;4CAAaC,QAAQ,EAAA,IAAA;AACxC5B,4CAAAA,QAAAA,EAAAA;;;;AAIND,gCAAAA,MAAAA,iBAASG,GAAC2B,CAAAA,cAAAA,EAAAA;oCAAe9B,MAAQA,EAAAA;AAAa,iCAAA,CAAA,GAAA;;;;;8BAGnDG,GAAC4B,CAAAA,gBAAAA,EAAAA;oBAAiBC,IAAK,EAAA,QAAA;AACrB,oBAAA,QAAA,gBAAA7B,GAAC8B,CAAAA,KAAAA,EAAAA;wBAAM/B,KAAM,EAAA;;;;;;AAKvB;;;;"}

View File

@@ -0,0 +1,58 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Symbols = require('@strapi/icons/symbols');
const iconByTypes = {
biginteger: /*#__PURE__*/ jsxRuntime.jsx(Symbols.NumberField, {}),
boolean: /*#__PURE__*/ jsxRuntime.jsx(Symbols.BooleanField, {}),
date: /*#__PURE__*/ jsxRuntime.jsx(Symbols.DateField, {}),
datetime: /*#__PURE__*/ jsxRuntime.jsx(Symbols.DateField, {}),
decimal: /*#__PURE__*/ jsxRuntime.jsx(Symbols.NumberField, {}),
email: /*#__PURE__*/ jsxRuntime.jsx(Symbols.EmailField, {}),
enumeration: /*#__PURE__*/ jsxRuntime.jsx(Symbols.EnumerationField, {}),
float: /*#__PURE__*/ jsxRuntime.jsx(Symbols.NumberField, {}),
integer: /*#__PURE__*/ jsxRuntime.jsx(Symbols.NumberField, {}),
media: /*#__PURE__*/ jsxRuntime.jsx(Symbols.MediaField, {}),
password: /*#__PURE__*/ jsxRuntime.jsx(Symbols.PasswordField, {}),
relation: /*#__PURE__*/ jsxRuntime.jsx(Symbols.RelationField, {}),
string: /*#__PURE__*/ jsxRuntime.jsx(Symbols.TextField, {}),
text: /*#__PURE__*/ jsxRuntime.jsx(Symbols.TextField, {}),
richtext: /*#__PURE__*/ jsxRuntime.jsx(Symbols.TextField, {}),
time: /*#__PURE__*/ jsxRuntime.jsx(Symbols.DateField, {}),
timestamp: /*#__PURE__*/ jsxRuntime.jsx(Symbols.DateField, {}),
json: /*#__PURE__*/ jsxRuntime.jsx(Symbols.JsonField, {}),
uid: /*#__PURE__*/ jsxRuntime.jsx(Symbols.UidField, {}),
component: /*#__PURE__*/ jsxRuntime.jsx(Symbols.ComponentField, {}),
dynamiczone: /*#__PURE__*/ jsxRuntime.jsx(Symbols.DynamicZoneField, {}),
blocks: /*#__PURE__*/ jsxRuntime.jsx(Symbols.BlocksField, {})
};
const FieldTypeIcon = ({ type, customFieldUid })=>{
const getCustomField = strapiAdmin.useStrapiApp('FieldTypeIcon', (state)=>state.customFields.get);
if (!type) {
return null;
}
let Compo = iconByTypes[type];
if (customFieldUid) {
const customField = getCustomField(customFieldUid);
const CustomFieldIcon = customField?.icon;
if (CustomFieldIcon) {
Compo = /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
marginRight: 3,
width: 7,
height: 6,
children: /*#__PURE__*/ jsxRuntime.jsx(CustomFieldIcon, {})
});
}
}
if (!iconByTypes[type]) {
return null;
}
return Compo;
};
exports.FieldTypeIcon = FieldTypeIcon;
//# sourceMappingURL=FieldTypeIcon.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FieldTypeIcon.js","sources":["../../../admin/src/components/FieldTypeIcon.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useStrapiApp } from '@strapi/admin/strapi-admin';\nimport { Box } from '@strapi/design-system';\nimport {\n BlocksField,\n BooleanField,\n ComponentField,\n DateField,\n DynamicZoneField,\n EmailField,\n EnumerationField,\n JsonField,\n MediaField,\n NumberField,\n PasswordField,\n RelationField,\n TextField,\n UidField,\n} from '@strapi/icons/symbols';\n\nimport type { Schema } from '@strapi/types';\n\nconst iconByTypes: Record<Schema.Attribute.Kind, React.ReactElement> = {\n biginteger: <NumberField />,\n boolean: <BooleanField />,\n date: <DateField />,\n datetime: <DateField />,\n decimal: <NumberField />,\n email: <EmailField />,\n enumeration: <EnumerationField />,\n float: <NumberField />,\n integer: <NumberField />,\n media: <MediaField />,\n password: <PasswordField />,\n relation: <RelationField />,\n string: <TextField />,\n text: <TextField />,\n richtext: <TextField />,\n time: <DateField />,\n timestamp: <DateField />,\n json: <JsonField />,\n uid: <UidField />,\n component: <ComponentField />,\n dynamiczone: <DynamicZoneField />,\n blocks: <BlocksField />,\n};\n\ninterface FieldTypeIconProps {\n type?: keyof typeof iconByTypes;\n customFieldUid?: string;\n}\n\nconst FieldTypeIcon = ({ type, customFieldUid }: FieldTypeIconProps) => {\n const getCustomField = useStrapiApp('FieldTypeIcon', (state) => state.customFields.get);\n\n if (!type) {\n return null;\n }\n\n let Compo = iconByTypes[type];\n\n if (customFieldUid) {\n const customField = getCustomField(customFieldUid);\n const CustomFieldIcon = customField?.icon;\n\n if (CustomFieldIcon) {\n Compo = (\n <Box marginRight={3} width={7} height={6}>\n <CustomFieldIcon />\n </Box>\n );\n }\n }\n\n if (!iconByTypes[type]) {\n return null;\n }\n\n return Compo;\n};\n\nexport { FieldTypeIcon };\n"],"names":["iconByTypes","biginteger","_jsx","NumberField","boolean","BooleanField","date","DateField","datetime","decimal","email","EmailField","enumeration","EnumerationField","float","integer","media","MediaField","password","PasswordField","relation","RelationField","string","TextField","text","richtext","time","timestamp","json","JsonField","uid","UidField","component","ComponentField","dynamiczone","DynamicZoneField","blocks","BlocksField","FieldTypeIcon","type","customFieldUid","getCustomField","useStrapiApp","state","customFields","get","Compo","customField","CustomFieldIcon","icon","Box","marginRight","width","height"],"mappings":";;;;;;;;AAuBA,MAAMA,WAAiE,GAAA;AACrEC,IAAAA,UAAAA,gBAAYC,cAACC,CAAAA,mBAAAA,EAAAA,EAAAA,CAAAA;AACbC,IAAAA,OAAAA,gBAASF,cAACG,CAAAA,oBAAAA,EAAAA,EAAAA,CAAAA;AACVC,IAAAA,IAAAA,gBAAMJ,cAACK,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACPC,IAAAA,QAAAA,gBAAUN,cAACK,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACXE,IAAAA,OAAAA,gBAASP,cAACC,CAAAA,mBAAAA,EAAAA,EAAAA,CAAAA;AACVO,IAAAA,KAAAA,gBAAOR,cAACS,CAAAA,kBAAAA,EAAAA,EAAAA,CAAAA;AACRC,IAAAA,WAAAA,gBAAaV,cAACW,CAAAA,wBAAAA,EAAAA,EAAAA,CAAAA;AACdC,IAAAA,KAAAA,gBAAOZ,cAACC,CAAAA,mBAAAA,EAAAA,EAAAA,CAAAA;AACRY,IAAAA,OAAAA,gBAASb,cAACC,CAAAA,mBAAAA,EAAAA,EAAAA,CAAAA;AACVa,IAAAA,KAAAA,gBAAOd,cAACe,CAAAA,kBAAAA,EAAAA,EAAAA,CAAAA;AACRC,IAAAA,QAAAA,gBAAUhB,cAACiB,CAAAA,qBAAAA,EAAAA,EAAAA,CAAAA;AACXC,IAAAA,QAAAA,gBAAUlB,cAACmB,CAAAA,qBAAAA,EAAAA,EAAAA,CAAAA;AACXC,IAAAA,MAAAA,gBAAQpB,cAACqB,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACTC,IAAAA,IAAAA,gBAAMtB,cAACqB,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACPE,IAAAA,QAAAA,gBAAUvB,cAACqB,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACXG,IAAAA,IAAAA,gBAAMxB,cAACK,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACPoB,IAAAA,SAAAA,gBAAWzB,cAACK,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACZqB,IAAAA,IAAAA,gBAAM1B,cAAC2B,CAAAA,iBAAAA,EAAAA,EAAAA,CAAAA;AACPC,IAAAA,GAAAA,gBAAK5B,cAAC6B,CAAAA,gBAAAA,EAAAA,EAAAA,CAAAA;AACNC,IAAAA,SAAAA,gBAAW9B,cAAC+B,CAAAA,sBAAAA,EAAAA,EAAAA,CAAAA;AACZC,IAAAA,WAAAA,gBAAahC,cAACiC,CAAAA,wBAAAA,EAAAA,EAAAA,CAAAA;AACdC,IAAAA,MAAAA,gBAAQlC,cAACmC,CAAAA,mBAAAA,EAAAA,EAAAA;AACX,CAAA;AAOA,MAAMC,gBAAgB,CAAC,EAAEC,IAAI,EAAEC,cAAc,EAAsB,GAAA;IACjE,MAAMC,cAAAA,GAAiBC,yBAAa,eAAiB,EAAA,CAACC,QAAUA,KAAMC,CAAAA,YAAY,CAACC,GAAG,CAAA;AAEtF,IAAA,IAAI,CAACN,IAAM,EAAA;QACT,OAAO,IAAA;AACT;IAEA,IAAIO,KAAAA,GAAQ9C,WAAW,CAACuC,IAAK,CAAA;AAE7B,IAAA,IAAIC,cAAgB,EAAA;AAClB,QAAA,MAAMO,cAAcN,cAAeD,CAAAA,cAAAA,CAAAA;AACnC,QAAA,MAAMQ,kBAAkBD,WAAaE,EAAAA,IAAAA;AAErC,QAAA,IAAID,eAAiB,EAAA;AACnBF,YAAAA,KAAAA,iBACE5C,cAACgD,CAAAA,gBAAAA,EAAAA;gBAAIC,WAAa,EAAA,CAAA;gBAAGC,KAAO,EAAA,CAAA;gBAAGC,MAAQ,EAAA,CAAA;AACrC,gBAAA,QAAA,gBAAAnD,cAAC8C,CAAAA,eAAAA,EAAAA,EAAAA;;AAGP;AACF;AAEA,IAAA,IAAI,CAAChD,WAAW,CAACuC,IAAAA,CAAK,EAAE;QACtB,OAAO,IAAA;AACT;IAEA,OAAOO,KAAAA;AACT;;;;"}

View File

@@ -0,0 +1,56 @@
import { jsx } from 'react/jsx-runtime';
import 'react';
import { useStrapiApp } from '@strapi/admin/strapi-admin';
import { Box } from '@strapi/design-system';
import { NumberField, BooleanField, DateField, EmailField, EnumerationField, MediaField, PasswordField, RelationField, TextField, JsonField, UidField, ComponentField, DynamicZoneField, BlocksField } from '@strapi/icons/symbols';
const iconByTypes = {
biginteger: /*#__PURE__*/ jsx(NumberField, {}),
boolean: /*#__PURE__*/ jsx(BooleanField, {}),
date: /*#__PURE__*/ jsx(DateField, {}),
datetime: /*#__PURE__*/ jsx(DateField, {}),
decimal: /*#__PURE__*/ jsx(NumberField, {}),
email: /*#__PURE__*/ jsx(EmailField, {}),
enumeration: /*#__PURE__*/ jsx(EnumerationField, {}),
float: /*#__PURE__*/ jsx(NumberField, {}),
integer: /*#__PURE__*/ jsx(NumberField, {}),
media: /*#__PURE__*/ jsx(MediaField, {}),
password: /*#__PURE__*/ jsx(PasswordField, {}),
relation: /*#__PURE__*/ jsx(RelationField, {}),
string: /*#__PURE__*/ jsx(TextField, {}),
text: /*#__PURE__*/ jsx(TextField, {}),
richtext: /*#__PURE__*/ jsx(TextField, {}),
time: /*#__PURE__*/ jsx(DateField, {}),
timestamp: /*#__PURE__*/ jsx(DateField, {}),
json: /*#__PURE__*/ jsx(JsonField, {}),
uid: /*#__PURE__*/ jsx(UidField, {}),
component: /*#__PURE__*/ jsx(ComponentField, {}),
dynamiczone: /*#__PURE__*/ jsx(DynamicZoneField, {}),
blocks: /*#__PURE__*/ jsx(BlocksField, {})
};
const FieldTypeIcon = ({ type, customFieldUid })=>{
const getCustomField = useStrapiApp('FieldTypeIcon', (state)=>state.customFields.get);
if (!type) {
return null;
}
let Compo = iconByTypes[type];
if (customFieldUid) {
const customField = getCustomField(customFieldUid);
const CustomFieldIcon = customField?.icon;
if (CustomFieldIcon) {
Compo = /*#__PURE__*/ jsx(Box, {
marginRight: 3,
width: 7,
height: 6,
children: /*#__PURE__*/ jsx(CustomFieldIcon, {})
});
}
}
if (!iconByTypes[type]) {
return null;
}
return Compo;
};
export { FieldTypeIcon };
//# sourceMappingURL=FieldTypeIcon.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FieldTypeIcon.mjs","sources":["../../../admin/src/components/FieldTypeIcon.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { useStrapiApp } from '@strapi/admin/strapi-admin';\nimport { Box } from '@strapi/design-system';\nimport {\n BlocksField,\n BooleanField,\n ComponentField,\n DateField,\n DynamicZoneField,\n EmailField,\n EnumerationField,\n JsonField,\n MediaField,\n NumberField,\n PasswordField,\n RelationField,\n TextField,\n UidField,\n} from '@strapi/icons/symbols';\n\nimport type { Schema } from '@strapi/types';\n\nconst iconByTypes: Record<Schema.Attribute.Kind, React.ReactElement> = {\n biginteger: <NumberField />,\n boolean: <BooleanField />,\n date: <DateField />,\n datetime: <DateField />,\n decimal: <NumberField />,\n email: <EmailField />,\n enumeration: <EnumerationField />,\n float: <NumberField />,\n integer: <NumberField />,\n media: <MediaField />,\n password: <PasswordField />,\n relation: <RelationField />,\n string: <TextField />,\n text: <TextField />,\n richtext: <TextField />,\n time: <DateField />,\n timestamp: <DateField />,\n json: <JsonField />,\n uid: <UidField />,\n component: <ComponentField />,\n dynamiczone: <DynamicZoneField />,\n blocks: <BlocksField />,\n};\n\ninterface FieldTypeIconProps {\n type?: keyof typeof iconByTypes;\n customFieldUid?: string;\n}\n\nconst FieldTypeIcon = ({ type, customFieldUid }: FieldTypeIconProps) => {\n const getCustomField = useStrapiApp('FieldTypeIcon', (state) => state.customFields.get);\n\n if (!type) {\n return null;\n }\n\n let Compo = iconByTypes[type];\n\n if (customFieldUid) {\n const customField = getCustomField(customFieldUid);\n const CustomFieldIcon = customField?.icon;\n\n if (CustomFieldIcon) {\n Compo = (\n <Box marginRight={3} width={7} height={6}>\n <CustomFieldIcon />\n </Box>\n );\n }\n }\n\n if (!iconByTypes[type]) {\n return null;\n }\n\n return Compo;\n};\n\nexport { FieldTypeIcon };\n"],"names":["iconByTypes","biginteger","_jsx","NumberField","boolean","BooleanField","date","DateField","datetime","decimal","email","EmailField","enumeration","EnumerationField","float","integer","media","MediaField","password","PasswordField","relation","RelationField","string","TextField","text","richtext","time","timestamp","json","JsonField","uid","UidField","component","ComponentField","dynamiczone","DynamicZoneField","blocks","BlocksField","FieldTypeIcon","type","customFieldUid","getCustomField","useStrapiApp","state","customFields","get","Compo","customField","CustomFieldIcon","icon","Box","marginRight","width","height"],"mappings":";;;;;;AAuBA,MAAMA,WAAiE,GAAA;AACrEC,IAAAA,UAAAA,gBAAYC,GAACC,CAAAA,WAAAA,EAAAA,EAAAA,CAAAA;AACbC,IAAAA,OAAAA,gBAASF,GAACG,CAAAA,YAAAA,EAAAA,EAAAA,CAAAA;AACVC,IAAAA,IAAAA,gBAAMJ,GAACK,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACPC,IAAAA,QAAAA,gBAAUN,GAACK,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACXE,IAAAA,OAAAA,gBAASP,GAACC,CAAAA,WAAAA,EAAAA,EAAAA,CAAAA;AACVO,IAAAA,KAAAA,gBAAOR,GAACS,CAAAA,UAAAA,EAAAA,EAAAA,CAAAA;AACRC,IAAAA,WAAAA,gBAAaV,GAACW,CAAAA,gBAAAA,EAAAA,EAAAA,CAAAA;AACdC,IAAAA,KAAAA,gBAAOZ,GAACC,CAAAA,WAAAA,EAAAA,EAAAA,CAAAA;AACRY,IAAAA,OAAAA,gBAASb,GAACC,CAAAA,WAAAA,EAAAA,EAAAA,CAAAA;AACVa,IAAAA,KAAAA,gBAAOd,GAACe,CAAAA,UAAAA,EAAAA,EAAAA,CAAAA;AACRC,IAAAA,QAAAA,gBAAUhB,GAACiB,CAAAA,aAAAA,EAAAA,EAAAA,CAAAA;AACXC,IAAAA,QAAAA,gBAAUlB,GAACmB,CAAAA,aAAAA,EAAAA,EAAAA,CAAAA;AACXC,IAAAA,MAAAA,gBAAQpB,GAACqB,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACTC,IAAAA,IAAAA,gBAAMtB,GAACqB,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACPE,IAAAA,QAAAA,gBAAUvB,GAACqB,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACXG,IAAAA,IAAAA,gBAAMxB,GAACK,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACPoB,IAAAA,SAAAA,gBAAWzB,GAACK,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACZqB,IAAAA,IAAAA,gBAAM1B,GAAC2B,CAAAA,SAAAA,EAAAA,EAAAA,CAAAA;AACPC,IAAAA,GAAAA,gBAAK5B,GAAC6B,CAAAA,QAAAA,EAAAA,EAAAA,CAAAA;AACNC,IAAAA,SAAAA,gBAAW9B,GAAC+B,CAAAA,cAAAA,EAAAA,EAAAA,CAAAA;AACZC,IAAAA,WAAAA,gBAAahC,GAACiC,CAAAA,gBAAAA,EAAAA,EAAAA,CAAAA;AACdC,IAAAA,MAAAA,gBAAQlC,GAACmC,CAAAA,WAAAA,EAAAA,EAAAA;AACX,CAAA;AAOA,MAAMC,gBAAgB,CAAC,EAAEC,IAAI,EAAEC,cAAc,EAAsB,GAAA;IACjE,MAAMC,cAAAA,GAAiBC,aAAa,eAAiB,EAAA,CAACC,QAAUA,KAAMC,CAAAA,YAAY,CAACC,GAAG,CAAA;AAEtF,IAAA,IAAI,CAACN,IAAM,EAAA;QACT,OAAO,IAAA;AACT;IAEA,IAAIO,KAAAA,GAAQ9C,WAAW,CAACuC,IAAK,CAAA;AAE7B,IAAA,IAAIC,cAAgB,EAAA;AAClB,QAAA,MAAMO,cAAcN,cAAeD,CAAAA,cAAAA,CAAAA;AACnC,QAAA,MAAMQ,kBAAkBD,WAAaE,EAAAA,IAAAA;AAErC,QAAA,IAAID,eAAiB,EAAA;AACnBF,YAAAA,KAAAA,iBACE5C,GAACgD,CAAAA,GAAAA,EAAAA;gBAAIC,WAAa,EAAA,CAAA;gBAAGC,KAAO,EAAA,CAAA;gBAAGC,MAAQ,EAAA,CAAA;AACrC,gBAAA,QAAA,gBAAAnD,GAAC8C,CAAAA,eAAAA,EAAAA,EAAAA;;AAGP;AACF;AAEA,IAAA,IAAI,CAAChD,WAAW,CAACuC,IAAAA,CAAK,EAAE;QACtB,OAAO,IAAA;AACT;IAEA,OAAOO,KAAAA;AACT;;;;"}

View File

@@ -0,0 +1,44 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var plugin = require('../constants/plugin.js');
const INJECTION_ZONES = {
editView: {
informations: [],
'right-links': []
},
listView: {
actions: [],
deleteModalAdditionalInfos: [],
publishModalAdditionalInfos: [],
unpublishModalAdditionalInfos: []
},
preview: {
actions: []
}
};
/**
* You can't know what this component props will be because it's generic and used everywhere
* e.g. content-manager edit view, we just send the slug but we might not in the listView,
* therefore, people should type it themselves on the components they render.
*/ const InjectionZone = ({ area, ...props })=>{
const components = useInjectionZone(area);
return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
children: components.map((component)=>/*#__PURE__*/ jsxRuntime.jsx(component.Component, {
...props
}, component.name))
});
};
const useInjectionZone = (area)=>{
const getPlugin = strapiAdmin.useStrapiApp('useInjectionZone', (state)=>state.getPlugin);
const contentManagerPlugin = getPlugin(plugin.PLUGIN_ID);
const [page, position] = area.split('.');
return contentManagerPlugin.getInjectedComponents(page, position);
};
exports.INJECTION_ZONES = INJECTION_ZONES;
exports.InjectionZone = InjectionZone;
exports.useInjectionZone = useInjectionZone;
//# sourceMappingURL=InjectionZone.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"InjectionZone.js","sources":["../../../admin/src/components/InjectionZone.tsx"],"sourcesContent":["import { useStrapiApp, InjectionZoneComponent } from '@strapi/admin/strapi-admin';\n\nimport { PLUGIN_ID } from '../constants/plugin';\n\nconst INJECTION_ZONES = {\n editView: { informations: [], 'right-links': [] },\n listView: {\n actions: [],\n deleteModalAdditionalInfos: [],\n publishModalAdditionalInfos: [],\n unpublishModalAdditionalInfos: [],\n },\n preview: {\n actions: [],\n },\n} satisfies InjectionZones;\n\ninterface InjectionZones {\n editView: {\n informations: InjectionZoneComponent[];\n 'right-links': InjectionZoneComponent[];\n };\n listView: {\n actions: InjectionZoneComponent[];\n deleteModalAdditionalInfos: InjectionZoneComponent[];\n publishModalAdditionalInfos: InjectionZoneComponent[];\n unpublishModalAdditionalInfos: InjectionZoneComponent[];\n };\n preview: {\n actions: InjectionZoneComponent[];\n };\n}\n\ntype InjectionZoneArea =\n | 'editView.informations'\n | 'editView.right-links'\n | 'listView.actions'\n | 'listView.unpublishModalAdditionalInfos'\n | 'listView.deleteModalAdditionalInfos'\n | 'listView.publishModalAdditionalInfos'\n | 'listView.deleteModalAdditionalInfos'\n | 'preview.actions';\n\ntype InjectionZoneModule = InjectionZoneArea extends `${infer Word}.${string}` ? Word : never;\ntype InjectionZoneContainer = InjectionZoneArea extends `${string}.${infer Word}.${string}`\n ? Word\n : never;\ntype InjectionZoneBlock = InjectionZoneArea extends `${string}.${string}.${infer Word}`\n ? Word\n : never;\n\n/**\n * You can't know what this component props will be because it's generic and used everywhere\n * e.g. content-manager edit view, we just send the slug but we might not in the listView,\n * therefore, people should type it themselves on the components they render.\n */\nconst InjectionZone = ({ area, ...props }: { area: InjectionZoneArea; [key: string]: unknown }) => {\n const components = useInjectionZone(area);\n\n return (\n <>\n {components.map((component) => (\n <component.Component key={component.name} {...props} />\n ))}\n </>\n );\n};\n\nexport const useInjectionZone = (area: InjectionZoneArea) => {\n const getPlugin = useStrapiApp('useInjectionZone', (state) => state.getPlugin);\n const contentManagerPlugin = getPlugin(PLUGIN_ID);\n const [page, position] = area.split('.') as [InjectionZoneContainer, InjectionZoneBlock];\n\n return contentManagerPlugin.getInjectedComponents(page, position);\n};\n\nexport { InjectionZone, INJECTION_ZONES };\n\nexport type {\n InjectionZoneArea,\n InjectionZoneComponent,\n InjectionZones,\n InjectionZoneModule,\n InjectionZoneContainer,\n InjectionZoneBlock,\n};\n"],"names":["INJECTION_ZONES","editView","informations","listView","actions","deleteModalAdditionalInfos","publishModalAdditionalInfos","unpublishModalAdditionalInfos","preview","InjectionZone","area","props","components","useInjectionZone","_jsx","_Fragment","map","component","Component","name","getPlugin","useStrapiApp","state","contentManagerPlugin","PLUGIN_ID","page","position","split","getInjectedComponents"],"mappings":";;;;;;AAIA,MAAMA,eAAkB,GAAA;IACtBC,QAAU,EAAA;AAAEC,QAAAA,YAAAA,EAAc,EAAE;AAAE,QAAA,aAAA,EAAe;AAAG,KAAA;IAChDC,QAAU,EAAA;AACRC,QAAAA,OAAAA,EAAS,EAAE;AACXC,QAAAA,0BAAAA,EAA4B,EAAE;AAC9BC,QAAAA,2BAAAA,EAA6B,EAAE;AAC/BC,QAAAA,6BAAAA,EAA+B;AACjC,KAAA;IACAC,OAAS,EAAA;AACPJ,QAAAA,OAAAA,EAAS;AACX;AACF;AAoCA;;;;AAIC,UACKK,aAAgB,GAAA,CAAC,EAAEC,IAAI,EAAE,GAAGC,KAA4D,EAAA,GAAA;AAC5F,IAAA,MAAMC,aAAaC,gBAAiBH,CAAAA,IAAAA,CAAAA;IAEpC,qBACEI,cAAA,CAAAC,mBAAA,EAAA;AACGH,QAAAA,QAAAA,EAAAA,UAAAA,CAAWI,GAAG,CAAC,CAACC,SACf,iBAAAH,cAAA,CAACG,UAAUC,SAAS,EAAA;AAAuB,gBAAA,GAAGP;AAApBM,aAAAA,EAAAA,SAAAA,CAAUE,IAAI,CAAA;;AAIhD;AAEO,MAAMN,mBAAmB,CAACH,IAAAA,GAAAA;AAC/B,IAAA,MAAMU,YAAYC,wBAAa,CAAA,kBAAA,EAAoB,CAACC,KAAAA,GAAUA,MAAMF,SAAS,CAAA;AAC7E,IAAA,MAAMG,uBAAuBH,SAAUI,CAAAA,gBAAAA,CAAAA;AACvC,IAAA,MAAM,CAACC,IAAMC,EAAAA,QAAAA,CAAS,GAAGhB,IAAAA,CAAKiB,KAAK,CAAC,GAAA,CAAA;IAEpC,OAAOJ,oBAAAA,CAAqBK,qBAAqB,CAACH,IAAMC,EAAAA,QAAAA,CAAAA;AAC1D;;;;;;"}

View File

@@ -0,0 +1,40 @@
import { jsx, Fragment } from 'react/jsx-runtime';
import { useStrapiApp } from '@strapi/admin/strapi-admin';
import { PLUGIN_ID } from '../constants/plugin.mjs';
const INJECTION_ZONES = {
editView: {
informations: [],
'right-links': []
},
listView: {
actions: [],
deleteModalAdditionalInfos: [],
publishModalAdditionalInfos: [],
unpublishModalAdditionalInfos: []
},
preview: {
actions: []
}
};
/**
* You can't know what this component props will be because it's generic and used everywhere
* e.g. content-manager edit view, we just send the slug but we might not in the listView,
* therefore, people should type it themselves on the components they render.
*/ const InjectionZone = ({ area, ...props })=>{
const components = useInjectionZone(area);
return /*#__PURE__*/ jsx(Fragment, {
children: components.map((component)=>/*#__PURE__*/ jsx(component.Component, {
...props
}, component.name))
});
};
const useInjectionZone = (area)=>{
const getPlugin = useStrapiApp('useInjectionZone', (state)=>state.getPlugin);
const contentManagerPlugin = getPlugin(PLUGIN_ID);
const [page, position] = area.split('.');
return contentManagerPlugin.getInjectedComponents(page, position);
};
export { INJECTION_ZONES, InjectionZone, useInjectionZone };
//# sourceMappingURL=InjectionZone.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"InjectionZone.mjs","sources":["../../../admin/src/components/InjectionZone.tsx"],"sourcesContent":["import { useStrapiApp, InjectionZoneComponent } from '@strapi/admin/strapi-admin';\n\nimport { PLUGIN_ID } from '../constants/plugin';\n\nconst INJECTION_ZONES = {\n editView: { informations: [], 'right-links': [] },\n listView: {\n actions: [],\n deleteModalAdditionalInfos: [],\n publishModalAdditionalInfos: [],\n unpublishModalAdditionalInfos: [],\n },\n preview: {\n actions: [],\n },\n} satisfies InjectionZones;\n\ninterface InjectionZones {\n editView: {\n informations: InjectionZoneComponent[];\n 'right-links': InjectionZoneComponent[];\n };\n listView: {\n actions: InjectionZoneComponent[];\n deleteModalAdditionalInfos: InjectionZoneComponent[];\n publishModalAdditionalInfos: InjectionZoneComponent[];\n unpublishModalAdditionalInfos: InjectionZoneComponent[];\n };\n preview: {\n actions: InjectionZoneComponent[];\n };\n}\n\ntype InjectionZoneArea =\n | 'editView.informations'\n | 'editView.right-links'\n | 'listView.actions'\n | 'listView.unpublishModalAdditionalInfos'\n | 'listView.deleteModalAdditionalInfos'\n | 'listView.publishModalAdditionalInfos'\n | 'listView.deleteModalAdditionalInfos'\n | 'preview.actions';\n\ntype InjectionZoneModule = InjectionZoneArea extends `${infer Word}.${string}` ? Word : never;\ntype InjectionZoneContainer = InjectionZoneArea extends `${string}.${infer Word}.${string}`\n ? Word\n : never;\ntype InjectionZoneBlock = InjectionZoneArea extends `${string}.${string}.${infer Word}`\n ? Word\n : never;\n\n/**\n * You can't know what this component props will be because it's generic and used everywhere\n * e.g. content-manager edit view, we just send the slug but we might not in the listView,\n * therefore, people should type it themselves on the components they render.\n */\nconst InjectionZone = ({ area, ...props }: { area: InjectionZoneArea; [key: string]: unknown }) => {\n const components = useInjectionZone(area);\n\n return (\n <>\n {components.map((component) => (\n <component.Component key={component.name} {...props} />\n ))}\n </>\n );\n};\n\nexport const useInjectionZone = (area: InjectionZoneArea) => {\n const getPlugin = useStrapiApp('useInjectionZone', (state) => state.getPlugin);\n const contentManagerPlugin = getPlugin(PLUGIN_ID);\n const [page, position] = area.split('.') as [InjectionZoneContainer, InjectionZoneBlock];\n\n return contentManagerPlugin.getInjectedComponents(page, position);\n};\n\nexport { InjectionZone, INJECTION_ZONES };\n\nexport type {\n InjectionZoneArea,\n InjectionZoneComponent,\n InjectionZones,\n InjectionZoneModule,\n InjectionZoneContainer,\n InjectionZoneBlock,\n};\n"],"names":["INJECTION_ZONES","editView","informations","listView","actions","deleteModalAdditionalInfos","publishModalAdditionalInfos","unpublishModalAdditionalInfos","preview","InjectionZone","area","props","components","useInjectionZone","_jsx","_Fragment","map","component","Component","name","getPlugin","useStrapiApp","state","contentManagerPlugin","PLUGIN_ID","page","position","split","getInjectedComponents"],"mappings":";;;;AAIA,MAAMA,eAAkB,GAAA;IACtBC,QAAU,EAAA;AAAEC,QAAAA,YAAAA,EAAc,EAAE;AAAE,QAAA,aAAA,EAAe;AAAG,KAAA;IAChDC,QAAU,EAAA;AACRC,QAAAA,OAAAA,EAAS,EAAE;AACXC,QAAAA,0BAAAA,EAA4B,EAAE;AAC9BC,QAAAA,2BAAAA,EAA6B,EAAE;AAC/BC,QAAAA,6BAAAA,EAA+B;AACjC,KAAA;IACAC,OAAS,EAAA;AACPJ,QAAAA,OAAAA,EAAS;AACX;AACF;AAoCA;;;;AAIC,UACKK,aAAgB,GAAA,CAAC,EAAEC,IAAI,EAAE,GAAGC,KAA4D,EAAA,GAAA;AAC5F,IAAA,MAAMC,aAAaC,gBAAiBH,CAAAA,IAAAA,CAAAA;IAEpC,qBACEI,GAAA,CAAAC,QAAA,EAAA;AACGH,QAAAA,QAAAA,EAAAA,UAAAA,CAAWI,GAAG,CAAC,CAACC,SACf,iBAAAH,GAAA,CAACG,UAAUC,SAAS,EAAA;AAAuB,gBAAA,GAAGP;AAApBM,aAAAA,EAAAA,SAAAA,CAAUE,IAAI,CAAA;;AAIhD;AAEO,MAAMN,mBAAmB,CAACH,IAAAA,GAAAA;AAC/B,IAAA,MAAMU,YAAYC,YAAa,CAAA,kBAAA,EAAoB,CAACC,KAAAA,GAAUA,MAAMF,SAAS,CAAA;AAC7E,IAAA,MAAMG,uBAAuBH,SAAUI,CAAAA,SAAAA,CAAAA;AACvC,IAAA,MAAM,CAACC,IAAMC,EAAAA,QAAAA,CAAS,GAAGhB,IAAAA,CAAKiB,KAAK,CAAC,GAAA,CAAA;IAEpC,OAAOJ,oBAAAA,CAAqBK,qBAAqB,CAACH,IAAMC,EAAAA,QAAAA,CAAAA;AAC1D;;;;"}

View File

@@ -0,0 +1,172 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var qs = require('qs');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var useContentTypeSchema = require('../hooks/useContentTypeSchema.js');
var hooks = require('../modules/hooks.js');
var translations = require('../utils/translations.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const SubNavLinkCustom = styledComponents.styled(designSystem.SubNavLink)`
div {
width: inherit;
span:nth-child(2) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: inherit;
}
}
`;
const LeftMenu = ()=>{
const [search, setSearch] = React__namespace.useState('');
const [{ query }] = strapiAdmin.useQueryParams();
const { formatMessage, locale } = reactIntl.useIntl();
const collectionTypeLinks = hooks.useTypedSelector((state)=>state['content-manager'].app.collectionTypeLinks);
const singleTypeLinks = hooks.useTypedSelector((state)=>state['content-manager'].app.singleTypeLinks);
const { schemas } = useContentTypeSchema.useContentTypeSchema();
const { startsWith } = designSystem.useFilter(locale, {
sensitivity: 'base'
});
const formatter = designSystem.useCollator(locale, {
sensitivity: 'base'
});
const menu = React__namespace.useMemo(()=>[
{
id: 'collectionTypes',
title: formatMessage({
id: translations.getTranslation('components.LeftMenu.collection-types'),
defaultMessage: 'Collection Types'
}),
searchable: true,
links: collectionTypeLinks
},
{
id: 'singleTypes',
title: formatMessage({
id: translations.getTranslation('components.LeftMenu.single-types'),
defaultMessage: 'Single Types'
}),
searchable: true,
links: singleTypeLinks
}
].map((section)=>({
...section,
links: section.links/**
* Filter by the search value
*/ .filter((link)=>startsWith(link.title, search))/**
* Sort correctly using the language
*/ .sort((a, b)=>formatter.compare(a.title, b.title))/**
* Apply the formated strings to the links from react-intl
*/ .map((link)=>{
return {
...link,
title: formatMessage({
id: link.title,
defaultMessage: link.title
})
};
})
})), [
collectionTypeLinks,
search,
singleTypeLinks,
startsWith,
formatMessage,
formatter
]);
const handleClear = ()=>{
setSearch('');
};
const handleChangeSearch = ({ target: { value } })=>{
setSearch(value);
};
const label = formatMessage({
id: translations.getTranslation('header.name'),
defaultMessage: 'Content Manager'
});
const getPluginsParamsForLink = (link)=>{
const schema = schemas.find((schema)=>schema.uid === link.uid);
const isI18nEnabled = Boolean(schema?.pluginOptions?.i18n?.localized);
// The search params have the i18n plugin
if (query.plugins && 'i18n' in query.plugins) {
// Prepare removal of i18n from the plugins search params
const { i18n, ...restPlugins } = query.plugins;
// i18n is not enabled, remove it from the plugins search params
if (!isI18nEnabled) {
return restPlugins;
}
// i18n is enabled, put the plugins search params back together
return {
i18n,
...restPlugins
};
}
return query.plugins;
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.SubNav, {
"aria-label": label,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.SubNavHeader, {
label: label,
searchable: true,
value: search,
onChange: handleChangeSearch,
onClear: handleClear,
searchLabel: formatMessage({
id: 'content-manager.components.LeftMenu.Search.label',
defaultMessage: 'Search for a content type'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.SubNavSections, {
children: menu.map((section)=>{
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.SubNavSection, {
label: section.title,
badgeLabel: section.links.length.toString(),
children: section.links.map((link)=>{
return /*#__PURE__*/ jsxRuntime.jsx(SubNavLinkCustom, {
tag: reactRouterDom.NavLink,
to: {
pathname: link.to,
search: qs.stringify({
...qs.parse(link.search ?? ''),
plugins: getPluginsParamsForLink(link)
})
},
width: "100%",
children: link.title
}, link.uid);
})
}, section.id);
})
})
]
});
};
exports.LeftMenu = LeftMenu;
//# sourceMappingURL=LeftMenu.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,151 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { useQueryParams } from '@strapi/admin/strapi-admin';
import { SubNavLink, useFilter, useCollator, SubNav, SubNavHeader, SubNavSections, SubNavSection } from '@strapi/design-system';
import { stringify, parse } from 'qs';
import { useIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { useContentTypeSchema } from '../hooks/useContentTypeSchema.mjs';
import { useTypedSelector } from '../modules/hooks.mjs';
import { getTranslation } from '../utils/translations.mjs';
const SubNavLinkCustom = styled(SubNavLink)`
div {
width: inherit;
span:nth-child(2) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: inherit;
}
}
`;
const LeftMenu = ()=>{
const [search, setSearch] = React.useState('');
const [{ query }] = useQueryParams();
const { formatMessage, locale } = useIntl();
const collectionTypeLinks = useTypedSelector((state)=>state['content-manager'].app.collectionTypeLinks);
const singleTypeLinks = useTypedSelector((state)=>state['content-manager'].app.singleTypeLinks);
const { schemas } = useContentTypeSchema();
const { startsWith } = useFilter(locale, {
sensitivity: 'base'
});
const formatter = useCollator(locale, {
sensitivity: 'base'
});
const menu = React.useMemo(()=>[
{
id: 'collectionTypes',
title: formatMessage({
id: getTranslation('components.LeftMenu.collection-types'),
defaultMessage: 'Collection Types'
}),
searchable: true,
links: collectionTypeLinks
},
{
id: 'singleTypes',
title: formatMessage({
id: getTranslation('components.LeftMenu.single-types'),
defaultMessage: 'Single Types'
}),
searchable: true,
links: singleTypeLinks
}
].map((section)=>({
...section,
links: section.links/**
* Filter by the search value
*/ .filter((link)=>startsWith(link.title, search))/**
* Sort correctly using the language
*/ .sort((a, b)=>formatter.compare(a.title, b.title))/**
* Apply the formated strings to the links from react-intl
*/ .map((link)=>{
return {
...link,
title: formatMessage({
id: link.title,
defaultMessage: link.title
})
};
})
})), [
collectionTypeLinks,
search,
singleTypeLinks,
startsWith,
formatMessage,
formatter
]);
const handleClear = ()=>{
setSearch('');
};
const handleChangeSearch = ({ target: { value } })=>{
setSearch(value);
};
const label = formatMessage({
id: getTranslation('header.name'),
defaultMessage: 'Content Manager'
});
const getPluginsParamsForLink = (link)=>{
const schema = schemas.find((schema)=>schema.uid === link.uid);
const isI18nEnabled = Boolean(schema?.pluginOptions?.i18n?.localized);
// The search params have the i18n plugin
if (query.plugins && 'i18n' in query.plugins) {
// Prepare removal of i18n from the plugins search params
const { i18n, ...restPlugins } = query.plugins;
// i18n is not enabled, remove it from the plugins search params
if (!isI18nEnabled) {
return restPlugins;
}
// i18n is enabled, put the plugins search params back together
return {
i18n,
...restPlugins
};
}
return query.plugins;
};
return /*#__PURE__*/ jsxs(SubNav, {
"aria-label": label,
children: [
/*#__PURE__*/ jsx(SubNavHeader, {
label: label,
searchable: true,
value: search,
onChange: handleChangeSearch,
onClear: handleClear,
searchLabel: formatMessage({
id: 'content-manager.components.LeftMenu.Search.label',
defaultMessage: 'Search for a content type'
})
}),
/*#__PURE__*/ jsx(SubNavSections, {
children: menu.map((section)=>{
return /*#__PURE__*/ jsx(SubNavSection, {
label: section.title,
badgeLabel: section.links.length.toString(),
children: section.links.map((link)=>{
return /*#__PURE__*/ jsx(SubNavLinkCustom, {
tag: NavLink,
to: {
pathname: link.to,
search: stringify({
...parse(link.search ?? ''),
plugins: getPluginsParamsForLink(link)
})
},
width: "100%",
children: link.title
}, link.uid);
})
}, section.id);
})
})
]
});
};
export { LeftMenu };
//# sourceMappingURL=LeftMenu.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,76 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var dateFns = require('date-fns');
var reactIntl = require('react-intl');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const intervals = [
'years',
'months',
'days',
'hours',
'minutes',
'seconds'
];
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/ const RelativeTime = /*#__PURE__*/ React__namespace.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
const { formatRelativeTime, formatDate, formatTime } = reactIntl.useIntl();
/**
* TODO: make this auto-update, like a clock.
*/ const interval = dateFns.intervalToDuration({
start: timestamp,
end: Date.now()
});
const unit = intervals.find((intervalUnit)=>{
return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
}) ?? 'seconds';
const relativeTime = dateFns.isPast(timestamp) ? -interval[unit] : interval[unit];
// Display custom text if interval is less than the threshold
const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
numeric: 'auto'
});
return /*#__PURE__*/ jsxRuntime.jsx("time", {
ref: forwardedRef,
dateTime: timestamp.toISOString(),
role: "time",
title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
...restProps,
children: displayText
});
});
exports.RelativeTime = RelativeTime;
//# sourceMappingURL=RelativeTime.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RelativeTime.js","sources":["../../../admin/src/components/RelativeTime.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Duration, intervalToDuration, isPast } from 'date-fns';\nimport { useIntl } from 'react-intl';\n\nconst intervals: Array<keyof Duration> = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\ninterface CustomInterval {\n unit: keyof Duration;\n text: string;\n threshold: number;\n}\n\ninterface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {\n timestamp: Date;\n customIntervals?: CustomInterval[];\n}\n\n/**\n * Displays the relative time between a given timestamp and the current time.\n * You can display a custom message for given time intervals by passing an array of custom intervals.\n *\n * @example\n * ```jsx\n * <caption>Display \"last hour\" if the timestamp is less than an hour ago</caption>\n * <RelativeTime\n * timestamp={new Date('2021-01-01')}\n * customIntervals={[\n * { unit: 'hours', threshold: 1, text: 'last hour' },\n * ]}\n * ```\n */\nconst RelativeTime = React.forwardRef<HTMLTimeElement, RelativeTimeProps>(\n ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {\n const { formatRelativeTime, formatDate, formatTime } = useIntl();\n\n /**\n * TODO: make this auto-update, like a clock.\n */\n const interval = intervalToDuration({\n start: timestamp,\n end: Date.now(),\n // see https://github.com/date-fns/date-fns/issues/2891 No idea why it's all partial it returns it every time.\n }) as Required<Duration>;\n\n const unit =\n intervals.find((intervalUnit) => {\n return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);\n }) ?? 'seconds';\n\n const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];\n\n // Display custom text if interval is less than the threshold\n const customInterval = customIntervals.find(\n (custom) => interval[custom.unit] < custom.threshold\n );\n\n const displayText = customInterval\n ? customInterval.text\n : formatRelativeTime(relativeTime, unit, { numeric: 'auto' });\n\n return (\n <time\n ref={forwardedRef}\n dateTime={timestamp.toISOString()}\n role=\"time\"\n title={`${formatDate(timestamp)} ${formatTime(timestamp)}`}\n {...restProps}\n >\n {displayText}\n </time>\n );\n }\n);\n\nexport { RelativeTime };\nexport type { CustomInterval, RelativeTimeProps };\n"],"names":["intervals","RelativeTime","React","forwardRef","timestamp","customIntervals","restProps","forwardedRef","formatRelativeTime","formatDate","formatTime","useIntl","interval","intervalToDuration","start","end","Date","now","unit","find","intervalUnit","Object","keys","includes","relativeTime","isPast","customInterval","custom","threshold","displayText","text","numeric","_jsx","time","ref","dateTime","toISOString","role","title"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,MAAMA,SAAmC,GAAA;AAAC,IAAA,OAAA;AAAS,IAAA,QAAA;AAAU,IAAA,MAAA;AAAQ,IAAA,OAAA;AAAS,IAAA,SAAA;AAAW,IAAA;AAAU,CAAA;AAanG;;;;;;;;;;;;;AAaC,IACKC,MAAAA,YAAAA,iBAAeC,gBAAMC,CAAAA,UAAU,CACnC,CAAC,EAAEC,SAAS,EAAEC,eAAkB,GAAA,EAAE,EAAE,GAAGC,WAAW,EAAEC,YAAAA,GAAAA;AAClD,IAAA,MAAM,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,iBAAAA,EAAAA;AAEvD;;QAGA,MAAMC,WAAWC,0BAAmB,CAAA;QAClCC,KAAOV,EAAAA,SAAAA;AACPW,QAAAA,GAAAA,EAAKC,KAAKC,GAAG;AAEf,KAAA,CAAA;AAEA,IAAA,MAAMC,IACJlB,GAAAA,SAAAA,CAAUmB,IAAI,CAAC,CAACC,YAAAA,GAAAA;QACd,OAAOR,QAAQ,CAACQ,YAAAA,CAAa,GAAG,CAAA,IAAKC,OAAOC,IAAI,CAACV,QAAUW,CAAAA,CAAAA,QAAQ,CAACH,YAAAA,CAAAA;KAChE,CAAA,IAAA,SAAA;IAER,MAAMI,YAAAA,GAAeC,cAAOrB,CAAAA,SAAAA,CAAAA,GAAa,CAACQ,QAAQ,CAACM,IAAK,CAAA,GAAGN,QAAQ,CAACM,IAAK,CAAA;;AAGzE,IAAA,MAAMQ,cAAiBrB,GAAAA,eAAAA,CAAgBc,IAAI,CACzC,CAACQ,MAAAA,GAAWf,QAAQ,CAACe,MAAOT,CAAAA,IAAI,CAAC,GAAGS,OAAOC,SAAS,CAAA;AAGtD,IAAA,MAAMC,cAAcH,cAChBA,GAAAA,cAAAA,CAAeI,IAAI,GACnBtB,kBAAAA,CAAmBgB,cAAcN,IAAM,EAAA;QAAEa,OAAS,EAAA;AAAO,KAAA,CAAA;AAE7D,IAAA,qBACEC,cAACC,CAAAA,MAAAA,EAAAA;QACCC,GAAK3B,EAAAA,YAAAA;AACL4B,QAAAA,QAAAA,EAAU/B,UAAUgC,WAAW,EAAA;QAC/BC,IAAK,EAAA,MAAA;QACLC,KAAO,EAAA,CAAC,EAAE7B,UAAWL,CAAAA,SAAAA,CAAAA,CAAW,CAAC,EAAEM,UAAAA,CAAWN,WAAW,CAAC;AACzD,QAAA,GAAGE,SAAS;AAEZuB,QAAAA,QAAAA,EAAAA;;AAGP,CAAA;;;;"}

View File

@@ -0,0 +1,55 @@
import { jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { intervalToDuration, isPast } from 'date-fns';
import { useIntl } from 'react-intl';
const intervals = [
'years',
'months',
'days',
'hours',
'minutes',
'seconds'
];
/**
* Displays the relative time between a given timestamp and the current time.
* You can display a custom message for given time intervals by passing an array of custom intervals.
*
* @example
* ```jsx
* <caption>Display "last hour" if the timestamp is less than an hour ago</caption>
* <RelativeTime
* timestamp={new Date('2021-01-01')}
* customIntervals={[
* { unit: 'hours', threshold: 1, text: 'last hour' },
* ]}
* ```
*/ const RelativeTime = /*#__PURE__*/ React.forwardRef(({ timestamp, customIntervals = [], ...restProps }, forwardedRef)=>{
const { formatRelativeTime, formatDate, formatTime } = useIntl();
/**
* TODO: make this auto-update, like a clock.
*/ const interval = intervalToDuration({
start: timestamp,
end: Date.now()
});
const unit = intervals.find((intervalUnit)=>{
return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);
}) ?? 'seconds';
const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];
// Display custom text if interval is less than the threshold
const customInterval = customIntervals.find((custom)=>interval[custom.unit] < custom.threshold);
const displayText = customInterval ? customInterval.text : formatRelativeTime(relativeTime, unit, {
numeric: 'auto'
});
return /*#__PURE__*/ jsx("time", {
ref: forwardedRef,
dateTime: timestamp.toISOString(),
role: "time",
title: `${formatDate(timestamp)} ${formatTime(timestamp)}`,
...restProps,
children: displayText
});
});
export { RelativeTime };
//# sourceMappingURL=RelativeTime.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RelativeTime.mjs","sources":["../../../admin/src/components/RelativeTime.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { Duration, intervalToDuration, isPast } from 'date-fns';\nimport { useIntl } from 'react-intl';\n\nconst intervals: Array<keyof Duration> = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\ninterface CustomInterval {\n unit: keyof Duration;\n text: string;\n threshold: number;\n}\n\ninterface RelativeTimeProps extends React.ComponentPropsWithoutRef<'time'> {\n timestamp: Date;\n customIntervals?: CustomInterval[];\n}\n\n/**\n * Displays the relative time between a given timestamp and the current time.\n * You can display a custom message for given time intervals by passing an array of custom intervals.\n *\n * @example\n * ```jsx\n * <caption>Display \"last hour\" if the timestamp is less than an hour ago</caption>\n * <RelativeTime\n * timestamp={new Date('2021-01-01')}\n * customIntervals={[\n * { unit: 'hours', threshold: 1, text: 'last hour' },\n * ]}\n * ```\n */\nconst RelativeTime = React.forwardRef<HTMLTimeElement, RelativeTimeProps>(\n ({ timestamp, customIntervals = [], ...restProps }, forwardedRef) => {\n const { formatRelativeTime, formatDate, formatTime } = useIntl();\n\n /**\n * TODO: make this auto-update, like a clock.\n */\n const interval = intervalToDuration({\n start: timestamp,\n end: Date.now(),\n // see https://github.com/date-fns/date-fns/issues/2891 No idea why it's all partial it returns it every time.\n }) as Required<Duration>;\n\n const unit =\n intervals.find((intervalUnit) => {\n return interval[intervalUnit] > 0 && Object.keys(interval).includes(intervalUnit);\n }) ?? 'seconds';\n\n const relativeTime = isPast(timestamp) ? -interval[unit] : interval[unit];\n\n // Display custom text if interval is less than the threshold\n const customInterval = customIntervals.find(\n (custom) => interval[custom.unit] < custom.threshold\n );\n\n const displayText = customInterval\n ? customInterval.text\n : formatRelativeTime(relativeTime, unit, { numeric: 'auto' });\n\n return (\n <time\n ref={forwardedRef}\n dateTime={timestamp.toISOString()}\n role=\"time\"\n title={`${formatDate(timestamp)} ${formatTime(timestamp)}`}\n {...restProps}\n >\n {displayText}\n </time>\n );\n }\n);\n\nexport { RelativeTime };\nexport type { CustomInterval, RelativeTimeProps };\n"],"names":["intervals","RelativeTime","React","forwardRef","timestamp","customIntervals","restProps","forwardedRef","formatRelativeTime","formatDate","formatTime","useIntl","interval","intervalToDuration","start","end","Date","now","unit","find","intervalUnit","Object","keys","includes","relativeTime","isPast","customInterval","custom","threshold","displayText","text","numeric","_jsx","time","ref","dateTime","toISOString","role","title"],"mappings":";;;;;AAKA,MAAMA,SAAmC,GAAA;AAAC,IAAA,OAAA;AAAS,IAAA,QAAA;AAAU,IAAA,MAAA;AAAQ,IAAA,OAAA;AAAS,IAAA,SAAA;AAAW,IAAA;AAAU,CAAA;AAanG;;;;;;;;;;;;;AAaC,IACKC,MAAAA,YAAAA,iBAAeC,KAAMC,CAAAA,UAAU,CACnC,CAAC,EAAEC,SAAS,EAAEC,eAAkB,GAAA,EAAE,EAAE,GAAGC,WAAW,EAAEC,YAAAA,GAAAA;AAClD,IAAA,MAAM,EAAEC,kBAAkB,EAAEC,UAAU,EAAEC,UAAU,EAAE,GAAGC,OAAAA,EAAAA;AAEvD;;QAGA,MAAMC,WAAWC,kBAAmB,CAAA;QAClCC,KAAOV,EAAAA,SAAAA;AACPW,QAAAA,GAAAA,EAAKC,KAAKC,GAAG;AAEf,KAAA,CAAA;AAEA,IAAA,MAAMC,IACJlB,GAAAA,SAAAA,CAAUmB,IAAI,CAAC,CAACC,YAAAA,GAAAA;QACd,OAAOR,QAAQ,CAACQ,YAAAA,CAAa,GAAG,CAAA,IAAKC,OAAOC,IAAI,CAACV,QAAUW,CAAAA,CAAAA,QAAQ,CAACH,YAAAA,CAAAA;KAChE,CAAA,IAAA,SAAA;IAER,MAAMI,YAAAA,GAAeC,MAAOrB,CAAAA,SAAAA,CAAAA,GAAa,CAACQ,QAAQ,CAACM,IAAK,CAAA,GAAGN,QAAQ,CAACM,IAAK,CAAA;;AAGzE,IAAA,MAAMQ,cAAiBrB,GAAAA,eAAAA,CAAgBc,IAAI,CACzC,CAACQ,MAAAA,GAAWf,QAAQ,CAACe,MAAOT,CAAAA,IAAI,CAAC,GAAGS,OAAOC,SAAS,CAAA;AAGtD,IAAA,MAAMC,cAAcH,cAChBA,GAAAA,cAAAA,CAAeI,IAAI,GACnBtB,kBAAAA,CAAmBgB,cAAcN,IAAM,EAAA;QAAEa,OAAS,EAAA;AAAO,KAAA,CAAA;AAE7D,IAAA,qBACEC,GAACC,CAAAA,MAAAA,EAAAA;QACCC,GAAK3B,EAAAA,YAAAA;AACL4B,QAAAA,QAAAA,EAAU/B,UAAUgC,WAAW,EAAA;QAC/BC,IAAK,EAAA,MAAA;QACLC,KAAO,EAAA,CAAC,EAAE7B,UAAWL,CAAAA,SAAAA,CAAAA,CAAW,CAAC,EAAEM,UAAAA,CAAWN,WAAW,CAAC;AACzD,QAAA,GAAGE,SAAS;AAEZuB,QAAAA,QAAAA,EAAAA;;AAGP,CAAA;;;;"}

View File

@@ -0,0 +1,161 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var DocumentStatus = require('../pages/EditView/components/DocumentStatus.js');
var homepage = require('../services/homepage.js');
var RelativeTime = require('./RelativeTime.js');
const CellTypography = styledComponents.styled(designSystem.Typography).attrs({
maxWidth: '14.4rem',
display: 'block'
})`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const RecentDocumentsTable = ({ documents })=>{
const { formatMessage } = reactIntl.useIntl();
const { trackUsage } = strapiAdmin.useTracking();
const navigate = reactRouterDom.useNavigate();
const getEditViewLink = (document)=>{
const isSingleType = document.kind === 'singleType';
const kindPath = isSingleType ? 'single-types' : 'collection-types';
const queryParams = document.locale ? `?plugins[i18n][locale]=${document.locale}` : '';
return `/content-manager/${kindPath}/${document.contentTypeUid}${isSingleType ? '' : '/' + document.documentId}${queryParams}`;
};
const handleRowClick = (document)=>()=>{
trackUsage('willEditEntryFromHome');
const link = getEditViewLink(document);
navigate(link);
};
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Table, {
colCount: 5,
rowCount: documents?.length ?? 0,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Tbody, {
children: documents?.map((document)=>/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Tr, {
onClick: handleRowClick(document),
cursor: "pointer",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
children: /*#__PURE__*/ jsxRuntime.jsx(CellTypography, {
title: document.title,
variant: "omega",
textColor: "neutral800",
children: document.title
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
children: /*#__PURE__*/ jsxRuntime.jsx(CellTypography, {
variant: "omega",
textColor: "neutral600",
children: document.kind === 'singleType' ? formatMessage({
id: 'content-manager.widget.last-edited.single-type',
defaultMessage: 'Single-Type'
}) : formatMessage({
id: document.contentTypeDisplayName,
defaultMessage: document.contentTypeDisplayName
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
display: "inline-block",
children: document.status ? /*#__PURE__*/ jsxRuntime.jsx(DocumentStatus.DocumentStatus, {
status: document.status
}) : /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral600",
"aria-hidden": true,
children: "-"
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "neutral600",
children: /*#__PURE__*/ jsxRuntime.jsx(RelativeTime.RelativeTime, {
timestamp: new Date(document.updatedAt)
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Td, {
onClick: (e)=>e.stopPropagation(),
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
display: "inline-block",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.IconButton, {
tag: reactRouterDom.Link,
to: getEditViewLink(document),
onClick: ()=>trackUsage('willEditEntryFromHome'),
label: formatMessage({
id: 'content-manager.actions.edit.label',
defaultMessage: 'Edit'
}),
variant: "ghost",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.Pencil, {})
})
})
})
]
}, document.documentId))
})
});
};
/* -------------------------------------------------------------------------------------------------
* LastEditedWidget
* -----------------------------------------------------------------------------------------------*/ const LastEditedWidget = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { data, isLoading, error } = homepage.useGetRecentDocumentsQuery({
action: 'update'
});
if (isLoading) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Widget.Loading, {});
}
if (error || !data) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Widget.Error, {});
}
if (data.length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Widget.NoData, {
children: formatMessage({
id: 'content-manager.widget.last-edited.no-data',
defaultMessage: 'No edited entries'
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(RecentDocumentsTable, {
documents: data
});
};
/* -------------------------------------------------------------------------------------------------
* LastPublishedWidget
* -----------------------------------------------------------------------------------------------*/ const LastPublishedWidget = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { data, isLoading, error } = homepage.useGetRecentDocumentsQuery({
action: 'publish'
});
if (isLoading) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Widget.Loading, {});
}
if (error || !data) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Widget.Error, {});
}
if (data.length === 0) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Widget.NoData, {
children: formatMessage({
id: 'content-manager.widget.last-published.no-data',
defaultMessage: 'No published entries'
})
});
}
return /*#__PURE__*/ jsxRuntime.jsx(RecentDocumentsTable, {
documents: data
});
};
exports.LastEditedWidget = LastEditedWidget;
exports.LastPublishedWidget = LastPublishedWidget;
//# sourceMappingURL=Widgets.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,158 @@
import { jsx, jsxs } from 'react/jsx-runtime';
import { Widget, useTracking } from '@strapi/admin/strapi-admin';
import { Typography, Table, Tbody, Tr, Td, Box, IconButton } from '@strapi/design-system';
import { Pencil } from '@strapi/icons';
import { useIntl } from 'react-intl';
import { useNavigate, Link } from 'react-router-dom';
import { styled } from 'styled-components';
import { DocumentStatus } from '../pages/EditView/components/DocumentStatus.mjs';
import { useGetRecentDocumentsQuery } from '../services/homepage.mjs';
import { RelativeTime } from './RelativeTime.mjs';
const CellTypography = styled(Typography).attrs({
maxWidth: '14.4rem',
display: 'block'
})`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
const RecentDocumentsTable = ({ documents })=>{
const { formatMessage } = useIntl();
const { trackUsage } = useTracking();
const navigate = useNavigate();
const getEditViewLink = (document)=>{
const isSingleType = document.kind === 'singleType';
const kindPath = isSingleType ? 'single-types' : 'collection-types';
const queryParams = document.locale ? `?plugins[i18n][locale]=${document.locale}` : '';
return `/content-manager/${kindPath}/${document.contentTypeUid}${isSingleType ? '' : '/' + document.documentId}${queryParams}`;
};
const handleRowClick = (document)=>()=>{
trackUsage('willEditEntryFromHome');
const link = getEditViewLink(document);
navigate(link);
};
return /*#__PURE__*/ jsx(Table, {
colCount: 5,
rowCount: documents?.length ?? 0,
children: /*#__PURE__*/ jsx(Tbody, {
children: documents?.map((document)=>/*#__PURE__*/ jsxs(Tr, {
onClick: handleRowClick(document),
cursor: "pointer",
children: [
/*#__PURE__*/ jsx(Td, {
children: /*#__PURE__*/ jsx(CellTypography, {
title: document.title,
variant: "omega",
textColor: "neutral800",
children: document.title
})
}),
/*#__PURE__*/ jsx(Td, {
children: /*#__PURE__*/ jsx(CellTypography, {
variant: "omega",
textColor: "neutral600",
children: document.kind === 'singleType' ? formatMessage({
id: 'content-manager.widget.last-edited.single-type',
defaultMessage: 'Single-Type'
}) : formatMessage({
id: document.contentTypeDisplayName,
defaultMessage: document.contentTypeDisplayName
})
})
}),
/*#__PURE__*/ jsx(Td, {
children: /*#__PURE__*/ jsx(Box, {
display: "inline-block",
children: document.status ? /*#__PURE__*/ jsx(DocumentStatus, {
status: document.status
}) : /*#__PURE__*/ jsx(Typography, {
textColor: "neutral600",
"aria-hidden": true,
children: "-"
})
})
}),
/*#__PURE__*/ jsx(Td, {
children: /*#__PURE__*/ jsx(Typography, {
textColor: "neutral600",
children: /*#__PURE__*/ jsx(RelativeTime, {
timestamp: new Date(document.updatedAt)
})
})
}),
/*#__PURE__*/ jsx(Td, {
onClick: (e)=>e.stopPropagation(),
children: /*#__PURE__*/ jsx(Box, {
display: "inline-block",
children: /*#__PURE__*/ jsx(IconButton, {
tag: Link,
to: getEditViewLink(document),
onClick: ()=>trackUsage('willEditEntryFromHome'),
label: formatMessage({
id: 'content-manager.actions.edit.label',
defaultMessage: 'Edit'
}),
variant: "ghost",
children: /*#__PURE__*/ jsx(Pencil, {})
})
})
})
]
}, document.documentId))
})
});
};
/* -------------------------------------------------------------------------------------------------
* LastEditedWidget
* -----------------------------------------------------------------------------------------------*/ const LastEditedWidget = ()=>{
const { formatMessage } = useIntl();
const { data, isLoading, error } = useGetRecentDocumentsQuery({
action: 'update'
});
if (isLoading) {
return /*#__PURE__*/ jsx(Widget.Loading, {});
}
if (error || !data) {
return /*#__PURE__*/ jsx(Widget.Error, {});
}
if (data.length === 0) {
return /*#__PURE__*/ jsx(Widget.NoData, {
children: formatMessage({
id: 'content-manager.widget.last-edited.no-data',
defaultMessage: 'No edited entries'
})
});
}
return /*#__PURE__*/ jsx(RecentDocumentsTable, {
documents: data
});
};
/* -------------------------------------------------------------------------------------------------
* LastPublishedWidget
* -----------------------------------------------------------------------------------------------*/ const LastPublishedWidget = ()=>{
const { formatMessage } = useIntl();
const { data, isLoading, error } = useGetRecentDocumentsQuery({
action: 'publish'
});
if (isLoading) {
return /*#__PURE__*/ jsx(Widget.Loading, {});
}
if (error || !data) {
return /*#__PURE__*/ jsx(Widget.Error, {});
}
if (data.length === 0) {
return /*#__PURE__*/ jsx(Widget.NoData, {
children: formatMessage({
id: 'content-manager.widget.last-published.no-data',
defaultMessage: 'No published entries'
})
});
}
return /*#__PURE__*/ jsx(RecentDocumentsTable, {
documents: data
});
};
export { LastEditedWidget, LastPublishedWidget };
//# sourceMappingURL=Widgets.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,48 @@
'use strict';
const ID = 'id';
const CREATED_BY_ATTRIBUTE_NAME = 'createdBy';
const UPDATED_BY_ATTRIBUTE_NAME = 'updatedBy';
const CREATOR_FIELDS = [
CREATED_BY_ATTRIBUTE_NAME,
UPDATED_BY_ATTRIBUTE_NAME
];
const PUBLISHED_BY_ATTRIBUTE_NAME = 'publishedBy';
const CREATED_AT_ATTRIBUTE_NAME = 'createdAt';
const UPDATED_AT_ATTRIBUTE_NAME = 'updatedAt';
const PUBLISHED_AT_ATTRIBUTE_NAME = 'publishedAt';
const DOCUMENT_META_FIELDS = [
ID,
...CREATOR_FIELDS,
PUBLISHED_BY_ATTRIBUTE_NAME,
CREATED_AT_ATTRIBUTE_NAME,
UPDATED_AT_ATTRIBUTE_NAME,
PUBLISHED_AT_ATTRIBUTE_NAME
];
/**
* List of attribute types that cannot be used as the main field.
* Not sure the name could be any clearer.
*/ const ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = [
'dynamiczone',
'json',
'text',
'relation',
'component',
'boolean',
'media',
'password',
'richtext',
'timestamp',
'blocks'
];
exports.ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD;
exports.CREATED_AT_ATTRIBUTE_NAME = CREATED_AT_ATTRIBUTE_NAME;
exports.CREATED_BY_ATTRIBUTE_NAME = CREATED_BY_ATTRIBUTE_NAME;
exports.CREATOR_FIELDS = CREATOR_FIELDS;
exports.DOCUMENT_META_FIELDS = DOCUMENT_META_FIELDS;
exports.PUBLISHED_AT_ATTRIBUTE_NAME = PUBLISHED_AT_ATTRIBUTE_NAME;
exports.PUBLISHED_BY_ATTRIBUTE_NAME = PUBLISHED_BY_ATTRIBUTE_NAME;
exports.UPDATED_AT_ATTRIBUTE_NAME = UPDATED_AT_ATTRIBUTE_NAME;
exports.UPDATED_BY_ATTRIBUTE_NAME = UPDATED_BY_ATTRIBUTE_NAME;
//# sourceMappingURL=attributes.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"attributes.js","sources":["../../../admin/src/constants/attributes.ts"],"sourcesContent":["const ID = 'id';\n\nconst CREATED_BY_ATTRIBUTE_NAME = 'createdBy';\nconst UPDATED_BY_ATTRIBUTE_NAME = 'updatedBy';\n\nconst CREATOR_FIELDS = [CREATED_BY_ATTRIBUTE_NAME, UPDATED_BY_ATTRIBUTE_NAME];\n\nconst PUBLISHED_BY_ATTRIBUTE_NAME = 'publishedBy';\nconst CREATED_AT_ATTRIBUTE_NAME = 'createdAt';\nconst UPDATED_AT_ATTRIBUTE_NAME = 'updatedAt';\nconst PUBLISHED_AT_ATTRIBUTE_NAME = 'publishedAt';\n\nconst DOCUMENT_META_FIELDS = [\n ID,\n ...CREATOR_FIELDS,\n PUBLISHED_BY_ATTRIBUTE_NAME,\n CREATED_AT_ATTRIBUTE_NAME,\n UPDATED_AT_ATTRIBUTE_NAME,\n PUBLISHED_AT_ATTRIBUTE_NAME,\n];\n\n/**\n * List of attribute types that cannot be used as the main field.\n * Not sure the name could be any clearer.\n */\nconst ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = [\n 'dynamiczone',\n 'json',\n 'text',\n 'relation',\n 'component',\n 'boolean',\n 'media',\n 'password',\n 'richtext',\n 'timestamp',\n 'blocks',\n];\n\nexport {\n ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD,\n CREATED_AT_ATTRIBUTE_NAME,\n UPDATED_AT_ATTRIBUTE_NAME,\n PUBLISHED_AT_ATTRIBUTE_NAME,\n CREATED_BY_ATTRIBUTE_NAME,\n UPDATED_BY_ATTRIBUTE_NAME,\n PUBLISHED_BY_ATTRIBUTE_NAME,\n CREATOR_FIELDS,\n DOCUMENT_META_FIELDS,\n};\n"],"names":["ID","CREATED_BY_ATTRIBUTE_NAME","UPDATED_BY_ATTRIBUTE_NAME","CREATOR_FIELDS","PUBLISHED_BY_ATTRIBUTE_NAME","CREATED_AT_ATTRIBUTE_NAME","UPDATED_AT_ATTRIBUTE_NAME","PUBLISHED_AT_ATTRIBUTE_NAME","DOCUMENT_META_FIELDS","ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD"],"mappings":";;AAAA,MAAMA,EAAK,GAAA,IAAA;AAEX,MAAMC,yBAA4B,GAAA;AAClC,MAAMC,yBAA4B,GAAA;AAElC,MAAMC,cAAiB,GAAA;AAACF,IAAAA,yBAAAA;AAA2BC,IAAAA;AAA0B;AAE7E,MAAME,2BAA8B,GAAA;AACpC,MAAMC,yBAA4B,GAAA;AAClC,MAAMC,yBAA4B,GAAA;AAClC,MAAMC,2BAA8B,GAAA;AAEpC,MAAMC,oBAAuB,GAAA;AAC3BR,IAAAA,EAAAA;AACGG,IAAAA,GAAAA,cAAAA;AACHC,IAAAA,2BAAAA;AACAC,IAAAA,yBAAAA;AACAC,IAAAA,yBAAAA;AACAC,IAAAA;AACD;AAED;;;AAGC,UACKE,yCAA4C,GAAA;AAChD,IAAA,aAAA;AACA,IAAA,MAAA;AACA,IAAA,MAAA;AACA,IAAA,UAAA;AACA,IAAA,WAAA;AACA,IAAA,SAAA;AACA,IAAA,OAAA;AACA,IAAA,UAAA;AACA,IAAA,UAAA;AACA,IAAA,WAAA;AACA,IAAA;AACD;;;;;;;;;;;;"}

View File

@@ -0,0 +1,38 @@
const ID = 'id';
const CREATED_BY_ATTRIBUTE_NAME = 'createdBy';
const UPDATED_BY_ATTRIBUTE_NAME = 'updatedBy';
const CREATOR_FIELDS = [
CREATED_BY_ATTRIBUTE_NAME,
UPDATED_BY_ATTRIBUTE_NAME
];
const PUBLISHED_BY_ATTRIBUTE_NAME = 'publishedBy';
const CREATED_AT_ATTRIBUTE_NAME = 'createdAt';
const UPDATED_AT_ATTRIBUTE_NAME = 'updatedAt';
const PUBLISHED_AT_ATTRIBUTE_NAME = 'publishedAt';
const DOCUMENT_META_FIELDS = [
ID,
...CREATOR_FIELDS,
PUBLISHED_BY_ATTRIBUTE_NAME,
CREATED_AT_ATTRIBUTE_NAME,
UPDATED_AT_ATTRIBUTE_NAME,
PUBLISHED_AT_ATTRIBUTE_NAME
];
/**
* List of attribute types that cannot be used as the main field.
* Not sure the name could be any clearer.
*/ const ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = [
'dynamiczone',
'json',
'text',
'relation',
'component',
'boolean',
'media',
'password',
'richtext',
'timestamp',
'blocks'
];
export { ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD, CREATED_AT_ATTRIBUTE_NAME, CREATED_BY_ATTRIBUTE_NAME, CREATOR_FIELDS, DOCUMENT_META_FIELDS, PUBLISHED_AT_ATTRIBUTE_NAME, PUBLISHED_BY_ATTRIBUTE_NAME, UPDATED_AT_ATTRIBUTE_NAME, UPDATED_BY_ATTRIBUTE_NAME };
//# sourceMappingURL=attributes.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"attributes.mjs","sources":["../../../admin/src/constants/attributes.ts"],"sourcesContent":["const ID = 'id';\n\nconst CREATED_BY_ATTRIBUTE_NAME = 'createdBy';\nconst UPDATED_BY_ATTRIBUTE_NAME = 'updatedBy';\n\nconst CREATOR_FIELDS = [CREATED_BY_ATTRIBUTE_NAME, UPDATED_BY_ATTRIBUTE_NAME];\n\nconst PUBLISHED_BY_ATTRIBUTE_NAME = 'publishedBy';\nconst CREATED_AT_ATTRIBUTE_NAME = 'createdAt';\nconst UPDATED_AT_ATTRIBUTE_NAME = 'updatedAt';\nconst PUBLISHED_AT_ATTRIBUTE_NAME = 'publishedAt';\n\nconst DOCUMENT_META_FIELDS = [\n ID,\n ...CREATOR_FIELDS,\n PUBLISHED_BY_ATTRIBUTE_NAME,\n CREATED_AT_ATTRIBUTE_NAME,\n UPDATED_AT_ATTRIBUTE_NAME,\n PUBLISHED_AT_ATTRIBUTE_NAME,\n];\n\n/**\n * List of attribute types that cannot be used as the main field.\n * Not sure the name could be any clearer.\n */\nconst ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD = [\n 'dynamiczone',\n 'json',\n 'text',\n 'relation',\n 'component',\n 'boolean',\n 'media',\n 'password',\n 'richtext',\n 'timestamp',\n 'blocks',\n];\n\nexport {\n ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD,\n CREATED_AT_ATTRIBUTE_NAME,\n UPDATED_AT_ATTRIBUTE_NAME,\n PUBLISHED_AT_ATTRIBUTE_NAME,\n CREATED_BY_ATTRIBUTE_NAME,\n UPDATED_BY_ATTRIBUTE_NAME,\n PUBLISHED_BY_ATTRIBUTE_NAME,\n CREATOR_FIELDS,\n DOCUMENT_META_FIELDS,\n};\n"],"names":["ID","CREATED_BY_ATTRIBUTE_NAME","UPDATED_BY_ATTRIBUTE_NAME","CREATOR_FIELDS","PUBLISHED_BY_ATTRIBUTE_NAME","CREATED_AT_ATTRIBUTE_NAME","UPDATED_AT_ATTRIBUTE_NAME","PUBLISHED_AT_ATTRIBUTE_NAME","DOCUMENT_META_FIELDS","ATTRIBUTE_TYPES_THAT_CANNOT_BE_MAIN_FIELD"],"mappings":"AAAA,MAAMA,EAAK,GAAA,IAAA;AAEX,MAAMC,yBAA4B,GAAA;AAClC,MAAMC,yBAA4B,GAAA;AAElC,MAAMC,cAAiB,GAAA;AAACF,IAAAA,yBAAAA;AAA2BC,IAAAA;AAA0B;AAE7E,MAAME,2BAA8B,GAAA;AACpC,MAAMC,yBAA4B,GAAA;AAClC,MAAMC,yBAA4B,GAAA;AAClC,MAAMC,2BAA8B,GAAA;AAEpC,MAAMC,oBAAuB,GAAA;AAC3BR,IAAAA,EAAAA;AACGG,IAAAA,GAAAA,cAAAA;AACHC,IAAAA,2BAAAA;AACAC,IAAAA,yBAAAA;AACAC,IAAAA,yBAAAA;AACAC,IAAAA;AACD;AAED;;;AAGC,UACKE,yCAA4C,GAAA;AAChD,IAAA,aAAA;AACA,IAAA,MAAA;AACA,IAAA,MAAA;AACA,IAAA,UAAA;AACA,IAAA,WAAA;AACA,IAAA,SAAA;AACA,IAAA,OAAA;AACA,IAAA,UAAA;AACA,IAAA,UAAA;AACA,IAAA,WAAA;AACA,IAAA;AACD;;;;"}

View File

@@ -0,0 +1,8 @@
'use strict';
const SINGLE_TYPES = 'single-types';
const COLLECTION_TYPES = 'collection-types';
exports.COLLECTION_TYPES = COLLECTION_TYPES;
exports.SINGLE_TYPES = SINGLE_TYPES;
//# sourceMappingURL=collections.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"collections.js","sources":["../../../admin/src/constants/collections.ts"],"sourcesContent":["const SINGLE_TYPES = 'single-types';\nconst COLLECTION_TYPES = 'collection-types';\n\nexport { SINGLE_TYPES, COLLECTION_TYPES };\n"],"names":["SINGLE_TYPES","COLLECTION_TYPES"],"mappings":";;AAAA,MAAMA,YAAe,GAAA;AACrB,MAAMC,gBAAmB,GAAA;;;;;"}

View File

@@ -0,0 +1,5 @@
const SINGLE_TYPES = 'single-types';
const COLLECTION_TYPES = 'collection-types';
export { COLLECTION_TYPES, SINGLE_TYPES };
//# sourceMappingURL=collections.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"collections.mjs","sources":["../../../admin/src/constants/collections.ts"],"sourcesContent":["const SINGLE_TYPES = 'single-types';\nconst COLLECTION_TYPES = 'collection-types';\n\nexport { SINGLE_TYPES, COLLECTION_TYPES };\n"],"names":["SINGLE_TYPES","COLLECTION_TYPES"],"mappings":"AAAA,MAAMA,YAAe,GAAA;AACrB,MAAMC,gBAAmB,GAAA;;;;"}

View File

@@ -0,0 +1,13 @@
'use strict';
const ItemTypes = {
COMPONENT: 'component',
EDIT_FIELD: 'editField',
FIELD: 'field',
DYNAMIC_ZONE: 'dynamicZone',
RELATION: 'relation',
BLOCKS: 'blocks'
};
exports.ItemTypes = ItemTypes;
//# sourceMappingURL=dragAndDrop.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"dragAndDrop.js","sources":["../../../admin/src/constants/dragAndDrop.ts"],"sourcesContent":["export const ItemTypes = {\n COMPONENT: 'component',\n EDIT_FIELD: 'editField',\n FIELD: 'field',\n DYNAMIC_ZONE: 'dynamicZone',\n RELATION: 'relation',\n BLOCKS: 'blocks',\n} as const;\n"],"names":["ItemTypes","COMPONENT","EDIT_FIELD","FIELD","DYNAMIC_ZONE","RELATION","BLOCKS"],"mappings":";;MAAaA,SAAY,GAAA;IACvBC,SAAW,EAAA,WAAA;IACXC,UAAY,EAAA,WAAA;IACZC,KAAO,EAAA,OAAA;IACPC,YAAc,EAAA,aAAA;IACdC,QAAU,EAAA,UAAA;IACVC,MAAQ,EAAA;AACV;;;;"}

View File

@@ -0,0 +1,11 @@
const ItemTypes = {
COMPONENT: 'component',
EDIT_FIELD: 'editField',
FIELD: 'field',
DYNAMIC_ZONE: 'dynamicZone',
RELATION: 'relation',
BLOCKS: 'blocks'
};
export { ItemTypes };
//# sourceMappingURL=dragAndDrop.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"dragAndDrop.mjs","sources":["../../../admin/src/constants/dragAndDrop.ts"],"sourcesContent":["export const ItemTypes = {\n COMPONENT: 'component',\n EDIT_FIELD: 'editField',\n FIELD: 'field',\n DYNAMIC_ZONE: 'dynamicZone',\n RELATION: 'relation',\n BLOCKS: 'blocks',\n} as const;\n"],"names":["ItemTypes","COMPONENT","EDIT_FIELD","FIELD","DYNAMIC_ZONE","RELATION","BLOCKS"],"mappings":"MAAaA,SAAY,GAAA;IACvBC,SAAW,EAAA,WAAA;IACXC,UAAY,EAAA,WAAA;IACZC,KAAO,EAAA,OAAA;IACPC,YAAc,EAAA,aAAA;IACdC,QAAU,EAAA,UAAA;IACVC,MAAQ,EAAA;AACV;;;;"}

View File

@@ -0,0 +1,27 @@
'use strict';
const HOOKS = {
/**
* Hook that allows to mutate the displayed headers of the list view table
* @constant
* @type {string}
*/ INJECT_COLUMN_IN_TABLE: 'Admin/CM/pages/ListView/inject-column-in-table',
/**
* Hook that allows to mutate the CM's collection types links pre-set filters
* @constant
* @type {string}
*/ MUTATE_COLLECTION_TYPES_LINKS: 'Admin/CM/pages/App/mutate-collection-types-links',
/**
* Hook that allows to mutate the CM's edit view layout
* @constant
* @type {string}
*/ MUTATE_EDIT_VIEW_LAYOUT: 'Admin/CM/pages/EditView/mutate-edit-view-layout',
/**
* Hook that allows to mutate the CM's single types links pre-set filters
* @constant
* @type {string}
*/ MUTATE_SINGLE_TYPES_LINKS: 'Admin/CM/pages/App/mutate-single-types-links'
};
exports.HOOKS = HOOKS;
//# sourceMappingURL=hooks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hooks.js","sources":["../../../admin/src/constants/hooks.ts"],"sourcesContent":["export const HOOKS = {\n /**\n * Hook that allows to mutate the displayed headers of the list view table\n * @constant\n * @type {string}\n */\n INJECT_COLUMN_IN_TABLE: 'Admin/CM/pages/ListView/inject-column-in-table',\n\n /**\n * Hook that allows to mutate the CM's collection types links pre-set filters\n * @constant\n * @type {string}\n */\n MUTATE_COLLECTION_TYPES_LINKS: 'Admin/CM/pages/App/mutate-collection-types-links',\n\n /**\n * Hook that allows to mutate the CM's edit view layout\n * @constant\n * @type {string}\n */\n MUTATE_EDIT_VIEW_LAYOUT: 'Admin/CM/pages/EditView/mutate-edit-view-layout',\n\n /**\n * Hook that allows to mutate the CM's single types links pre-set filters\n * @constant\n * @type {string}\n */\n MUTATE_SINGLE_TYPES_LINKS: 'Admin/CM/pages/App/mutate-single-types-links',\n};\n"],"names":["HOOKS","INJECT_COLUMN_IN_TABLE","MUTATE_COLLECTION_TYPES_LINKS","MUTATE_EDIT_VIEW_LAYOUT","MUTATE_SINGLE_TYPES_LINKS"],"mappings":";;MAAaA,KAAQ,GAAA;AACnB;;;;AAIC,MACDC,sBAAwB,EAAA,gDAAA;AAExB;;;;AAIC,MACDC,6BAA+B,EAAA,kDAAA;AAE/B;;;;AAIC,MACDC,uBAAyB,EAAA,iDAAA;AAEzB;;;;AAIC,MACDC,yBAA2B,EAAA;AAC7B;;;;"}

View File

@@ -0,0 +1,25 @@
const HOOKS = {
/**
* Hook that allows to mutate the displayed headers of the list view table
* @constant
* @type {string}
*/ INJECT_COLUMN_IN_TABLE: 'Admin/CM/pages/ListView/inject-column-in-table',
/**
* Hook that allows to mutate the CM's collection types links pre-set filters
* @constant
* @type {string}
*/ MUTATE_COLLECTION_TYPES_LINKS: 'Admin/CM/pages/App/mutate-collection-types-links',
/**
* Hook that allows to mutate the CM's edit view layout
* @constant
* @type {string}
*/ MUTATE_EDIT_VIEW_LAYOUT: 'Admin/CM/pages/EditView/mutate-edit-view-layout',
/**
* Hook that allows to mutate the CM's single types links pre-set filters
* @constant
* @type {string}
*/ MUTATE_SINGLE_TYPES_LINKS: 'Admin/CM/pages/App/mutate-single-types-links'
};
export { HOOKS };
//# sourceMappingURL=hooks.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hooks.mjs","sources":["../../../admin/src/constants/hooks.ts"],"sourcesContent":["export const HOOKS = {\n /**\n * Hook that allows to mutate the displayed headers of the list view table\n * @constant\n * @type {string}\n */\n INJECT_COLUMN_IN_TABLE: 'Admin/CM/pages/ListView/inject-column-in-table',\n\n /**\n * Hook that allows to mutate the CM's collection types links pre-set filters\n * @constant\n * @type {string}\n */\n MUTATE_COLLECTION_TYPES_LINKS: 'Admin/CM/pages/App/mutate-collection-types-links',\n\n /**\n * Hook that allows to mutate the CM's edit view layout\n * @constant\n * @type {string}\n */\n MUTATE_EDIT_VIEW_LAYOUT: 'Admin/CM/pages/EditView/mutate-edit-view-layout',\n\n /**\n * Hook that allows to mutate the CM's single types links pre-set filters\n * @constant\n * @type {string}\n */\n MUTATE_SINGLE_TYPES_LINKS: 'Admin/CM/pages/App/mutate-single-types-links',\n};\n"],"names":["HOOKS","INJECT_COLUMN_IN_TABLE","MUTATE_COLLECTION_TYPES_LINKS","MUTATE_EDIT_VIEW_LAYOUT","MUTATE_SINGLE_TYPES_LINKS"],"mappings":"MAAaA,KAAQ,GAAA;AACnB;;;;AAIC,MACDC,sBAAwB,EAAA,gDAAA;AAExB;;;;AAIC,MACDC,6BAA+B,EAAA,kDAAA;AAE/B;;;;AAIC,MACDC,uBAAyB,EAAA,iDAAA;AAEzB;;;;AAIC,MACDC,yBAA2B,EAAA;AAC7B;;;;"}

View File

@@ -0,0 +1,14 @@
'use strict';
const PLUGIN_ID = 'content-manager';
const PERMISSIONS = [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.read',
'plugin::content-manager.explorer.update',
'plugin::content-manager.explorer.delete',
'plugin::content-manager.explorer.publish'
];
exports.PERMISSIONS = PERMISSIONS;
exports.PLUGIN_ID = PLUGIN_ID;
//# sourceMappingURL=plugin.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"plugin.js","sources":["../../../admin/src/constants/plugin.ts"],"sourcesContent":["const PLUGIN_ID = 'content-manager';\n\nconst PERMISSIONS = [\n 'plugin::content-manager.explorer.create',\n 'plugin::content-manager.explorer.read',\n 'plugin::content-manager.explorer.update',\n 'plugin::content-manager.explorer.delete',\n 'plugin::content-manager.explorer.publish',\n];\n\nexport { PLUGIN_ID, PERMISSIONS };\n"],"names":["PLUGIN_ID","PERMISSIONS"],"mappings":";;AAAA,MAAMA,SAAY,GAAA;AAElB,MAAMC,WAAc,GAAA;AAClB,IAAA,yCAAA;AACA,IAAA,uCAAA;AACA,IAAA,yCAAA;AACA,IAAA,yCAAA;AACA,IAAA;AACD;;;;;"}

View File

@@ -0,0 +1,11 @@
const PLUGIN_ID = 'content-manager';
const PERMISSIONS = [
'plugin::content-manager.explorer.create',
'plugin::content-manager.explorer.read',
'plugin::content-manager.explorer.update',
'plugin::content-manager.explorer.delete',
'plugin::content-manager.explorer.publish'
];
export { PERMISSIONS, PLUGIN_ID };
//# sourceMappingURL=plugin.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"plugin.mjs","sources":["../../../admin/src/constants/plugin.ts"],"sourcesContent":["const PLUGIN_ID = 'content-manager';\n\nconst PERMISSIONS = [\n 'plugin::content-manager.explorer.create',\n 'plugin::content-manager.explorer.read',\n 'plugin::content-manager.explorer.update',\n 'plugin::content-manager.explorer.delete',\n 'plugin::content-manager.explorer.publish',\n];\n\nexport { PLUGIN_ID, PERMISSIONS };\n"],"names":["PLUGIN_ID","PERMISSIONS"],"mappings":"AAAA,MAAMA,SAAY,GAAA;AAElB,MAAMC,WAAc,GAAA;AAClB,IAAA,yCAAA;AACA,IAAA,uCAAA;AACA,IAAA,yCAAA;AACA,IAAA,yCAAA;AACA,IAAA;AACD;;;;"}

View File

@@ -0,0 +1,133 @@
'use strict';
var InjectionZone = require('./components/InjectionZone.js');
var plugin = require('./constants/plugin.js');
var DocumentActions = require('./pages/EditView/components/DocumentActions.js');
var Header = require('./pages/EditView/components/Header.js');
var Panels = require('./pages/EditView/components/Panels.js');
var Actions = require('./pages/ListView/components/BulkActions/Actions.js');
var TableActions = require('./pages/ListView/components/TableActions.js');
/* -------------------------------------------------------------------------------------------------
* ContentManager plugin
* -----------------------------------------------------------------------------------------------*/ class ContentManagerPlugin {
addEditViewSidePanel(panels) {
if (Array.isArray(panels)) {
this.editViewSidePanels = [
...this.editViewSidePanels,
...panels
];
} else if (typeof panels === 'function') {
this.editViewSidePanels = panels(this.editViewSidePanels);
} else {
throw new Error(`Expected the \`panels\` passed to \`addEditViewSidePanel\` to be an array or a function, but received ${getPrintableType(panels)}`);
}
}
addDocumentAction(actions) {
if (Array.isArray(actions)) {
this.documentActions = [
...this.documentActions,
...actions
];
} else if (typeof actions === 'function') {
this.documentActions = actions(this.documentActions);
} else {
throw new Error(`Expected the \`actions\` passed to \`addDocumentAction\` to be an array or a function, but received ${getPrintableType(actions)}`);
}
}
addDocumentHeaderAction(actions) {
if (Array.isArray(actions)) {
this.headerActions = [
...this.headerActions,
...actions
];
} else if (typeof actions === 'function') {
this.headerActions = actions(this.headerActions);
} else {
throw new Error(`Expected the \`actions\` passed to \`addDocumentHeaderAction\` to be an array or a function, but received ${getPrintableType(actions)}`);
}
}
addBulkAction(actions) {
if (Array.isArray(actions)) {
this.bulkActions = [
...this.bulkActions,
...actions
];
} else if (typeof actions === 'function') {
this.bulkActions = actions(this.bulkActions);
} else {
throw new Error(`Expected the \`actions\` passed to \`addBulkAction\` to be an array or a function, but received ${getPrintableType(actions)}`);
}
}
get config() {
return {
id: plugin.PLUGIN_ID,
name: 'Content Manager',
injectionZones: InjectionZone.INJECTION_ZONES,
apis: {
addBulkAction: this.addBulkAction.bind(this),
addDocumentAction: this.addDocumentAction.bind(this),
addDocumentHeaderAction: this.addDocumentHeaderAction.bind(this),
addEditViewSidePanel: this.addEditViewSidePanel.bind(this),
getBulkActions: ()=>this.bulkActions,
getDocumentActions: (position)=>{
/**
* When possible, pre-filter the actions by the components static position property.
* This avoids rendering the actions in multiple places where they weren't displayed,
* which wasn't visible but created issues with useEffect for instance.
* The response should still be filtered by the position, as the static property is new
* and not mandatory to avoid a breaking change.
*/ if (position) {
return this.documentActions.filter((action)=>{
return action.position == undefined || [
action.position
].flat().includes(position);
});
}
return this.documentActions;
},
getEditViewSidePanels: ()=>this.editViewSidePanels,
getHeaderActions: ()=>this.headerActions
}
};
}
constructor(){
/**
* The following properties are the stored ones provided by any plugins registering with
* the content-manager. The function calls however, need to be called at runtime in the
* application, so instead we collate them and run them later with the complete list incl.
* ones already registered & the context of the view.
*/ this.bulkActions = [
...Actions.DEFAULT_BULK_ACTIONS
];
this.documentActions = [
...DocumentActions.DEFAULT_ACTIONS,
...TableActions.DEFAULT_TABLE_ROW_ACTIONS,
...Header.DEFAULT_HEADER_ACTIONS
];
this.editViewSidePanels = [
Panels.ActionsPanel
];
this.headerActions = [];
}
}
/* -------------------------------------------------------------------------------------------------
* getPrintableType
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description Gets the human-friendly printable type name for the given value, for instance it will yield
* `array` instead of `object`, as the native `typeof` operator would do.
*/ const getPrintableType = (value)=>{
const nativeType = typeof value;
if (nativeType === 'object') {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
if (value instanceof Object && value.constructor.name !== 'Object') {
return value.constructor.name;
}
}
return nativeType;
};
exports.ContentManagerPlugin = ContentManagerPlugin;
//# sourceMappingURL=content-manager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,131 @@
import { INJECTION_ZONES } from './components/InjectionZone.mjs';
import { PLUGIN_ID } from './constants/plugin.mjs';
import { DEFAULT_ACTIONS } from './pages/EditView/components/DocumentActions.mjs';
import { DEFAULT_HEADER_ACTIONS } from './pages/EditView/components/Header.mjs';
import { ActionsPanel } from './pages/EditView/components/Panels.mjs';
import { DEFAULT_BULK_ACTIONS } from './pages/ListView/components/BulkActions/Actions.mjs';
import { DEFAULT_TABLE_ROW_ACTIONS } from './pages/ListView/components/TableActions.mjs';
/* -------------------------------------------------------------------------------------------------
* ContentManager plugin
* -----------------------------------------------------------------------------------------------*/ class ContentManagerPlugin {
addEditViewSidePanel(panels) {
if (Array.isArray(panels)) {
this.editViewSidePanels = [
...this.editViewSidePanels,
...panels
];
} else if (typeof panels === 'function') {
this.editViewSidePanels = panels(this.editViewSidePanels);
} else {
throw new Error(`Expected the \`panels\` passed to \`addEditViewSidePanel\` to be an array or a function, but received ${getPrintableType(panels)}`);
}
}
addDocumentAction(actions) {
if (Array.isArray(actions)) {
this.documentActions = [
...this.documentActions,
...actions
];
} else if (typeof actions === 'function') {
this.documentActions = actions(this.documentActions);
} else {
throw new Error(`Expected the \`actions\` passed to \`addDocumentAction\` to be an array or a function, but received ${getPrintableType(actions)}`);
}
}
addDocumentHeaderAction(actions) {
if (Array.isArray(actions)) {
this.headerActions = [
...this.headerActions,
...actions
];
} else if (typeof actions === 'function') {
this.headerActions = actions(this.headerActions);
} else {
throw new Error(`Expected the \`actions\` passed to \`addDocumentHeaderAction\` to be an array or a function, but received ${getPrintableType(actions)}`);
}
}
addBulkAction(actions) {
if (Array.isArray(actions)) {
this.bulkActions = [
...this.bulkActions,
...actions
];
} else if (typeof actions === 'function') {
this.bulkActions = actions(this.bulkActions);
} else {
throw new Error(`Expected the \`actions\` passed to \`addBulkAction\` to be an array or a function, but received ${getPrintableType(actions)}`);
}
}
get config() {
return {
id: PLUGIN_ID,
name: 'Content Manager',
injectionZones: INJECTION_ZONES,
apis: {
addBulkAction: this.addBulkAction.bind(this),
addDocumentAction: this.addDocumentAction.bind(this),
addDocumentHeaderAction: this.addDocumentHeaderAction.bind(this),
addEditViewSidePanel: this.addEditViewSidePanel.bind(this),
getBulkActions: ()=>this.bulkActions,
getDocumentActions: (position)=>{
/**
* When possible, pre-filter the actions by the components static position property.
* This avoids rendering the actions in multiple places where they weren't displayed,
* which wasn't visible but created issues with useEffect for instance.
* The response should still be filtered by the position, as the static property is new
* and not mandatory to avoid a breaking change.
*/ if (position) {
return this.documentActions.filter((action)=>{
return action.position == undefined || [
action.position
].flat().includes(position);
});
}
return this.documentActions;
},
getEditViewSidePanels: ()=>this.editViewSidePanels,
getHeaderActions: ()=>this.headerActions
}
};
}
constructor(){
/**
* The following properties are the stored ones provided by any plugins registering with
* the content-manager. The function calls however, need to be called at runtime in the
* application, so instead we collate them and run them later with the complete list incl.
* ones already registered & the context of the view.
*/ this.bulkActions = [
...DEFAULT_BULK_ACTIONS
];
this.documentActions = [
...DEFAULT_ACTIONS,
...DEFAULT_TABLE_ROW_ACTIONS,
...DEFAULT_HEADER_ACTIONS
];
this.editViewSidePanels = [
ActionsPanel
];
this.headerActions = [];
}
}
/* -------------------------------------------------------------------------------------------------
* getPrintableType
* -----------------------------------------------------------------------------------------------*/ /**
* @internal
* @description Gets the human-friendly printable type name for the given value, for instance it will yield
* `array` instead of `object`, as the native `typeof` operator would do.
*/ const getPrintableType = (value)=>{
const nativeType = typeof value;
if (nativeType === 'object') {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
if (value instanceof Object && value.constructor.name !== 'Object') {
return value.constructor.name;
}
}
return nativeType;
};
export { ContentManagerPlugin };
//# sourceMappingURL=content-manager.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,126 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var reactRouterDom = require('react-router-dom');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const [DocumentRBACProvider, useDocumentRBAC] = strapiAdmin.createContext('DocumentRBAC', {
canCreate: false,
canCreateFields: [],
canDelete: false,
canPublish: false,
canRead: false,
canReadFields: [],
canUpdate: false,
canUpdateFields: [],
canUserAction: ()=>false,
isLoading: false
});
/**
* @internal This component is not meant to be used outside of the Content Manager plugin.
* It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.
* If you do use the hook outside of the context, we default to `false` for all actions.
*
* It then creates an list of `can{Action}` that are passed to the context for consumption
* within the app to enforce RBAC.
*/ const DocumentRBAC = ({ children, permissions, model })=>{
const { slug } = reactRouterDom.useParams();
if (!slug && !model) {
throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');
}
const contentTypeUid = model ?? slug;
const [{ rawQuery }] = strapiAdmin.useQueryParams();
const userPermissions = strapiAdmin.useAuth('DocumentRBAC', (state)=>state.permissions);
const contentTypePermissions = React__namespace.useMemo(()=>{
const contentTypePermissions = userPermissions.filter((permission)=>permission.subject === contentTypeUid);
return contentTypePermissions.reduce((acc, permission)=>{
const [action] = permission.action.split('.').slice(-1);
return {
...acc,
[action]: [
permission
]
};
}, {});
}, [
contentTypeUid,
userPermissions
]);
const { isLoading, allowedActions } = strapiAdmin.useRBAC(contentTypePermissions, permissions ?? undefined, // TODO: useRBAC context should be typed and built differently
// We are passing raw query as context to the hook so that it can
// rely on the locale provided from DocumentRBAC for its permission calculations.
rawQuery);
const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
/**
* @description Checks if the user can perform an action on a field based on the field names
* provided as the second argument.
*/ const canUserAction = React__namespace.useCallback((fieldName, fieldsUserCanAction, fieldType)=>{
const name = removeNumericalStrings(fieldName.split('.'));
const componentFieldNames = fieldsUserCanAction// filter out fields that aren't components (components are dot separated)
.filter((field)=>field.split('.').length > 1);
if (fieldType === 'component') {
// check if the field name is within any of those arrays
return componentFieldNames.some((field)=>{
return field.includes(name.join('.'));
});
}
/**
* The field is within a component.
*/ if (name.length > 1) {
return componentFieldNames.includes(name.join('.'));
}
/**
* just a regular field
*/ return fieldsUserCanAction.includes(fieldName);
}, []);
if (isLoading) {
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Page.Loading, {});
}
return /*#__PURE__*/ jsxRuntime.jsx(DocumentRBACProvider, {
isLoading: isLoading,
canCreateFields: canCreateFields,
canReadFields: canReadFields,
canUpdateFields: canUpdateFields,
canUserAction: canUserAction,
...allowedActions,
children: children
});
};
/**
* @internal it's really small, but it's used three times in a row and DRY for something this straight forward.
*/ const extractAndDedupeFields = (permissions = [])=>permissions.flatMap((permission)=>permission.properties?.fields).filter((field, index, arr)=>arr.indexOf(field) === index && typeof field === 'string');
/**
* @internal removes numerical strings from arrays.
* @example
* ```ts
* const name = 'a.0.b';
* const res = removeNumericalStrings(name.split('.'));
* console.log(res); // ['a', 'b']
* ```
*/ const removeNumericalStrings = (arr)=>arr.filter((item)=>isNaN(Number(item)));
exports.DocumentRBAC = DocumentRBAC;
exports.useDocumentRBAC = useDocumentRBAC;
//# sourceMappingURL=DocumentRBAC.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,104 @@
import { jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { createContext, useQueryParams, useAuth, useRBAC, Page } from '@strapi/admin/strapi-admin';
import { useParams } from 'react-router-dom';
const [DocumentRBACProvider, useDocumentRBAC] = createContext('DocumentRBAC', {
canCreate: false,
canCreateFields: [],
canDelete: false,
canPublish: false,
canRead: false,
canReadFields: [],
canUpdate: false,
canUpdateFields: [],
canUserAction: ()=>false,
isLoading: false
});
/**
* @internal This component is not meant to be used outside of the Content Manager plugin.
* It depends on knowing the slug/model of the content-type using the params of the URL or the model if it is passed as arg.
* If you do use the hook outside of the context, we default to `false` for all actions.
*
* It then creates an list of `can{Action}` that are passed to the context for consumption
* within the app to enforce RBAC.
*/ const DocumentRBAC = ({ children, permissions, model })=>{
const { slug } = useParams();
if (!slug && !model) {
throw new Error('Cannot find the slug param in the URL or the model prop is not provided.');
}
const contentTypeUid = model ?? slug;
const [{ rawQuery }] = useQueryParams();
const userPermissions = useAuth('DocumentRBAC', (state)=>state.permissions);
const contentTypePermissions = React.useMemo(()=>{
const contentTypePermissions = userPermissions.filter((permission)=>permission.subject === contentTypeUid);
return contentTypePermissions.reduce((acc, permission)=>{
const [action] = permission.action.split('.').slice(-1);
return {
...acc,
[action]: [
permission
]
};
}, {});
}, [
contentTypeUid,
userPermissions
]);
const { isLoading, allowedActions } = useRBAC(contentTypePermissions, permissions ?? undefined, // TODO: useRBAC context should be typed and built differently
// We are passing raw query as context to the hook so that it can
// rely on the locale provided from DocumentRBAC for its permission calculations.
rawQuery);
const canCreateFields = !isLoading && allowedActions.canCreate ? extractAndDedupeFields(contentTypePermissions.create) : [];
const canReadFields = !isLoading && allowedActions.canRead ? extractAndDedupeFields(contentTypePermissions.read) : [];
const canUpdateFields = !isLoading && allowedActions.canUpdate ? extractAndDedupeFields(contentTypePermissions.update) : [];
/**
* @description Checks if the user can perform an action on a field based on the field names
* provided as the second argument.
*/ const canUserAction = React.useCallback((fieldName, fieldsUserCanAction, fieldType)=>{
const name = removeNumericalStrings(fieldName.split('.'));
const componentFieldNames = fieldsUserCanAction// filter out fields that aren't components (components are dot separated)
.filter((field)=>field.split('.').length > 1);
if (fieldType === 'component') {
// check if the field name is within any of those arrays
return componentFieldNames.some((field)=>{
return field.includes(name.join('.'));
});
}
/**
* The field is within a component.
*/ if (name.length > 1) {
return componentFieldNames.includes(name.join('.'));
}
/**
* just a regular field
*/ return fieldsUserCanAction.includes(fieldName);
}, []);
if (isLoading) {
return /*#__PURE__*/ jsx(Page.Loading, {});
}
return /*#__PURE__*/ jsx(DocumentRBACProvider, {
isLoading: isLoading,
canCreateFields: canCreateFields,
canReadFields: canReadFields,
canUpdateFields: canUpdateFields,
canUserAction: canUserAction,
...allowedActions,
children: children
});
};
/**
* @internal it's really small, but it's used three times in a row and DRY for something this straight forward.
*/ const extractAndDedupeFields = (permissions = [])=>permissions.flatMap((permission)=>permission.properties?.fields).filter((field, index, arr)=>arr.indexOf(field) === index && typeof field === 'string');
/**
* @internal removes numerical strings from arrays.
* @example
* ```ts
* const name = 'a.0.b';
* const res = removeNumericalStrings(name.split('.'));
* console.log(res); // ['a', 'b']
* ```
*/ const removeNumericalStrings = (arr)=>arr.filter((item)=>isNaN(Number(item)));
export { DocumentRBAC, useDocumentRBAC };
//# sourceMappingURL=DocumentRBAC.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,61 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var Icons = require('@strapi/icons');
var qs = require('qs');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
const HistoryAction = ({ model, document })=>{
const { formatMessage } = reactIntl.useIntl();
const [{ query }] = strapiAdmin.useQueryParams();
const navigate = reactRouterDom.useNavigate();
const { trackUsage } = strapiAdmin.useTracking();
const { pathname } = reactRouterDom.useLocation();
const pluginsQueryParams = qs.stringify({
plugins: query.plugins
}, {
encode: false
});
if (!window.strapi.features.isEnabled('cms-content-history')) {
return null;
}
const handleOnClick = ()=>{
const destination = {
pathname: 'history',
search: pluginsQueryParams
};
trackUsage('willNavigate', {
from: pathname,
to: `${pathname}/${destination.pathname}`
});
navigate(destination);
};
return {
icon: /*#__PURE__*/ jsxRuntime.jsx(Icons.ClockCounterClockwise, {}),
label: formatMessage({
id: 'content-manager.history.document-action',
defaultMessage: 'Content History'
}),
onClick: handleOnClick,
disabled: /**
* The user is creating a new document.
* It hasn't been saved yet, so there's no history to go to
*/ !document || /**
* The document has been created but the current dimension has never been saved.
* For example, the user is creating a new locale in an existing document,
* so there's no history for the document in that locale
*/ !document.id || /**
* History is only available for content types created by the user.
* These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
* which start with `admin::` or `plugin::`
*/ !model.startsWith('api::'),
position: 'header'
};
};
HistoryAction.type = 'history';
HistoryAction.position = 'header';
exports.HistoryAction = HistoryAction;
//# sourceMappingURL=HistoryAction.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HistoryAction.js","sources":["../../../../admin/src/history/components/HistoryAction.tsx"],"sourcesContent":["import { useQueryParams, useTracking } from '@strapi/admin/strapi-admin';\nimport { ClockCounterClockwise } from '@strapi/icons';\nimport { stringify } from 'qs';\nimport { useIntl } from 'react-intl';\nimport { useNavigate, useLocation } from 'react-router-dom';\n\nimport type { DocumentActionComponent } from '../../content-manager';\n\nconst HistoryAction: DocumentActionComponent = ({ model, document }) => {\n const { formatMessage } = useIntl();\n const [{ query }] = useQueryParams<{ plugins?: Record<string, unknown> }>();\n const navigate = useNavigate();\n const { trackUsage } = useTracking();\n const { pathname } = useLocation();\n const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });\n\n if (!window.strapi.features.isEnabled('cms-content-history')) {\n return null;\n }\n\n const handleOnClick = () => {\n const destination = { pathname: 'history', search: pluginsQueryParams };\n trackUsage('willNavigate', {\n from: pathname,\n to: `${pathname}/${destination.pathname}`,\n });\n navigate(destination);\n };\n\n return {\n icon: <ClockCounterClockwise />,\n label: formatMessage({\n id: 'content-manager.history.document-action',\n defaultMessage: 'Content History',\n }),\n onClick: handleOnClick,\n disabled:\n /**\n * The user is creating a new document.\n * It hasn't been saved yet, so there's no history to go to\n */\n !document ||\n /**\n * The document has been created but the current dimension has never been saved.\n * For example, the user is creating a new locale in an existing document,\n * so there's no history for the document in that locale\n */\n !document.id ||\n /**\n * History is only available for content types created by the user.\n * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,\n * which start with `admin::` or `plugin::`\n */\n !model.startsWith('api::'),\n position: 'header',\n };\n};\n\nHistoryAction.type = 'history';\nHistoryAction.position = 'header';\n\nexport { HistoryAction };\n"],"names":["HistoryAction","model","document","formatMessage","useIntl","query","useQueryParams","navigate","useNavigate","trackUsage","useTracking","pathname","useLocation","pluginsQueryParams","stringify","plugins","encode","window","strapi","features","isEnabled","handleOnClick","destination","search","from","to","icon","_jsx","ClockCounterClockwise","label","id","defaultMessage","onClick","disabled","startsWith","position","type"],"mappings":";;;;;;;;;AAQA,MAAMA,gBAAyC,CAAC,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAA;IACjE,MAAM,EAAEC,aAAa,EAAE,GAAGC,iBAAAA,EAAAA;AAC1B,IAAA,MAAM,CAAC,EAAEC,KAAK,EAAE,CAAC,GAAGC,0BAAAA,EAAAA;AACpB,IAAA,MAAMC,QAAWC,GAAAA,0BAAAA,EAAAA;IACjB,MAAM,EAAEC,UAAU,EAAE,GAAGC,uBAAAA,EAAAA;IACvB,MAAM,EAAEC,QAAQ,EAAE,GAAGC,0BAAAA,EAAAA;AACrB,IAAA,MAAMC,qBAAqBC,YAAU,CAAA;AAAEC,QAAAA,OAAAA,EAASV,MAAMU;KAAW,EAAA;QAAEC,MAAQ,EAAA;AAAM,KAAA,CAAA;IAEjF,IAAI,CAACC,OAAOC,MAAM,CAACC,QAAQ,CAACC,SAAS,CAAC,qBAAwB,CAAA,EAAA;QAC5D,OAAO,IAAA;AACT;AAEA,IAAA,MAAMC,aAAgB,GAAA,IAAA;AACpB,QAAA,MAAMC,WAAc,GAAA;YAAEX,QAAU,EAAA,SAAA;YAAWY,MAAQV,EAAAA;AAAmB,SAAA;AACtEJ,QAAAA,UAAAA,CAAW,cAAgB,EAAA;YACzBe,IAAMb,EAAAA,QAAAA;YACNc,EAAI,EAAA,CAAC,EAAEd,QAAS,CAAA,CAAC,EAAEW,WAAYX,CAAAA,QAAQ,CAAC;AAC1C,SAAA,CAAA;QACAJ,QAASe,CAAAA,WAAAA,CAAAA;AACX,KAAA;IAEA,OAAO;AACLI,QAAAA,IAAAA,gBAAMC,cAACC,CAAAA,2BAAAA,EAAAA,EAAAA,CAAAA;AACPC,QAAAA,KAAAA,EAAO1B,aAAc,CAAA;YACnB2B,EAAI,EAAA,yCAAA;YACJC,cAAgB,EAAA;AAClB,SAAA,CAAA;QACAC,OAASX,EAAAA,aAAAA;QACTY,QACE;;;AAGC,UACD,CAAC/B,QACD;;;;UAKA,CAACA,QAAS4B,CAAAA,EAAE;;;;UAMZ,CAAC7B,KAAMiC,CAAAA,UAAU,CAAC,OAAA,CAAA;QACpBC,QAAU,EAAA;AACZ,KAAA;AACF;AAEAnC,aAAAA,CAAcoC,IAAI,GAAG,SAAA;AACrBpC,aAAAA,CAAcmC,QAAQ,GAAG,QAAA;;;;"}

View File

@@ -0,0 +1,59 @@
import { jsx } from 'react/jsx-runtime';
import { useQueryParams, useTracking } from '@strapi/admin/strapi-admin';
import { ClockCounterClockwise } from '@strapi/icons';
import { stringify } from 'qs';
import { useIntl } from 'react-intl';
import { useNavigate, useLocation } from 'react-router-dom';
const HistoryAction = ({ model, document })=>{
const { formatMessage } = useIntl();
const [{ query }] = useQueryParams();
const navigate = useNavigate();
const { trackUsage } = useTracking();
const { pathname } = useLocation();
const pluginsQueryParams = stringify({
plugins: query.plugins
}, {
encode: false
});
if (!window.strapi.features.isEnabled('cms-content-history')) {
return null;
}
const handleOnClick = ()=>{
const destination = {
pathname: 'history',
search: pluginsQueryParams
};
trackUsage('willNavigate', {
from: pathname,
to: `${pathname}/${destination.pathname}`
});
navigate(destination);
};
return {
icon: /*#__PURE__*/ jsx(ClockCounterClockwise, {}),
label: formatMessage({
id: 'content-manager.history.document-action',
defaultMessage: 'Content History'
}),
onClick: handleOnClick,
disabled: /**
* The user is creating a new document.
* It hasn't been saved yet, so there's no history to go to
*/ !document || /**
* The document has been created but the current dimension has never been saved.
* For example, the user is creating a new locale in an existing document,
* so there's no history for the document in that locale
*/ !document.id || /**
* History is only available for content types created by the user.
* These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,
* which start with `admin::` or `plugin::`
*/ !model.startsWith('api::'),
position: 'header'
};
};
HistoryAction.type = 'history';
HistoryAction.position = 'header';
export { HistoryAction };
//# sourceMappingURL=HistoryAction.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HistoryAction.mjs","sources":["../../../../admin/src/history/components/HistoryAction.tsx"],"sourcesContent":["import { useQueryParams, useTracking } from '@strapi/admin/strapi-admin';\nimport { ClockCounterClockwise } from '@strapi/icons';\nimport { stringify } from 'qs';\nimport { useIntl } from 'react-intl';\nimport { useNavigate, useLocation } from 'react-router-dom';\n\nimport type { DocumentActionComponent } from '../../content-manager';\n\nconst HistoryAction: DocumentActionComponent = ({ model, document }) => {\n const { formatMessage } = useIntl();\n const [{ query }] = useQueryParams<{ plugins?: Record<string, unknown> }>();\n const navigate = useNavigate();\n const { trackUsage } = useTracking();\n const { pathname } = useLocation();\n const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });\n\n if (!window.strapi.features.isEnabled('cms-content-history')) {\n return null;\n }\n\n const handleOnClick = () => {\n const destination = { pathname: 'history', search: pluginsQueryParams };\n trackUsage('willNavigate', {\n from: pathname,\n to: `${pathname}/${destination.pathname}`,\n });\n navigate(destination);\n };\n\n return {\n icon: <ClockCounterClockwise />,\n label: formatMessage({\n id: 'content-manager.history.document-action',\n defaultMessage: 'Content History',\n }),\n onClick: handleOnClick,\n disabled:\n /**\n * The user is creating a new document.\n * It hasn't been saved yet, so there's no history to go to\n */\n !document ||\n /**\n * The document has been created but the current dimension has never been saved.\n * For example, the user is creating a new locale in an existing document,\n * so there's no history for the document in that locale\n */\n !document.id ||\n /**\n * History is only available for content types created by the user.\n * These have the `api::` prefix, as opposed to the ones created by Strapi or plugins,\n * which start with `admin::` or `plugin::`\n */\n !model.startsWith('api::'),\n position: 'header',\n };\n};\n\nHistoryAction.type = 'history';\nHistoryAction.position = 'header';\n\nexport { HistoryAction };\n"],"names":["HistoryAction","model","document","formatMessage","useIntl","query","useQueryParams","navigate","useNavigate","trackUsage","useTracking","pathname","useLocation","pluginsQueryParams","stringify","plugins","encode","window","strapi","features","isEnabled","handleOnClick","destination","search","from","to","icon","_jsx","ClockCounterClockwise","label","id","defaultMessage","onClick","disabled","startsWith","position","type"],"mappings":";;;;;;;AAQA,MAAMA,gBAAyC,CAAC,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAA;IACjE,MAAM,EAAEC,aAAa,EAAE,GAAGC,OAAAA,EAAAA;AAC1B,IAAA,MAAM,CAAC,EAAEC,KAAK,EAAE,CAAC,GAAGC,cAAAA,EAAAA;AACpB,IAAA,MAAMC,QAAWC,GAAAA,WAAAA,EAAAA;IACjB,MAAM,EAAEC,UAAU,EAAE,GAAGC,WAAAA,EAAAA;IACvB,MAAM,EAAEC,QAAQ,EAAE,GAAGC,WAAAA,EAAAA;AACrB,IAAA,MAAMC,qBAAqBC,SAAU,CAAA;AAAEC,QAAAA,OAAAA,EAASV,MAAMU;KAAW,EAAA;QAAEC,MAAQ,EAAA;AAAM,KAAA,CAAA;IAEjF,IAAI,CAACC,OAAOC,MAAM,CAACC,QAAQ,CAACC,SAAS,CAAC,qBAAwB,CAAA,EAAA;QAC5D,OAAO,IAAA;AACT;AAEA,IAAA,MAAMC,aAAgB,GAAA,IAAA;AACpB,QAAA,MAAMC,WAAc,GAAA;YAAEX,QAAU,EAAA,SAAA;YAAWY,MAAQV,EAAAA;AAAmB,SAAA;AACtEJ,QAAAA,UAAAA,CAAW,cAAgB,EAAA;YACzBe,IAAMb,EAAAA,QAAAA;YACNc,EAAI,EAAA,CAAC,EAAEd,QAAS,CAAA,CAAC,EAAEW,WAAYX,CAAAA,QAAQ,CAAC;AAC1C,SAAA,CAAA;QACAJ,QAASe,CAAAA,WAAAA,CAAAA;AACX,KAAA;IAEA,OAAO;AACLI,QAAAA,IAAAA,gBAAMC,GAACC,CAAAA,qBAAAA,EAAAA,EAAAA,CAAAA;AACPC,QAAAA,KAAAA,EAAO1B,aAAc,CAAA;YACnB2B,EAAI,EAAA,yCAAA;YACJC,cAAgB,EAAA;AAClB,SAAA,CAAA;QACAC,OAASX,EAAAA,aAAAA;QACTY,QACE;;;AAGC,UACD,CAAC/B,QACD;;;;UAKA,CAACA,QAAS4B,CAAAA,EAAE;;;;UAMZ,CAAC7B,KAAMiC,CAAAA,UAAU,CAAC,OAAA,CAAA;QACpBC,QAAU,EAAA;AACZ,KAAA;AACF;AAEAnC,aAAAA,CAAcoC,IAAI,GAAG,SAAA;AACrBpC,aAAAA,CAAcmC,QAAQ,GAAG,QAAA;;;;"}

View File

@@ -0,0 +1,264 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var pipe = require('lodash/fp/pipe');
var reactIntl = require('react-intl');
var useDocument = require('../../hooks/useDocument.js');
var hooks = require('../../modules/hooks.js');
var data = require('../../pages/EditView/utils/data.js');
var History = require('../pages/History.js');
var VersionInputRenderer = require('./VersionInputRenderer.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const createLayoutFromFields = (fields)=>{
return fields.reduce((rows, field)=>{
if (field.type === 'dynamiczone') {
// Dynamic zones take up all the columns in a row
rows.push([
field
]);
return rows;
}
if (!rows[rows.length - 1]) {
// Create a new row if there isn't one available
rows.push([]);
}
// Push fields to the current row, they wrap and handle their own column size
rows[rows.length - 1].push(field);
return rows;
}, [])// Map the rows to panels
.map((row)=>[
row
]);
};
/**
* Build a layout for the fields that are were deleted from the edit view layout
* via the configure the view page. This layout will be merged with the main one.
* Those fields would be restored if the user restores the history version, which is why it's
* important to show them, even if they're not in the normal layout.
*/ function getRemaingFieldsLayout({ layout, metadatas, schemaAttributes, fieldSizes }) {
const fieldsInLayout = layout.flatMap((panel)=>panel.flatMap((row)=>row.flatMap((field)=>field.name)));
const remainingFields = Object.entries(metadatas).reduce((currentRemainingFields, [name, field])=>{
// Make sure we do not fields that are not visible, e.g. "id"
if (!fieldsInLayout.includes(name) && field.edit.visible === true) {
const attribute = schemaAttributes[name];
// @ts-expect-error not sure why attribute causes type error
currentRemainingFields.push({
attribute,
type: attribute.type,
visible: true,
disabled: true,
label: field.edit.label || name,
name: name,
size: fieldSizes[attribute.type].default ?? 12
});
}
return currentRemainingFields;
}, []);
return createLayoutFromFields(remainingFields);
}
/* -------------------------------------------------------------------------------------------------
* FormPanel
* -----------------------------------------------------------------------------------------------*/ const FormPanel = ({ panel })=>{
if (panel.some((row)=>row.some((field)=>field.type === 'dynamiczone'))) {
const [row] = panel;
const [field] = row;
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 4,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: 12,
s: 12,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(VersionInputRenderer.VersionInputRenderer, {
...field
})
})
}, field.name);
}
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
hasRadius: true,
background: "neutral0",
shadow: "tableShadow",
paddingLeft: 6,
paddingRight: 6,
paddingTop: 6,
paddingBottom: 6,
borderColor: "neutral150",
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
children: panel.map((row, gridRowIndex)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Root, {
gap: 4,
children: row.map(({ size, ...field })=>{
return /*#__PURE__*/ jsxRuntime.jsx(designSystem.Grid.Item, {
col: size,
s: 12,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsxRuntime.jsx(VersionInputRenderer.VersionInputRenderer, {
...field
})
}, field.name);
})
}, gridRowIndex))
})
});
};
const VersionContent = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { fieldSizes } = hooks.useTypedSelector((state)=>state['content-manager'].app);
const version = History.useHistoryContext('VersionContent', (state)=>state.selectedVersion);
const layout = History.useHistoryContext('VersionContent', (state)=>state.layout);
const configuration = History.useHistoryContext('VersionContent', (state)=>state.configuration);
const schema = History.useHistoryContext('VersionContent', (state)=>state.schema);
// Build a layout for the unknown fields section
const removedAttributes = version.meta.unknownAttributes.removed;
const removedAttributesAsFields = Object.entries(removedAttributes).map(([attributeName, attribute])=>{
const field = {
attribute,
shouldIgnoreRBAC: true,
type: attribute.type,
visible: true,
disabled: true,
label: attributeName,
name: attributeName,
size: fieldSizes[attribute.type].default ?? 12
};
return field;
});
const unknownFieldsLayout = createLayoutFromFields(removedAttributesAsFields);
// Build a layout for the fields that are were deleted from the layout
const remainingFieldsLayout = getRemaingFieldsLayout({
metadatas: configuration.contentType.metadatas,
layout,
schemaAttributes: schema.attributes,
fieldSizes
});
const { components } = useDocument.useDoc();
/**
* Transform the data before passing it to the form so that each field
* has a uniquely generated key
*/ const transformedData = React__namespace.useMemo(()=>{
const transform = (schemaAttributes, components = {})=>(document)=>{
const schema = {
attributes: schemaAttributes
};
const transformations = pipe(data.removeFieldsThatDontExistOnSchema(schema), data.prepareTempKeys(schema, components));
return transformations(document);
};
return transform(version.schema, components)(version.data);
}, [
components,
version.data,
version.schema
]);
return /*#__PURE__*/ jsxRuntime.jsxs(strapiAdmin.Layouts.Content, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingBottom: 8,
children: /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
disabled: true,
method: "PUT",
initialValues: transformedData,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
position: "relative",
children: [
...layout,
...remainingFieldsLayout
].map((panel, index)=>{
return /*#__PURE__*/ jsxRuntime.jsx(FormPanel, {
panel: panel
}, index);
})
})
})
}),
removedAttributesAsFields.length > 0 && /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Divider, {}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
paddingTop: 8,
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "flex-start",
paddingBottom: 6,
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "delta",
children: formatMessage({
id: 'content-manager.history.content.unknown-fields.title',
defaultMessage: 'Unknown fields'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
children: formatMessage({
id: 'content-manager.history.content.unknown-fields.message',
defaultMessage: 'These fields have been deleted or renamed in the Content-Type Builder. <b>These fields will not be restored.</b>'
}, {
b: (chunks)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "pi",
fontWeight: "bold",
children: chunks
})
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
disabled: true,
method: "PUT",
initialValues: version.data,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
position: "relative",
children: unknownFieldsLayout.map((panel, index)=>{
return /*#__PURE__*/ jsxRuntime.jsx(FormPanel, {
panel: panel
}, index);
})
})
})
]
})
]
})
]
});
};
exports.VersionContent = VersionContent;
exports.getRemaingFieldsLayout = getRemaingFieldsLayout;
//# sourceMappingURL=VersionContent.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,242 @@
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { Layouts, Form } from '@strapi/admin/strapi-admin';
import { Box, Flex, Divider, Typography, Grid } from '@strapi/design-system';
import pipe from 'lodash/fp/pipe';
import { useIntl } from 'react-intl';
import { useDoc } from '../../hooks/useDocument.mjs';
import { useTypedSelector } from '../../modules/hooks.mjs';
import { removeFieldsThatDontExistOnSchema, prepareTempKeys } from '../../pages/EditView/utils/data.mjs';
import { useHistoryContext } from '../pages/History.mjs';
import { VersionInputRenderer } from './VersionInputRenderer.mjs';
const createLayoutFromFields = (fields)=>{
return fields.reduce((rows, field)=>{
if (field.type === 'dynamiczone') {
// Dynamic zones take up all the columns in a row
rows.push([
field
]);
return rows;
}
if (!rows[rows.length - 1]) {
// Create a new row if there isn't one available
rows.push([]);
}
// Push fields to the current row, they wrap and handle their own column size
rows[rows.length - 1].push(field);
return rows;
}, [])// Map the rows to panels
.map((row)=>[
row
]);
};
/**
* Build a layout for the fields that are were deleted from the edit view layout
* via the configure the view page. This layout will be merged with the main one.
* Those fields would be restored if the user restores the history version, which is why it's
* important to show them, even if they're not in the normal layout.
*/ function getRemaingFieldsLayout({ layout, metadatas, schemaAttributes, fieldSizes }) {
const fieldsInLayout = layout.flatMap((panel)=>panel.flatMap((row)=>row.flatMap((field)=>field.name)));
const remainingFields = Object.entries(metadatas).reduce((currentRemainingFields, [name, field])=>{
// Make sure we do not fields that are not visible, e.g. "id"
if (!fieldsInLayout.includes(name) && field.edit.visible === true) {
const attribute = schemaAttributes[name];
// @ts-expect-error not sure why attribute causes type error
currentRemainingFields.push({
attribute,
type: attribute.type,
visible: true,
disabled: true,
label: field.edit.label || name,
name: name,
size: fieldSizes[attribute.type].default ?? 12
});
}
return currentRemainingFields;
}, []);
return createLayoutFromFields(remainingFields);
}
/* -------------------------------------------------------------------------------------------------
* FormPanel
* -----------------------------------------------------------------------------------------------*/ const FormPanel = ({ panel })=>{
if (panel.some((row)=>row.some((field)=>field.type === 'dynamiczone'))) {
const [row] = panel;
const [field] = row;
return /*#__PURE__*/ jsx(Grid.Root, {
gap: 4,
children: /*#__PURE__*/ jsx(Grid.Item, {
col: 12,
s: 12,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(VersionInputRenderer, {
...field
})
})
}, field.name);
}
return /*#__PURE__*/ jsx(Box, {
hasRadius: true,
background: "neutral0",
shadow: "tableShadow",
paddingLeft: 6,
paddingRight: 6,
paddingTop: 6,
paddingBottom: 6,
borderColor: "neutral150",
children: /*#__PURE__*/ jsx(Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
children: panel.map((row, gridRowIndex)=>/*#__PURE__*/ jsx(Grid.Root, {
gap: 4,
children: row.map(({ size, ...field })=>{
return /*#__PURE__*/ jsx(Grid.Item, {
col: size,
s: 12,
xs: 12,
direction: "column",
alignItems: "stretch",
children: /*#__PURE__*/ jsx(VersionInputRenderer, {
...field
})
}, field.name);
})
}, gridRowIndex))
})
});
};
const VersionContent = ()=>{
const { formatMessage } = useIntl();
const { fieldSizes } = useTypedSelector((state)=>state['content-manager'].app);
const version = useHistoryContext('VersionContent', (state)=>state.selectedVersion);
const layout = useHistoryContext('VersionContent', (state)=>state.layout);
const configuration = useHistoryContext('VersionContent', (state)=>state.configuration);
const schema = useHistoryContext('VersionContent', (state)=>state.schema);
// Build a layout for the unknown fields section
const removedAttributes = version.meta.unknownAttributes.removed;
const removedAttributesAsFields = Object.entries(removedAttributes).map(([attributeName, attribute])=>{
const field = {
attribute,
shouldIgnoreRBAC: true,
type: attribute.type,
visible: true,
disabled: true,
label: attributeName,
name: attributeName,
size: fieldSizes[attribute.type].default ?? 12
};
return field;
});
const unknownFieldsLayout = createLayoutFromFields(removedAttributesAsFields);
// Build a layout for the fields that are were deleted from the layout
const remainingFieldsLayout = getRemaingFieldsLayout({
metadatas: configuration.contentType.metadatas,
layout,
schemaAttributes: schema.attributes,
fieldSizes
});
const { components } = useDoc();
/**
* Transform the data before passing it to the form so that each field
* has a uniquely generated key
*/ const transformedData = React.useMemo(()=>{
const transform = (schemaAttributes, components = {})=>(document)=>{
const schema = {
attributes: schemaAttributes
};
const transformations = pipe(removeFieldsThatDontExistOnSchema(schema), prepareTempKeys(schema, components));
return transformations(document);
};
return transform(version.schema, components)(version.data);
}, [
components,
version.data,
version.schema
]);
return /*#__PURE__*/ jsxs(Layouts.Content, {
children: [
/*#__PURE__*/ jsx(Box, {
paddingBottom: 8,
children: /*#__PURE__*/ jsx(Form, {
disabled: true,
method: "PUT",
initialValues: transformedData,
children: /*#__PURE__*/ jsx(Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
position: "relative",
children: [
...layout,
...remainingFieldsLayout
].map((panel, index)=>{
return /*#__PURE__*/ jsx(FormPanel, {
panel: panel
}, index);
})
})
})
}),
removedAttributesAsFields.length > 0 && /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Divider, {}),
/*#__PURE__*/ jsxs(Box, {
paddingTop: 8,
children: [
/*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "flex-start",
paddingBottom: 6,
gap: 1,
children: [
/*#__PURE__*/ jsx(Typography, {
variant: "delta",
children: formatMessage({
id: 'content-manager.history.content.unknown-fields.title',
defaultMessage: 'Unknown fields'
})
}),
/*#__PURE__*/ jsx(Typography, {
variant: "pi",
children: formatMessage({
id: 'content-manager.history.content.unknown-fields.message',
defaultMessage: 'These fields have been deleted or renamed in the Content-Type Builder. <b>These fields will not be restored.</b>'
}, {
b: (chunks)=>/*#__PURE__*/ jsx(Typography, {
variant: "pi",
fontWeight: "bold",
children: chunks
})
})
})
]
}),
/*#__PURE__*/ jsx(Form, {
disabled: true,
method: "PUT",
initialValues: version.data,
children: /*#__PURE__*/ jsx(Flex, {
direction: "column",
alignItems: "stretch",
gap: 6,
position: "relative",
children: unknownFieldsLayout.map((panel, index)=>{
return /*#__PURE__*/ jsx(FormPanel, {
panel: panel
}, index);
})
})
})
]
})
]
})
]
});
};
export { VersionContent, getRemaingFieldsLayout };
//# sourceMappingURL=VersionContent.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,210 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var Icons = require('@strapi/icons');
var qs = require('qs');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var plugin = require('../../constants/plugin.js');
var History = require('../pages/History.js');
var historyVersion = require('../services/historyVersion.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const VersionHeader = ({ headerId })=>{
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = React__namespace.useState(false);
const navigate = reactRouterDom.useNavigate();
const { formatMessage, formatDate } = reactIntl.useIntl();
const { trackUsage } = strapiAdmin.useTracking();
const { toggleNotification } = strapiAdmin.useNotification();
const [{ query }] = strapiAdmin.useQueryParams();
const { collectionType, slug } = reactRouterDom.useParams();
const [restoreVersion, { isLoading }] = historyVersion.useRestoreVersionMutation();
const { allowedActions } = strapiAdmin.useRBAC(plugin.PERMISSIONS.map((action)=>({
action,
subject: slug
})));
const version = History.useHistoryContext('VersionHeader', (state)=>state.selectedVersion);
const mainField = History.useHistoryContext('VersionHeader', (state)=>state.mainField);
const schema = History.useHistoryContext('VersionHeader', (state)=>state.schema);
const isCurrentVersion = History.useHistoryContext('VersionHeader', (state)=>state.page === 1 && state.versions.data[0].id === state.selectedVersion.id);
const mainFieldValue = version.data[mainField];
const getNextNavigation = ()=>{
const pluginsQueryParams = qs.stringify({
plugins: query.plugins
}, {
encode: false
});
return {
pathname: '..',
search: pluginsQueryParams
};
};
const handleRestore = async ()=>{
try {
const response = await restoreVersion({
documentId: version.relatedDocumentId,
collectionType,
params: {
versionId: version.id,
contentType: version.contentType
},
body: {
contentType: version.contentType
}
});
if ('data' in response) {
navigate(getNextNavigation(), {
relative: 'path'
});
toggleNotification({
type: 'success',
title: formatMessage({
id: 'content-manager.restore.success.title',
defaultMessage: 'Version restored.'
}),
message: formatMessage({
id: 'content-manager.restore.success.message',
defaultMessage: 'A past version of the content was restored.'
})
});
trackUsage('didRestoreHistoryVersion');
}
if ('error' in response) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'content-manager.history.restore.error.message',
defaultMessage: 'Could not restore version.'
})
});
}
} catch (error) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Dialog.Root, {
open: isConfirmDialogOpen,
onOpenChange: setIsConfirmDialogOpen,
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Layouts.BaseHeader, {
id: headerId,
title: formatDate(new Date(version.createdAt), {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
}),
subtitle: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "epsilon",
textColor: "neutral600",
children: formatMessage({
id: 'content-manager.history.version.subtitle',
defaultMessage: '{hasLocale, select, true {{subtitle}, in {locale}} other {{subtitle}}}'
}, {
hasLocale: Boolean(version.locale),
subtitle: `${mainFieldValue || ''} (${schema.info.singularName})`.trim(),
locale: version.locale?.name
})
}),
navigationAction: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Link, {
startIcon: /*#__PURE__*/ jsxRuntime.jsx(Icons.ArrowLeft, {}),
tag: reactRouterDom.NavLink,
to: getNextNavigation(),
relative: "path",
isExternal: false,
children: formatMessage({
id: 'global.back',
defaultMessage: 'Back'
})
}),
sticky: false,
primaryAction: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Trigger, {
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
disabled: !allowedActions.canUpdate || isCurrentVersion,
onClick: ()=>{
setIsConfirmDialogOpen(true);
},
children: formatMessage({
id: 'content-manager.history.restore.confirm.button',
defaultMessage: 'Restore'
})
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.ConfirmDialog, {
onConfirm: handleRestore,
endAction: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Button, {
variant: "secondary",
onClick: handleRestore,
loading: isLoading,
children: formatMessage({
id: 'content-manager.history.restore.confirm.button',
defaultMessage: 'Restore'
})
}),
children: /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "center",
justifyContent: "center",
gap: 2,
textAlign: "center",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
justifyContent: "center",
children: /*#__PURE__*/ jsxRuntime.jsx(Icons.WarningCircle, {
width: "24px",
height: "24px",
fill: "danger600"
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: formatMessage({
id: 'content-manager.history.restore.confirm.title',
defaultMessage: 'Are you sure you want to restore this version?'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: formatMessage({
id: 'content-manager.history.restore.confirm.message',
defaultMessage: "{isDraft, select, true {The restored content will override your draft.} other {The restored content won't be published, it will override the draft and be saved as pending changes. You'll be able to publish the changes at anytime.}}"
}, {
isDraft: version.status === 'draft'
})
})
]
})
})
]
});
};
exports.VersionHeader = VersionHeader;
//# sourceMappingURL=VersionHeader.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,189 @@
import { jsxs, jsx } from 'react/jsx-runtime';
import * as React from 'react';
import { useTracking, useNotification, useQueryParams, useRBAC, Layouts, ConfirmDialog } from '@strapi/admin/strapi-admin';
import { Dialog, Typography, Link, Button, Flex } from '@strapi/design-system';
import { ArrowLeft, WarningCircle } from '@strapi/icons';
import { stringify } from 'qs';
import { useIntl } from 'react-intl';
import { useNavigate, useParams, NavLink } from 'react-router-dom';
import { PERMISSIONS } from '../../constants/plugin.mjs';
import { useHistoryContext } from '../pages/History.mjs';
import { useRestoreVersionMutation } from '../services/historyVersion.mjs';
const VersionHeader = ({ headerId })=>{
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = React.useState(false);
const navigate = useNavigate();
const { formatMessage, formatDate } = useIntl();
const { trackUsage } = useTracking();
const { toggleNotification } = useNotification();
const [{ query }] = useQueryParams();
const { collectionType, slug } = useParams();
const [restoreVersion, { isLoading }] = useRestoreVersionMutation();
const { allowedActions } = useRBAC(PERMISSIONS.map((action)=>({
action,
subject: slug
})));
const version = useHistoryContext('VersionHeader', (state)=>state.selectedVersion);
const mainField = useHistoryContext('VersionHeader', (state)=>state.mainField);
const schema = useHistoryContext('VersionHeader', (state)=>state.schema);
const isCurrentVersion = useHistoryContext('VersionHeader', (state)=>state.page === 1 && state.versions.data[0].id === state.selectedVersion.id);
const mainFieldValue = version.data[mainField];
const getNextNavigation = ()=>{
const pluginsQueryParams = stringify({
plugins: query.plugins
}, {
encode: false
});
return {
pathname: '..',
search: pluginsQueryParams
};
};
const handleRestore = async ()=>{
try {
const response = await restoreVersion({
documentId: version.relatedDocumentId,
collectionType,
params: {
versionId: version.id,
contentType: version.contentType
},
body: {
contentType: version.contentType
}
});
if ('data' in response) {
navigate(getNextNavigation(), {
relative: 'path'
});
toggleNotification({
type: 'success',
title: formatMessage({
id: 'content-manager.restore.success.title',
defaultMessage: 'Version restored.'
}),
message: formatMessage({
id: 'content-manager.restore.success.message',
defaultMessage: 'A past version of the content was restored.'
})
});
trackUsage('didRestoreHistoryVersion');
}
if ('error' in response) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'content-manager.history.restore.error.message',
defaultMessage: 'Could not restore version.'
})
});
}
} catch (error) {
toggleNotification({
type: 'danger',
message: formatMessage({
id: 'notification.error',
defaultMessage: 'An error occurred'
})
});
}
};
return /*#__PURE__*/ jsxs(Dialog.Root, {
open: isConfirmDialogOpen,
onOpenChange: setIsConfirmDialogOpen,
children: [
/*#__PURE__*/ jsx(Layouts.BaseHeader, {
id: headerId,
title: formatDate(new Date(version.createdAt), {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
}),
subtitle: /*#__PURE__*/ jsx(Typography, {
variant: "epsilon",
textColor: "neutral600",
children: formatMessage({
id: 'content-manager.history.version.subtitle',
defaultMessage: '{hasLocale, select, true {{subtitle}, in {locale}} other {{subtitle}}}'
}, {
hasLocale: Boolean(version.locale),
subtitle: `${mainFieldValue || ''} (${schema.info.singularName})`.trim(),
locale: version.locale?.name
})
}),
navigationAction: /*#__PURE__*/ jsx(Link, {
startIcon: /*#__PURE__*/ jsx(ArrowLeft, {}),
tag: NavLink,
to: getNextNavigation(),
relative: "path",
isExternal: false,
children: formatMessage({
id: 'global.back',
defaultMessage: 'Back'
})
}),
sticky: false,
primaryAction: /*#__PURE__*/ jsx(Dialog.Trigger, {
children: /*#__PURE__*/ jsx(Button, {
disabled: !allowedActions.canUpdate || isCurrentVersion,
onClick: ()=>{
setIsConfirmDialogOpen(true);
},
children: formatMessage({
id: 'content-manager.history.restore.confirm.button',
defaultMessage: 'Restore'
})
})
})
}),
/*#__PURE__*/ jsx(ConfirmDialog, {
onConfirm: handleRestore,
endAction: /*#__PURE__*/ jsx(Button, {
variant: "secondary",
onClick: handleRestore,
loading: isLoading,
children: formatMessage({
id: 'content-manager.history.restore.confirm.button',
defaultMessage: 'Restore'
})
}),
children: /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "center",
justifyContent: "center",
gap: 2,
textAlign: "center",
children: [
/*#__PURE__*/ jsx(Flex, {
justifyContent: "center",
children: /*#__PURE__*/ jsx(WarningCircle, {
width: "24px",
height: "24px",
fill: "danger600"
})
}),
/*#__PURE__*/ jsx(Typography, {
children: formatMessage({
id: 'content-manager.history.restore.confirm.title',
defaultMessage: 'Are you sure you want to restore this version?'
})
}),
/*#__PURE__*/ jsx(Typography, {
children: formatMessage({
id: 'content-manager.history.restore.confirm.message',
defaultMessage: "{isDraft, select, true {The restored content will override your draft.} other {The restored content won't be published, it will override the draft and be saved as pending changes. You'll be able to publish the changes at anytime.}}"
}, {
isDraft: version.status === 'draft'
})
})
]
})
})
]
});
};
export { VersionHeader };
//# sourceMappingURL=VersionHeader.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,490 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var React = require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var styledComponents = require('styled-components');
var collections = require('../../constants/collections.js');
var DocumentRBAC = require('../../features/DocumentRBAC.js');
var useDocument = require('../../hooks/useDocument.js');
var useDocumentLayout = require('../../hooks/useDocumentLayout.js');
var useLazyComponents = require('../../hooks/useLazyComponents.js');
var hooks = require('../../modules/hooks.js');
var DocumentStatus = require('../../pages/EditView/components/DocumentStatus.js');
var BlocksInput = require('../../pages/EditView/components/FormInputs/BlocksInput/BlocksInput.js');
var Input = require('../../pages/EditView/components/FormInputs/Component/Input.js');
var Field = require('../../pages/EditView/components/FormInputs/DynamicZone/Field.js');
var NotAllowed = require('../../pages/EditView/components/FormInputs/NotAllowed.js');
var UID = require('../../pages/EditView/components/FormInputs/UID.js');
var Field$1 = require('../../pages/EditView/components/FormInputs/Wysiwyg/Field.js');
var InputRenderer = require('../../pages/EditView/components/InputRenderer.js');
var relations = require('../../utils/relations.js');
var History = require('../pages/History.js');
var VersionContent = require('./VersionContent.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
const StyledAlert = styledComponents.styled(designSystem.Alert).attrs({
closeLabel: 'Close',
onClose: ()=>{},
shadow: 'none'
})`
button {
display: none;
}
`;
/* -------------------------------------------------------------------------------------------------
* CustomRelationInput
* -----------------------------------------------------------------------------------------------*/ const LinkEllipsis = styledComponents.styled(designSystem.Link)`
display: block;
& > span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
`;
const CustomRelationInput = (props)=>{
const { formatMessage } = reactIntl.useIntl();
const field = strapiAdmin.useField(props.name);
/**
* Ideally the server would return the correct shape, however, for admin user relations
* it sanitizes everything out when it finds an object for the relation value.
*/ let formattedFieldValue;
if (field) {
formattedFieldValue = Array.isArray(field.value) ? {
results: field.value,
meta: {
missingCount: 0
}
} : field.value;
}
if (!formattedFieldValue || formattedFieldValue.results.length === 0 && formattedFieldValue.meta.missingCount === 0) {
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
action: props.labelAction,
children: props.label
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
marginTop: 1,
children: /*#__PURE__*/ jsxRuntime.jsx(StyledAlert, {
variant: "default",
children: formatMessage({
id: 'content-manager.history.content.no-relations',
defaultMessage: 'No relations.'
})
})
})
]
});
}
const { results, meta } = formattedFieldValue;
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: props.label
}),
results.length > 0 && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
gap: 2,
marginTop: 1,
alignItems: "stretch",
children: results.map((relationData)=>{
// @ts-expect-error - targetModel does exist on the attribute. But it's not typed.
const { targetModel } = props.attribute;
const href = `../${collections.COLLECTION_TYPES}/${targetModel}/${relationData.documentId}`;
const label = relations.getRelationLabel(relationData, props.mainField);
const isAdminUserRelation = targetModel === 'admin::user';
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 4,
paddingRight: 4,
hasRadius: true,
borderColor: "neutral200",
background: "neutral150",
justifyContent: "space-between",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
minWidth: 0,
paddingTop: 1,
paddingBottom: 1,
paddingRight: 4,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Tooltip, {
label: label,
children: isAdminUserRelation ? /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
children: label
}) : /*#__PURE__*/ jsxRuntime.jsx(LinkEllipsis, {
tag: reactRouterDom.NavLink,
to: href,
children: label
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(DocumentStatus.DocumentStatus, {
status: relationData.status
})
]
}, relationData.documentId ?? relationData.id);
})
}),
meta.missingCount > 0 && /* @ts-expect-error we dont need closeLabel */ /*#__PURE__*/ jsxRuntime.jsx(StyledAlert, {
marginTop: 1,
variant: "warning",
title: formatMessage({
id: 'content-manager.history.content.missing-relations.title',
defaultMessage: '{number, plural, =1 {Missing relation} other {{number} missing relations}}'
}, {
number: meta.missingCount
}),
children: formatMessage({
id: 'content-manager.history.content.missing-relations.message',
defaultMessage: "{number, plural, =1 {It has} other {They have}} been deleted and can't be restored."
}, {
number: meta.missingCount
})
})
]
});
};
/* -------------------------------------------------------------------------------------------------
* CustomMediaInput
* -----------------------------------------------------------------------------------------------*/ // Create an object with value at key path (i.e. 'a.b.c')
const createInitialValuesForPath = (keyPath, value)=>{
const keys = keyPath.split('.');
// The root level object
const root = {};
// Make the first node the root
let node = root;
keys.forEach((key, index)=>{
// Skip prototype pollution keys
if (key === '__proto__' || key === 'constructor') return;
// If it's the last key, set the node value
if (index === keys.length - 1) {
node[key] = value;
} else {
// Ensure the key exists and is an object
node[key] = node[key] || {};
}
// Traverse down the tree
node = node[key];
});
return root;
};
const CustomMediaInput = (props)=>{
const { value } = strapiAdmin.useField(props.name);
const results = value?.results ?? [];
const meta = value?.meta ?? {
missingCount: 0
};
const { formatMessage } = reactIntl.useIntl();
const fields = strapiAdmin.useStrapiApp('CustomMediaInput', (state)=>state.fields);
const MediaLibrary = fields.media;
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 2,
alignItems: "stretch",
children: [
/*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.Form, {
method: "PUT",
disabled: true,
initialValues: createInitialValuesForPath(props.name, results),
children: /*#__PURE__*/ jsxRuntime.jsx(MediaLibrary, {
...props,
disabled: true,
multiple: results.length > 1
})
}),
meta.missingCount > 0 && /*#__PURE__*/ jsxRuntime.jsx(StyledAlert, {
variant: "warning",
closeLabel: "Close",
onClose: ()=>{},
title: formatMessage({
id: 'content-manager.history.content.missing-assets.title',
defaultMessage: '{number, plural, =1 {Missing asset} other {{number} missing assets}}'
}, {
number: meta.missingCount
}),
children: formatMessage({
id: 'content-manager.history.content.missing-assets.message',
defaultMessage: "{number, plural, =1 {It has} other {They have}} been deleted in the Media Library and can't be restored."
}, {
number: meta.missingCount
})
})
]
});
};
/**
* Checks if the i18n plugin added a label action to the field and modifies it
* to adapt the wording for the history page.
*/ const getLabelAction = (labelAction)=>{
if (!/*#__PURE__*/ React__namespace.isValidElement(labelAction)) {
return labelAction;
}
// TODO: find a better way to do this rather than access internals
const labelActionTitleId = labelAction.props.title.id;
if (labelActionTitleId === 'i18n.Field.localized') {
return /*#__PURE__*/ React__namespace.cloneElement(labelAction, {
...labelAction.props,
title: {
id: 'history.content.localized',
defaultMessage: 'This value is specific to this locale. If you restore this version, the content will not be replaced for other locales.'
}
});
}
if (labelActionTitleId === 'i18n.Field.not-localized') {
return /*#__PURE__*/ React__namespace.cloneElement(labelAction, {
...labelAction.props,
title: {
id: 'history.content.not-localized',
defaultMessage: 'This value is common to all locales. If you restore this version and save the changes, the content will be replaced for all locales.'
}
});
}
// Label action is unrelated to i18n, don't touch it.
return labelAction;
};
/**
* @internal
*
* @description An abstraction around the regular form input renderer designed specifically
* to be used on the History page in the content-manager. It understands how to render specific
* inputs within the context of a history version (i.e. relations, media, ignored RBAC, etc...)
*/ const VersionInputRenderer = ({ visible, hint: providedHint, shouldIgnoreRBAC = false, labelAction, ...props })=>{
const customLabelAction = getLabelAction(labelAction);
const { formatMessage } = reactIntl.useIntl();
const version = History.useHistoryContext('VersionContent', (state)=>state.selectedVersion);
const configuration = History.useHistoryContext('VersionContent', (state)=>state.configuration);
const fieldSizes = hooks.useTypedSelector((state)=>state['content-manager'].app.fieldSizes);
const { id, components } = useDocument.useDoc();
const isFormDisabled = strapiAdmin.useForm('InputRenderer', (state)=>state.disabled);
const isInDynamicZone = Field.useDynamicZone('isInDynamicZone', (state)=>state.isInDynamicZone);
const canCreateFields = DocumentRBAC.useDocumentRBAC('InputRenderer', (rbac)=>rbac.canCreateFields);
const canReadFields = DocumentRBAC.useDocumentRBAC('InputRenderer', (rbac)=>rbac.canReadFields);
const canUpdateFields = DocumentRBAC.useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUpdateFields);
const canUserAction = DocumentRBAC.useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUserAction);
const editableFields = id ? canUpdateFields : canCreateFields;
const readableFields = id ? canReadFields : canCreateFields;
/**
* Component fields are always readable and editable,
* however the fields within them may not be.
*/ const canUserReadField = canUserAction(props.name, readableFields, props.type);
const canUserEditField = canUserAction(props.name, editableFields, props.type);
const fields = strapiAdmin.useStrapiApp('InputRenderer', (app)=>app.fields);
const { lazyComponentStore } = useLazyComponents.useLazyComponents(attributeHasCustomFieldProperty(props.attribute) ? [
props.attribute.customField
] : undefined);
const hint = InputRenderer.useFieldHint(providedHint, props.attribute);
const { edit: { components: componentsLayout } } = useDocumentLayout.useDocLayout();
if (!visible) {
return null;
}
/**
* Don't render the field if the user can't read it.
*/ if (!shouldIgnoreRBAC && !canUserReadField && !isInDynamicZone) {
return /*#__PURE__*/ jsxRuntime.jsx(NotAllowed.NotAllowedInput, {
hint: hint,
...props
});
}
const fieldIsDisabled = !canUserEditField && !isInDynamicZone || props.disabled || isFormDisabled;
/**
* Attributes found on the current content-type schema cannot be restored. We handle
* this by displaying a warning alert to the user instead of the input for that field type.
*/ const addedAttributes = version.meta.unknownAttributes.added;
if (Object.keys(addedAttributes).includes(props.name)) {
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "flex-start",
gap: 1,
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Field.Label, {
children: props.label
}),
/*#__PURE__*/ jsxRuntime.jsx(StyledAlert, {
width: "100%",
closeLabel: "Close",
onClose: ()=>{},
variant: "warning",
title: formatMessage({
id: 'content-manager.history.content.new-field.title',
defaultMessage: 'New field'
}),
children: formatMessage({
id: 'content-manager.history.content.new-field.message',
defaultMessage: "This field didn't exist when this version was saved. If you restore this version, it will be empty."
})
})
]
});
}
/**
* Because a custom field has a unique prop but the type could be confused with either
* the useField hook or the type of the field we need to handle it separately and first.
*/ if (attributeHasCustomFieldProperty(props.attribute)) {
const CustomInput = lazyComponentStore[props.attribute.customField];
if (CustomInput) {
return /*#__PURE__*/ jsxRuntime.jsx(CustomInput, {
...props,
// @ts-expect-error TODO: fix this type error in the useLazyComponents hook.
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
}
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.InputRenderer, {
...props,
hint: hint,
labelAction: customLabelAction,
// @ts-expect-error this workaround lets us display that the custom field is missing.
type: props.attribute.customField,
disabled: fieldIsDisabled
});
}
/**
* Since media fields use a custom input via the upload plugin provided by the useLibrary hook,
* we need to handle the them before other custom inputs coming from the useLibrary hook.
*/ if (props.type === 'media') {
return /*#__PURE__*/ jsxRuntime.jsx(CustomMediaInput, {
...props,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
}
/**
* This is where we handle ONLY the fields from the `useLibrary` hook.
*/ const addedInputTypes = Object.keys(fields);
if (!attributeHasCustomFieldProperty(props.attribute) && addedInputTypes.includes(props.type)) {
const CustomInput = fields[props.type];
return /*#__PURE__*/ jsxRuntime.jsx(CustomInput, {
...props,
// @ts-expect-error TODO: fix this type error in the useLibrary hook.
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
}
/**
* These include the content-manager specific fields, failing that we fall back
* to the more generic form input renderer.
*/ switch(props.type){
case 'blocks':
return /*#__PURE__*/ jsxRuntime.jsx(BlocksInput.BlocksInput, {
...props,
hint: hint,
type: props.type,
disabled: fieldIsDisabled
});
case 'component':
const { layout } = componentsLayout[props.attribute.component];
// Components can only have one panel, so only save the first layout item
const [remainingFieldsLayout] = VersionContent.getRemaingFieldsLayout({
layout: [
layout
],
metadatas: configuration.components[props.attribute.component].metadatas,
fieldSizes,
schemaAttributes: components[props.attribute.component].attributes
});
return /*#__PURE__*/ jsxRuntime.jsx(Input.ComponentInput, {
...props,
layout: [
...layout,
...remainingFieldsLayout || []
],
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled,
children: (inputProps)=>/*#__PURE__*/ jsxRuntime.jsx(VersionInputRenderer, {
...inputProps,
shouldIgnoreRBAC: true
})
});
case 'dynamiczone':
return /*#__PURE__*/ jsxRuntime.jsx(Field.DynamicZone, {
...props,
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled,
children: (inputProps)=>/*#__PURE__*/ jsxRuntime.jsx(VersionInputRenderer, {
...inputProps,
shouldIgnoreRBAC: true
})
});
case 'relation':
return /*#__PURE__*/ jsxRuntime.jsx(CustomRelationInput, {
...props,
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
case 'richtext':
return /*#__PURE__*/ jsxRuntime.jsx(Field$1.Wysiwyg, {
...props,
hint: hint,
type: props.type,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
case 'uid':
return /*#__PURE__*/ jsxRuntime.jsx(UID.UIDInput, {
...props,
hint: hint,
type: props.type,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
/**
* Enumerations are a special case because they require options.
*/ case 'enumeration':
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.InputRenderer, {
...props,
hint: hint,
labelAction: customLabelAction,
options: props.attribute.enum.map((value)=>({
value
})),
// @ts-expect-error Temp workaround so we don't forget custom-fields don't work!
type: props.customField ? 'custom-field' : props.type,
disabled: fieldIsDisabled
});
default:
// These props are not needed for the generic form input renderer.
const { unique: _unique, mainField: _mainField, ...restProps } = props;
return /*#__PURE__*/ jsxRuntime.jsx(strapiAdmin.InputRenderer, {
...restProps,
hint: hint,
labelAction: customLabelAction,
// @ts-expect-error Temp workaround so we don't forget custom-fields don't work!
type: props.customField ? 'custom-field' : props.type,
disabled: fieldIsDisabled
});
}
};
const attributeHasCustomFieldProperty = (attribute)=>'customField' in attribute && typeof attribute.customField === 'string';
exports.VersionInputRenderer = VersionInputRenderer;
//# sourceMappingURL=VersionInputRenderer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,469 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import { useForm, useStrapiApp, InputRenderer, useField, Form } from '@strapi/admin/strapi-admin';
import { Alert, Link, Flex, Field, Box, Tooltip, Typography } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import { styled } from 'styled-components';
import { COLLECTION_TYPES } from '../../constants/collections.mjs';
import { useDocumentRBAC } from '../../features/DocumentRBAC.mjs';
import { useDoc } from '../../hooks/useDocument.mjs';
import { useDocLayout } from '../../hooks/useDocumentLayout.mjs';
import { useLazyComponents } from '../../hooks/useLazyComponents.mjs';
import { useTypedSelector } from '../../modules/hooks.mjs';
import { DocumentStatus } from '../../pages/EditView/components/DocumentStatus.mjs';
import { BlocksInput as MemoizedBlocksInput } from '../../pages/EditView/components/FormInputs/BlocksInput/BlocksInput.mjs';
import { ComponentInput as MemoizedComponentInput } from '../../pages/EditView/components/FormInputs/Component/Input.mjs';
import { useDynamicZone, DynamicZone } from '../../pages/EditView/components/FormInputs/DynamicZone/Field.mjs';
import { NotAllowedInput } from '../../pages/EditView/components/FormInputs/NotAllowed.mjs';
import { UIDInput as MemoizedUIDInput } from '../../pages/EditView/components/FormInputs/UID.mjs';
import { Wysiwyg as MemoizedWysiwyg } from '../../pages/EditView/components/FormInputs/Wysiwyg/Field.mjs';
import { useFieldHint } from '../../pages/EditView/components/InputRenderer.mjs';
import { getRelationLabel } from '../../utils/relations.mjs';
import { useHistoryContext } from '../pages/History.mjs';
import { getRemaingFieldsLayout } from './VersionContent.mjs';
const StyledAlert = styled(Alert).attrs({
closeLabel: 'Close',
onClose: ()=>{},
shadow: 'none'
})`
button {
display: none;
}
`;
/* -------------------------------------------------------------------------------------------------
* CustomRelationInput
* -----------------------------------------------------------------------------------------------*/ const LinkEllipsis = styled(Link)`
display: block;
& > span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
`;
const CustomRelationInput = (props)=>{
const { formatMessage } = useIntl();
const field = useField(props.name);
/**
* Ideally the server would return the correct shape, however, for admin user relations
* it sanitizes everything out when it finds an object for the relation value.
*/ let formattedFieldValue;
if (field) {
formattedFieldValue = Array.isArray(field.value) ? {
results: field.value,
meta: {
missingCount: 0
}
} : field.value;
}
if (!formattedFieldValue || formattedFieldValue.results.length === 0 && formattedFieldValue.meta.missingCount === 0) {
return /*#__PURE__*/ jsxs(Fragment, {
children: [
/*#__PURE__*/ jsx(Field.Label, {
action: props.labelAction,
children: props.label
}),
/*#__PURE__*/ jsx(Box, {
marginTop: 1,
children: /*#__PURE__*/ jsx(StyledAlert, {
variant: "default",
children: formatMessage({
id: 'content-manager.history.content.no-relations',
defaultMessage: 'No relations.'
})
})
})
]
});
}
const { results, meta } = formattedFieldValue;
return /*#__PURE__*/ jsxs(Box, {
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: props.label
}),
results.length > 0 && /*#__PURE__*/ jsx(Flex, {
direction: "column",
gap: 2,
marginTop: 1,
alignItems: "stretch",
children: results.map((relationData)=>{
// @ts-expect-error - targetModel does exist on the attribute. But it's not typed.
const { targetModel } = props.attribute;
const href = `../${COLLECTION_TYPES}/${targetModel}/${relationData.documentId}`;
const label = getRelationLabel(relationData, props.mainField);
const isAdminUserRelation = targetModel === 'admin::user';
return /*#__PURE__*/ jsxs(Flex, {
paddingTop: 2,
paddingBottom: 2,
paddingLeft: 4,
paddingRight: 4,
hasRadius: true,
borderColor: "neutral200",
background: "neutral150",
justifyContent: "space-between",
children: [
/*#__PURE__*/ jsx(Box, {
minWidth: 0,
paddingTop: 1,
paddingBottom: 1,
paddingRight: 4,
children: /*#__PURE__*/ jsx(Tooltip, {
label: label,
children: isAdminUserRelation ? /*#__PURE__*/ jsx(Typography, {
children: label
}) : /*#__PURE__*/ jsx(LinkEllipsis, {
tag: NavLink,
to: href,
children: label
})
})
}),
/*#__PURE__*/ jsx(DocumentStatus, {
status: relationData.status
})
]
}, relationData.documentId ?? relationData.id);
})
}),
meta.missingCount > 0 && /* @ts-expect-error we dont need closeLabel */ /*#__PURE__*/ jsx(StyledAlert, {
marginTop: 1,
variant: "warning",
title: formatMessage({
id: 'content-manager.history.content.missing-relations.title',
defaultMessage: '{number, plural, =1 {Missing relation} other {{number} missing relations}}'
}, {
number: meta.missingCount
}),
children: formatMessage({
id: 'content-manager.history.content.missing-relations.message',
defaultMessage: "{number, plural, =1 {It has} other {They have}} been deleted and can't be restored."
}, {
number: meta.missingCount
})
})
]
});
};
/* -------------------------------------------------------------------------------------------------
* CustomMediaInput
* -----------------------------------------------------------------------------------------------*/ // Create an object with value at key path (i.e. 'a.b.c')
const createInitialValuesForPath = (keyPath, value)=>{
const keys = keyPath.split('.');
// The root level object
const root = {};
// Make the first node the root
let node = root;
keys.forEach((key, index)=>{
// Skip prototype pollution keys
if (key === '__proto__' || key === 'constructor') return;
// If it's the last key, set the node value
if (index === keys.length - 1) {
node[key] = value;
} else {
// Ensure the key exists and is an object
node[key] = node[key] || {};
}
// Traverse down the tree
node = node[key];
});
return root;
};
const CustomMediaInput = (props)=>{
const { value } = useField(props.name);
const results = value?.results ?? [];
const meta = value?.meta ?? {
missingCount: 0
};
const { formatMessage } = useIntl();
const fields = useStrapiApp('CustomMediaInput', (state)=>state.fields);
const MediaLibrary = fields.media;
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
gap: 2,
alignItems: "stretch",
children: [
/*#__PURE__*/ jsx(Form, {
method: "PUT",
disabled: true,
initialValues: createInitialValuesForPath(props.name, results),
children: /*#__PURE__*/ jsx(MediaLibrary, {
...props,
disabled: true,
multiple: results.length > 1
})
}),
meta.missingCount > 0 && /*#__PURE__*/ jsx(StyledAlert, {
variant: "warning",
closeLabel: "Close",
onClose: ()=>{},
title: formatMessage({
id: 'content-manager.history.content.missing-assets.title',
defaultMessage: '{number, plural, =1 {Missing asset} other {{number} missing assets}}'
}, {
number: meta.missingCount
}),
children: formatMessage({
id: 'content-manager.history.content.missing-assets.message',
defaultMessage: "{number, plural, =1 {It has} other {They have}} been deleted in the Media Library and can't be restored."
}, {
number: meta.missingCount
})
})
]
});
};
/**
* Checks if the i18n plugin added a label action to the field and modifies it
* to adapt the wording for the history page.
*/ const getLabelAction = (labelAction)=>{
if (!/*#__PURE__*/ React.isValidElement(labelAction)) {
return labelAction;
}
// TODO: find a better way to do this rather than access internals
const labelActionTitleId = labelAction.props.title.id;
if (labelActionTitleId === 'i18n.Field.localized') {
return /*#__PURE__*/ React.cloneElement(labelAction, {
...labelAction.props,
title: {
id: 'history.content.localized',
defaultMessage: 'This value is specific to this locale. If you restore this version, the content will not be replaced for other locales.'
}
});
}
if (labelActionTitleId === 'i18n.Field.not-localized') {
return /*#__PURE__*/ React.cloneElement(labelAction, {
...labelAction.props,
title: {
id: 'history.content.not-localized',
defaultMessage: 'This value is common to all locales. If you restore this version and save the changes, the content will be replaced for all locales.'
}
});
}
// Label action is unrelated to i18n, don't touch it.
return labelAction;
};
/**
* @internal
*
* @description An abstraction around the regular form input renderer designed specifically
* to be used on the History page in the content-manager. It understands how to render specific
* inputs within the context of a history version (i.e. relations, media, ignored RBAC, etc...)
*/ const VersionInputRenderer = ({ visible, hint: providedHint, shouldIgnoreRBAC = false, labelAction, ...props })=>{
const customLabelAction = getLabelAction(labelAction);
const { formatMessage } = useIntl();
const version = useHistoryContext('VersionContent', (state)=>state.selectedVersion);
const configuration = useHistoryContext('VersionContent', (state)=>state.configuration);
const fieldSizes = useTypedSelector((state)=>state['content-manager'].app.fieldSizes);
const { id, components } = useDoc();
const isFormDisabled = useForm('InputRenderer', (state)=>state.disabled);
const isInDynamicZone = useDynamicZone('isInDynamicZone', (state)=>state.isInDynamicZone);
const canCreateFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canCreateFields);
const canReadFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canReadFields);
const canUpdateFields = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUpdateFields);
const canUserAction = useDocumentRBAC('InputRenderer', (rbac)=>rbac.canUserAction);
const editableFields = id ? canUpdateFields : canCreateFields;
const readableFields = id ? canReadFields : canCreateFields;
/**
* Component fields are always readable and editable,
* however the fields within them may not be.
*/ const canUserReadField = canUserAction(props.name, readableFields, props.type);
const canUserEditField = canUserAction(props.name, editableFields, props.type);
const fields = useStrapiApp('InputRenderer', (app)=>app.fields);
const { lazyComponentStore } = useLazyComponents(attributeHasCustomFieldProperty(props.attribute) ? [
props.attribute.customField
] : undefined);
const hint = useFieldHint(providedHint, props.attribute);
const { edit: { components: componentsLayout } } = useDocLayout();
if (!visible) {
return null;
}
/**
* Don't render the field if the user can't read it.
*/ if (!shouldIgnoreRBAC && !canUserReadField && !isInDynamicZone) {
return /*#__PURE__*/ jsx(NotAllowedInput, {
hint: hint,
...props
});
}
const fieldIsDisabled = !canUserEditField && !isInDynamicZone || props.disabled || isFormDisabled;
/**
* Attributes found on the current content-type schema cannot be restored. We handle
* this by displaying a warning alert to the user instead of the input for that field type.
*/ const addedAttributes = version.meta.unknownAttributes.added;
if (Object.keys(addedAttributes).includes(props.name)) {
return /*#__PURE__*/ jsxs(Flex, {
direction: "column",
alignItems: "flex-start",
gap: 1,
children: [
/*#__PURE__*/ jsx(Field.Label, {
children: props.label
}),
/*#__PURE__*/ jsx(StyledAlert, {
width: "100%",
closeLabel: "Close",
onClose: ()=>{},
variant: "warning",
title: formatMessage({
id: 'content-manager.history.content.new-field.title',
defaultMessage: 'New field'
}),
children: formatMessage({
id: 'content-manager.history.content.new-field.message',
defaultMessage: "This field didn't exist when this version was saved. If you restore this version, it will be empty."
})
})
]
});
}
/**
* Because a custom field has a unique prop but the type could be confused with either
* the useField hook or the type of the field we need to handle it separately and first.
*/ if (attributeHasCustomFieldProperty(props.attribute)) {
const CustomInput = lazyComponentStore[props.attribute.customField];
if (CustomInput) {
return /*#__PURE__*/ jsx(CustomInput, {
...props,
// @ts-expect-error TODO: fix this type error in the useLazyComponents hook.
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
}
return /*#__PURE__*/ jsx(InputRenderer, {
...props,
hint: hint,
labelAction: customLabelAction,
// @ts-expect-error this workaround lets us display that the custom field is missing.
type: props.attribute.customField,
disabled: fieldIsDisabled
});
}
/**
* Since media fields use a custom input via the upload plugin provided by the useLibrary hook,
* we need to handle the them before other custom inputs coming from the useLibrary hook.
*/ if (props.type === 'media') {
return /*#__PURE__*/ jsx(CustomMediaInput, {
...props,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
}
/**
* This is where we handle ONLY the fields from the `useLibrary` hook.
*/ const addedInputTypes = Object.keys(fields);
if (!attributeHasCustomFieldProperty(props.attribute) && addedInputTypes.includes(props.type)) {
const CustomInput = fields[props.type];
return /*#__PURE__*/ jsx(CustomInput, {
...props,
// @ts-expect-error TODO: fix this type error in the useLibrary hook.
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
}
/**
* These include the content-manager specific fields, failing that we fall back
* to the more generic form input renderer.
*/ switch(props.type){
case 'blocks':
return /*#__PURE__*/ jsx(MemoizedBlocksInput, {
...props,
hint: hint,
type: props.type,
disabled: fieldIsDisabled
});
case 'component':
const { layout } = componentsLayout[props.attribute.component];
// Components can only have one panel, so only save the first layout item
const [remainingFieldsLayout] = getRemaingFieldsLayout({
layout: [
layout
],
metadatas: configuration.components[props.attribute.component].metadatas,
fieldSizes,
schemaAttributes: components[props.attribute.component].attributes
});
return /*#__PURE__*/ jsx(MemoizedComponentInput, {
...props,
layout: [
...layout,
...remainingFieldsLayout || []
],
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled,
children: (inputProps)=>/*#__PURE__*/ jsx(VersionInputRenderer, {
...inputProps,
shouldIgnoreRBAC: true
})
});
case 'dynamiczone':
return /*#__PURE__*/ jsx(DynamicZone, {
...props,
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled,
children: (inputProps)=>/*#__PURE__*/ jsx(VersionInputRenderer, {
...inputProps,
shouldIgnoreRBAC: true
})
});
case 'relation':
return /*#__PURE__*/ jsx(CustomRelationInput, {
...props,
hint: hint,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
case 'richtext':
return /*#__PURE__*/ jsx(MemoizedWysiwyg, {
...props,
hint: hint,
type: props.type,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
case 'uid':
return /*#__PURE__*/ jsx(MemoizedUIDInput, {
...props,
hint: hint,
type: props.type,
labelAction: customLabelAction,
disabled: fieldIsDisabled
});
/**
* Enumerations are a special case because they require options.
*/ case 'enumeration':
return /*#__PURE__*/ jsx(InputRenderer, {
...props,
hint: hint,
labelAction: customLabelAction,
options: props.attribute.enum.map((value)=>({
value
})),
// @ts-expect-error Temp workaround so we don't forget custom-fields don't work!
type: props.customField ? 'custom-field' : props.type,
disabled: fieldIsDisabled
});
default:
// These props are not needed for the generic form input renderer.
const { unique: _unique, mainField: _mainField, ...restProps } = props;
return /*#__PURE__*/ jsx(InputRenderer, {
...restProps,
hint: hint,
labelAction: customLabelAction,
// @ts-expect-error Temp workaround so we don't forget custom-fields don't work!
type: props.customField ? 'custom-field' : props.type,
disabled: fieldIsDisabled
});
}
};
const attributeHasCustomFieldProperty = (attribute)=>'customField' in attribute && typeof attribute.customField === 'string';
export { VersionInputRenderer };
//# sourceMappingURL=VersionInputRenderer.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,210 @@
'use strict';
var jsxRuntime = require('react/jsx-runtime');
require('react');
var strapiAdmin = require('@strapi/admin/strapi-admin');
var designSystem = require('@strapi/design-system');
var qs = require('qs');
var reactIntl = require('react-intl');
var reactRouterDom = require('react-router-dom');
var RelativeTime = require('../../components/RelativeTime.js');
var DocumentStatus = require('../../pages/EditView/components/DocumentStatus.js');
var users = require('../../utils/users.js');
var History = require('../pages/History.js');
/* -------------------------------------------------------------------------------------------------
* BlueText
* -----------------------------------------------------------------------------------------------*/ const BlueText = (children)=>/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
textColor: "primary600",
variant: "pi",
children: children
});
const VersionCard = ({ version, isCurrent })=>{
const { formatDate, formatMessage } = reactIntl.useIntl();
const [{ query }] = strapiAdmin.useQueryParams();
const isActive = query.id === version.id.toString();
const author = version.createdBy && users.getDisplayName(version.createdBy);
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
alignItems: "flex-start",
gap: 3,
hasRadius: true,
borderWidth: "1px",
borderStyle: "solid",
borderColor: isActive ? 'primary600' : 'neutral200',
color: "neutral800",
padding: 5,
tag: reactRouterDom.Link,
to: `?${qs.stringify({
...query,
id: version.id
})}`,
style: {
textDecoration: 'none'
},
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "column",
gap: 1,
alignItems: "flex-start",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
tag: "h3",
fontWeight: "semiBold",
children: formatDate(version.createdAt, {
day: 'numeric',
month: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
tag: "p",
variant: "pi",
textColor: "neutral600",
children: formatMessage({
id: 'content-manager.history.sidebar.versionDescription',
defaultMessage: '{distanceToNow}{isAnonymous, select, true {} other { by {author}}}{isCurrent, select, true { <b>(current)</b>} other {}}'
}, {
distanceToNow: /*#__PURE__*/ jsxRuntime.jsx(RelativeTime.RelativeTime, {
timestamp: new Date(version.createdAt)
}),
author,
isAnonymous: !Boolean(version.createdBy),
isCurrent,
b: BlueText
})
})
]
}),
version.status && /*#__PURE__*/ jsxRuntime.jsx(DocumentStatus.DocumentStatus, {
status: version.status,
size: "XS"
})
]
});
};
const PaginationButton = ({ page, children })=>{
const [{ query }] = strapiAdmin.useQueryParams();
// Remove the id from the pagination link, so that the history page can redirect
// to the id of the first history version in the new page once it's loaded
const { id: _id, ...queryRest } = query;
return /*#__PURE__*/ jsxRuntime.jsx(reactRouterDom.Link, {
to: {
search: qs.stringify({
...queryRest,
page
})
},
style: {
textDecoration: 'none'
},
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "omega",
textColor: "primary600",
children: children
})
});
};
/* -------------------------------------------------------------------------------------------------
* VersionsList
* -----------------------------------------------------------------------------------------------*/ const VersionsList = ()=>{
const { formatMessage } = reactIntl.useIntl();
const { versions, page } = History.useHistoryContext('VersionsList', (state)=>({
versions: state.versions,
page: state.page
}));
return /*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
shrink: 0,
direction: "column",
alignItems: "stretch",
width: "320px",
height: "100vh",
background: "neutral0",
borderColor: "neutral200",
borderWidth: "0 0 0 1px",
borderStyle: "solid",
tag: "aside",
children: [
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Flex, {
direction: "row",
justifyContent: "space-between",
padding: 4,
borderColor: "neutral200",
borderWidth: "0 0 1px",
borderStyle: "solid",
tag: "header",
children: [
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
tag: "h2",
variant: "omega",
fontWeight: "semiBold",
children: formatMessage({
id: 'content-manager.history.sidebar.title',
defaultMessage: 'Versions'
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
background: "neutral150",
hasRadius: true,
padding: 1,
children: /*#__PURE__*/ jsxRuntime.jsx(designSystem.Typography, {
variant: "sigma",
textColor: "neutral600",
children: versions.meta.pagination.total
})
})
]
}),
/*#__PURE__*/ jsxRuntime.jsxs(designSystem.Box, {
flex: 1,
overflow: "auto",
children: [
versions.meta.pagination.page > 1 && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingTop: 4,
textAlign: "center",
children: /*#__PURE__*/ jsxRuntime.jsx(PaginationButton, {
page: page - 1,
children: formatMessage({
id: 'content-manager.history.sidebar.show-newer',
defaultMessage: 'Show newer versions'
})
})
}),
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Flex, {
direction: "column",
gap: 3,
padding: 4,
tag: "ul",
alignItems: "stretch",
children: versions.data.map((version, index)=>/*#__PURE__*/ jsxRuntime.jsx("li", {
"aria-label": formatMessage({
id: 'content-manager.history.sidebar.title.version-card.aria-label',
defaultMessage: 'Version card'
}),
children: /*#__PURE__*/ jsxRuntime.jsx(VersionCard, {
version: version,
isCurrent: page === 1 && index === 0
})
}, version.id))
}),
versions.meta.pagination.page < versions.meta.pagination.pageCount && /*#__PURE__*/ jsxRuntime.jsx(designSystem.Box, {
paddingBottom: 4,
textAlign: "center",
children: /*#__PURE__*/ jsxRuntime.jsx(PaginationButton, {
page: page + 1,
children: formatMessage({
id: 'content-manager.history.sidebar.show-older',
defaultMessage: 'Show older versions'
})
})
})
]
})
]
});
};
exports.VersionsList = VersionsList;
//# sourceMappingURL=VersionsList.js.map

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More